Skip to content

Commit 656865d

Browse files
committed
src/goInstallTools: use go install for go1.16+
`go install` with the version specifier is THE way to install a binary in recent versions of Go. From go1.18, `go get` will not do build & install so shouldn't be used. For `dlv-dap` or `gocode-gomod`, we use `go get` + `go build` because `go install` does not allow to specify output path. (golang/go#44469) Use of `go install` allows us to address another issue: we no longer require a dummy main module in a temp directory, and we can run it from the workspace directory. The use of temp directory broke direnv, asdf users who configured their go to pick a different version of go based on the directory. Thus, we pinned the go binary by selecting the go binary from the current GOROOT's bin directory. Unfortunately, that broke another group of users who are using GOROOT for different purpose. (#1691) Now we can remove the hack to pin the go binary and use the go binary path as it is, and simplifies the installation logic. Reduced the verbose logging printed in the console. Instead, we log the command and the directory path so users could reproduce. In case of errors, the exception still includes all the stdout and stderr. Updates #1825 Change-Id: Idb1604e4484cd73832c24c0077220f8cc57dfaa0 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/355974 Trust: Hyang-Ah Hana Kim <[email protected]> Run-TryBot: Hyang-Ah Hana Kim <[email protected]> TryBot-Result: kokoro <[email protected]> Reviewed-by: Suzy Mueller <[email protected]>
1 parent af8377d commit 656865d

File tree

2 files changed

+79
-56
lines changed

2 files changed

+79
-56
lines changed

src/goInstallTools.ts

Lines changed: 78 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -157,25 +157,16 @@ export async function installTools(
157157

158158
outputChannel.appendLine(''); // Blank line for spacing.
159159

160-
const toInstall: Promise<{ tool: Tool; reason: string }>[] = [];
160+
const failures: { tool: ToolAtVersion; reason: string }[] = [];
161161
for (const tool of missing) {
162162
const modulesOffForTool = modulesOff;
163163

164-
const reason = installTool(tool, goVersion, envForTools, !modulesOffForTool);
165-
toInstall.push(Promise.resolve({ tool, reason: await reason }));
166-
}
167-
168-
const results = await Promise.all(toInstall);
169-
170-
const failures: { tool: ToolAtVersion; reason: string }[] = [];
171-
for (const result of results) {
172-
if (result.reason === '') {
164+
const failed = await installTool(tool, goVersion, envForTools, !modulesOffForTool);
165+
if (failed) {
166+
failures.push({ tool, reason: failed });
167+
} else if (tool.name === 'gopls') {
173168
// Restart the language server if a new binary has been installed.
174-
if (result.tool.name === 'gopls') {
175-
restartLanguageServer('installation');
176-
}
177-
} else {
178-
failures.push(result);
169+
restartLanguageServer('installation');
179170
}
180171
}
181172

@@ -222,16 +213,61 @@ export async function installTool(
222213
return reason;
223214
}
224215
}
225-
let toolsTmpDir = '';
216+
217+
const env = Object.assign({}, envForTools);
218+
env['GO111MODULE'] = modulesOn ? 'on' : 'off';
219+
220+
let importPath: string;
221+
if (!modulesOn) {
222+
importPath = getImportPath(tool, goVersion);
223+
} else {
224+
let version: semver.SemVer | string | undefined = tool.version;
225+
if (!version) {
226+
if (tool.usePrereleaseInPreviewMode && isInPreviewMode()) {
227+
version = await latestToolVersion(tool, true);
228+
} else if (tool.defaultVersion) {
229+
version = tool.defaultVersion;
230+
}
231+
}
232+
importPath = getImportPathWithVersion(tool, version, goVersion);
233+
}
234+
226235
try {
227-
toolsTmpDir = await tmpDirForToolInstallation();
236+
if (!modulesOn || goVersion.lt('1.16') || hasModSuffix(tool) || tool.name === 'dlv-dap') {
237+
await installToolWithGoGet(tool, goVersion, env, modulesOn, importPath);
238+
} else {
239+
await installToolWithGoInstall(goVersion, env, importPath);
240+
}
241+
const toolInstallPath = getBinPath(tool.name);
242+
outputChannel.appendLine(`Installing ${importPath} (${toolInstallPath}) SUCCEEDED`);
228243
} catch (e) {
229-
return `Failed to create a temp directory: ${e}`;
244+
outputChannel.appendLine(`Installing ${importPath} FAILED`);
245+
outputChannel.appendLine(`${JSON.stringify(e, null, 1)}`);
246+
return `failed to install ${tool.name}(${importPath}): ${e}`;
230247
}
248+
}
231249

232-
const env = Object.assign({}, envForTools);
233-
env['GO111MODULE'] = modulesOn ? 'on' : 'off';
250+
async function installToolWithGoInstall(goVersion: GoVersion, env: NodeJS.Dict<string>, importPath: string) {
251+
// Unlike installToolWithGoGet, `go install` in module mode
252+
// can run in the current directory safely. So, use the user-specified go tool path.
253+
const goBinary = goVersion.binaryPath || getBinPath('go');
254+
const opts = {
255+
env,
256+
cwd: getWorkspaceFolderPath()
257+
};
234258

259+
const execFile = util.promisify(cp.execFile);
260+
logVerbose(`$ ${goBinary} install -x ${importPath}} (cwd: ${opts.cwd})`);
261+
await execFile(goBinary, ['install', '-x', importPath], opts);
262+
}
263+
264+
async function installToolWithGoGet(
265+
tool: ToolAtVersion,
266+
goVersion: GoVersion,
267+
env: NodeJS.Dict<string>,
268+
modulesOn: boolean,
269+
importPath: string
270+
) {
235271
// Some users use direnv-like setup where the choice of go is affected by
236272
// the current directory path. In order to avoid choosing a different go,
237273
// we will explicitly use `GOROOT/bin/go` instead of goVersion.binaryPath
@@ -241,7 +277,7 @@ export async function installTool(
241277
: goVersion.binaryPath;
242278

243279
// Build the arguments list for the tool installation.
244-
const args = ['get', '-v'];
280+
const args = ['get', '-x'];
245281
// Only get tools at master if we are not using modules.
246282
if (!modulesOn) {
247283
args.push('-u');
@@ -251,33 +287,24 @@ export async function installTool(
251287
if (hasModSuffix(tool) || tool.name === 'dlv-dap') {
252288
args.push('-d'); // get the version, but don't build.
253289
}
254-
let importPath: string;
255-
if (!modulesOn) {
256-
importPath = getImportPath(tool, goVersion);
257-
} else {
258-
let version: semver.SemVer | string | undefined = tool.version;
259-
if (!version) {
260-
if (tool.usePrereleaseInPreviewMode && isInPreviewMode()) {
261-
version = await latestToolVersion(tool, true);
262-
} else if (tool.defaultVersion) {
263-
version = tool.defaultVersion;
264-
}
265-
}
266-
importPath = getImportPathWithVersion(tool, version, goVersion);
267-
}
268290
args.push(importPath);
269291

270-
let output = 'no output';
271-
let result = '';
292+
let toolsTmpDir = '';
293+
try {
294+
toolsTmpDir = await tmpDirForToolInstallation();
295+
} catch (e) {
296+
throw new Error(`Failed to create a temp directory: ${e}`);
297+
}
298+
299+
const opts = {
300+
env,
301+
cwd: toolsTmpDir
302+
};
272303
try {
273-
const opts = {
274-
env,
275-
cwd: toolsTmpDir
276-
};
277304
const execFile = util.promisify(cp.execFile);
278-
const { stdout, stderr } = await execFile(goBinary, args, opts);
279-
output = `${stdout} ${stderr}`;
280-
logVerbose(`install: ${goBinary} ${args.join(' ')}\n${stdout}${stderr}`);
305+
logVerbose(`$ ${goBinary} ${args.join(' ')} (cwd: ${opts.cwd})`);
306+
await execFile(goBinary, args, opts);
307+
281308
if (hasModSuffix(tool) || tool.name === 'dlv-dap') {
282309
// Actual installation of the -gomod tool and dlv-dap is done by running go build.
283310
let destDir = env['GOBIN'];
@@ -291,21 +318,17 @@ export async function installTool(
291318
const outputFile = path.join(destDir, correctBinname(tool.name));
292319

293320
// go build does not take @version suffix yet.
294-
const importPath = getImportPath(tool, goVersion);
295-
await execFile(goBinary, ['build', '-o', outputFile, importPath], opts);
321+
const importPathWithoutVersion = getImportPath(tool, goVersion);
322+
logVerbose(`$ ${goBinary} build -o ${outputFile} ${importPathWithoutVersion} (cwd: ${opts.cwd})`);
323+
await execFile(goBinary, ['build', '-o', outputFile, importPathWithoutVersion], opts);
296324
}
297-
const toolInstallPath = getBinPath(tool.name);
298-
outputChannel.appendLine(`Installing ${importPath} (${toolInstallPath}) SUCCEEDED`);
299325
} catch (e) {
300-
outputChannel.appendLine(`Installing ${importPath} FAILED`);
301-
outputChannel.appendLine(`${JSON.stringify(e, null, 1)}`);
302-
result = `failed to install ${tool.name}(${importPath}): ${e} ${output}`;
326+
logVerbose(`FAILED: ${JSON.stringify(e, null, 1)}`);
327+
throw e;
303328
} finally {
304329
// Delete the temporary installation directory.
305330
rmdirRecursive(toolsTmpDir);
306331
}
307-
308-
return result;
309332
}
310333

311334
export function declinedToolInstall(toolName: string) {
@@ -662,11 +685,11 @@ async function defaultInspectGoToolVersion(binPath: string): Promise<{ goVersion
662685
dep github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
663686
664687
if the binary was built in GOPATH mode => the following code will throw an error which will be handled.
665-
/Users/hakim/go/bin/gopls: go1.16
688+
/Users/hakim/go/bin/gopls: go1.16
666689
667690
if the binary was built in dev branch, in module mode => the following code will not throw an error,
668691
and return (devel) as the moduleVersion.
669-
/Users/hakim/go/bin/gopls: go1.16
692+
/Users/hakim/go/bin/gopls: go1.16
670693
path golang.org/x/tools/gopls
671694
mod golang.org/x/tools/gopls (devel)
672695
dep github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=

src/goTools.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export function getImportPathWithVersion(
8686
return importPath + '@' + version;
8787
}
8888
}
89-
return importPath;
89+
return importPath + '@latest';
9090
}
9191

9292
export function containsTool(tools: Tool[], tool: Tool): boolean {

0 commit comments

Comments
 (0)