Skip to content

Commit b78b78e

Browse files
committed
extension/src/goInstallTools.ts: require go1.21+ for tools installation
The Go extension will require go1.21 for tools installation from v0.44.0 (and is prerelease version v0.43.x). This is a planned change and it was discussed in the v0.42.0 release note. (https://github.com/golang/vscode-go/releases/tag/v0.42.0 Jul 17 2024). `installTools` is the entry function for tools installation. If the go version is too old, it suggests go1.21+ or the workaround (go.toolsManagement.go). * Misc changes - Previously, when the build info of a binary is not available, we didn't ask to update the tool. Since go1.18, the build info should be available. So, now suggest to reinstall the tool. - Bug fix: For vscgo, we used toolExecutionEnvironment when running go install. It should be toolInstallationEnvironment. This clears some env vars like GO111MODULE, GOPROXY, GOOS, GOARCH, GOROOT which can interfere with the go tool invocation. Fixes #3411 Change-Id: Ifff0661d88a9adfc6bd3e0a25702d91921bcb77f Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/616676 Reviewed-by: Robert Findley <[email protected]> Commit-Queue: Hyang-Ah Hana Kim <[email protected]> kokoro-CI: kokoro <[email protected]> Reviewed-by: Hongxiang Jiang <[email protected]>
1 parent c107653 commit b78b78e

File tree

4 files changed

+118
-87
lines changed

4 files changed

+118
-87
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
55

66
## Unreleased
77

8+
### Changes
9+
10+
#### Tools installation
11+
12+
* The extension requires go1.21 or newer when it installs required tools. If your project must use go1.20 or older,
13+
please manually install [compatible versions of required tools](https://github.com/golang/vscode-go/wiki/compatibility),
14+
or configure the [`"go.toolsManagement.go"` setting](https://github.com/golang/vscode-go/wiki/settings#gotoolsmanagementgo)
15+
to use the go1.21 or newer when installing tools. ([Issue 3411](https://github.com/golang/vscode-go/issues/3411))
16+
817
### Code Health
918

1019
* Extension build target is set to `es2022`. ([Issue 3540](https://github.com/golang/vscode-go/issues/3540))

extension/src/goInstallTools.ts

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ import { allToolsInformation } from './goToolsInformation';
4444

4545
const STATUS_BAR_ITEM_NAME = 'Go Tools';
4646

47+
// minimum go version required for tools installation.
48+
const MINIMUM_GO_VERSION = '1.21.0';
49+
4750
// declinedUpdates tracks the tools that the user has declined to update.
4851
const declinedUpdates: Tool[] = [];
4952

@@ -52,7 +55,7 @@ const declinedInstalls: Tool[] = [];
5255

5356
export interface IToolsManager {
5457
getMissingTools(filter: (tool: Tool) => boolean): Promise<Tool[]>;
55-
installTool(tool: Tool, goVersion: GoVersion, env: NodeJS.Dict<string>): Promise<string | undefined>;
58+
installTool(tool: Tool, goVersionForInstall: GoVersion, env: NodeJS.Dict<string>): Promise<string | undefined>;
5659
}
5760

5861
export const defaultToolsManager: IToolsManager = {
@@ -105,20 +108,27 @@ export async function installAllTools(updateExistingToolsOnly = false) {
105108
);
106109
}
107110

108-
export const getGoForInstall = _getGoForInstall;
109-
async function _getGoForInstall(goVersion: GoVersion): Promise<GoVersion | undefined> {
110-
let configured = getGoConfig().get<string>('toolsManagement.go');
111-
if (!configured) {
112-
configured = goVersion.binaryPath;
111+
// Returns the go version to be used for tools installation.
112+
// If `go.toolsManagement.go` is set, it is preferred. Otherwise, the provided
113+
// goVersion or the default version returned by getGoVersion is returned.
114+
export const getGoVersionForInstall = _getGoVersionForInstall;
115+
async function _getGoVersionForInstall(goVersion?: GoVersion): Promise<GoVersion | undefined> {
116+
let configuredGoForInstall = getGoConfig().get<string>('toolsManagement.go');
117+
if (!configuredGoForInstall) {
118+
// A separate Go for install is not configured. Use the default Go.
119+
const defaultGoVersion = goVersion ?? (await getGoVersion());
120+
configuredGoForInstall = defaultGoVersion?.binaryPath;
113121
}
114122
try {
115123
// goVersion may be the version picked based on the the minimum
116124
// toolchain version requirement specified in go.mod or go.work.
117125
// Compute the local toolchain version. (GOTOOLCHAIN=local go version)
118-
const go = await getGoVersion(configured, 'local');
126+
const go = await getGoVersion(configuredGoForInstall, 'local');
119127
if (go) return go;
120128
} catch (e) {
121-
outputChannel.error(`failed to run "go version" with "${configured}". Provide a valid path to the Go binary`);
129+
outputChannel.error(
130+
`failed to run "go version" with "${configuredGoForInstall}". Provide a valid path to the Go binary`
131+
);
122132
}
123133
return;
124134
}
@@ -155,7 +165,7 @@ export async function installTools(
155165
outputChannel.show();
156166
}
157167

158-
const goForInstall = await getGoForInstall(goVersion);
168+
const goForInstall = await getGoVersionForInstall(goVersion);
159169
if (!goForInstall || !goForInstall.isValid()) {
160170
vscode.window.showErrorMessage('Failed to find a go command needed to install tools.');
161171
outputChannel.show(); // show error.
@@ -164,16 +174,15 @@ export async function installTools(
164174
});
165175
}
166176

167-
const minVersion = goForInstall.lt('1.21') ? (goVersion.lt('1.19') ? '1.19' : goVersion.format()) : '1.21.0';
168-
if (goForInstall.lt(minVersion)) {
177+
if (goForInstall.lt(MINIMUM_GO_VERSION)) {
169178
vscode.window.showErrorMessage(
170-
`Failed to find a go command (go${minVersion} or newer) needed to install tools. ` +
179+
`Failed to find a go command (go${MINIMUM_GO_VERSION} or newer) needed to install tools. ` +
171180
`The go command (${goForInstall.binaryPath}) is too old (go${goForInstall.svString}). ` +
172-
'If your project requires a Go version older than go1.19, either manually install the tools or, use the "go.toolsManagement.go" setting ' +
173-
'to configure the Go version used for tools installation. See https://github.com/golang/vscode-go/issues/2898.'
181+
`If your project requires a Go version older than go${MINIMUM_GO_VERSION}, please manually install the tools or, use the "go.toolsManagement.go" setting ` +
182+
`to configure a different go command (go ${MINIMUM_GO_VERSION}+) to be used for tools installation. See https://github.com/golang/vscode-go/issues/3411.`
174183
);
175184
return missing.map((tool) => {
176-
return { tool: tool, reason: `failed to find go (requires go${minVersion} or newer)` };
185+
return { tool: tool, reason: `failed to find go (requires go${MINIMUM_GO_VERSION} or newer)` };
177186
});
178187
}
179188

@@ -273,20 +282,21 @@ async function tmpDirForToolInstallation() {
273282
return toolsTmpDir;
274283
}
275284

276-
// installTool installs the specified tool.
285+
// installTool is used by goEnvironmentStatus.ts.
286+
// TODO(hyangah): replace the callsite to use defaultToolsManager and remove this.
277287
export async function installTool(tool: ToolAtVersion): Promise<string | undefined> {
278-
const goVersion = await getGoForInstall(await getGoVersion());
279-
if (!goVersion) {
288+
const goVersionForInstall = await getGoVersionForInstall();
289+
if (!goVersionForInstall) {
280290
return 'failed to find "go" for install';
281291
}
282292
const envForTools = toolInstallationEnvironment();
283293

284-
return await installToolWithGo(tool, goVersion, envForTools);
294+
return await installToolWithGo(tool, goVersionForInstall, envForTools);
285295
}
286296

287297
async function installToolWithGo(
288298
tool: ToolAtVersion,
289-
goVersion: GoVersion, // go version to be used for installation.
299+
goVersionForInstall: GoVersion, // go version used to install the tool.
290300
envForTools: NodeJS.Dict<string>
291301
): Promise<string | undefined> {
292302
const env = Object.assign({}, envForTools);
@@ -295,10 +305,14 @@ async function installToolWithGo(
295305
if (!version && tool.usePrereleaseInPreviewMode && extensionInfo.isPreview) {
296306
version = await latestToolVersion(tool, true);
297307
}
298-
const importPath = getImportPathWithVersion(tool, version, goVersion);
308+
// TODO(hyangah): should we allow to choose a different version of the tool
309+
// depending on the project's go version (i.e. getGoVersion())? For example,
310+
// if a user is using go1.20 for their project, should we pick [email protected]
311+
// instead? In that case, we should pass getGoVersion().
312+
const importPath = getImportPathWithVersion(tool, version, goVersionForInstall);
299313

300314
try {
301-
await installToolWithGoInstall(goVersion, env, importPath);
315+
await installToolWithGoInstall(goVersionForInstall, env, importPath);
302316
const toolInstallPath = getBinPath(tool.name);
303317
outputChannel.appendLine(`Installing ${importPath} (${toolInstallPath}) SUCCEEDED`);
304318
} catch (e) {
@@ -533,8 +547,9 @@ export function updateGoVarsFromConfig(goCtx: GoExtensionContext): Promise<void>
533547
});
534548
}
535549

536-
// maybeInstallImportantTools checks whether important tools are installed,
537-
// and tries to auto-install them if missing.
550+
// maybeInstallImportantTools checks whether important tools are installed
551+
// and they meet the version requirement.
552+
// Then it tries to auto-install them if missing.
538553
export async function maybeInstallImportantTools(
539554
alternateTools: { [key: string]: string } | undefined,
540555
tm: IToolsManager = defaultToolsManager
@@ -783,10 +798,10 @@ export async function shouldUpdateTool(tool: Tool, toolPath: string): Promise<bo
783798
}
784799

785800
export async function suggestUpdates() {
786-
const configuredGoVersion = await getGoVersion();
787-
if (!configuredGoVersion || configuredGoVersion.lt('1.19')) {
788-
// User is using an ancient or a dev version of go. Don't suggest updates -
789-
// user should know what they are doing.
801+
const configuredGoVersion = await getGoVersionForInstall();
802+
if (!configuredGoVersion || configuredGoVersion.lt(MINIMUM_GO_VERSION)) {
803+
// User is using an old or dev version of go.
804+
// Don't suggest updates.
790805
return;
791806
}
792807

@@ -830,15 +845,13 @@ export async function listOutdatedTools(configuredGoVersion: GoVersion | undefin
830845
return;
831846
}
832847
const m = await inspectGoToolVersion(toolPath);
833-
if (!m) {
834-
console.log(`failed to get go tool version: ${toolPath}`);
835-
return;
836-
}
837-
const { goVersion } = m;
848+
const { goVersion } = m || {};
838849
if (!goVersion) {
839-
// TODO: we cannot tell whether the tool was compiled with a newer version of go
850+
// The tool was compiled with a newer version of go
851+
// or a very old go (<go1.18)
840852
// or compiled in an unconventional way.
841-
return;
853+
// Suggest to reinstall the tool anyway.
854+
return tool;
842855
}
843856
const toolGoVersion = new GoVersion('', `go version ${goVersion} os/arch`);
844857
if (!toolGoVersion || !toolGoVersion.sv) {
@@ -860,12 +873,12 @@ export async function listOutdatedTools(configuredGoVersion: GoVersion | undefin
860873
// We test the inequality by checking whether the exact beta or rc version
861874
// appears in the `go version` output. e.g.,
862875
// configuredGoVersion.version goVersion(tool) update
863-
// 'go version go1.21 ...' 'go1.21beta1' Yes
864-
// 'go version go1.21beta1 ...' 'go1.21beta1' No
865-
// 'go version go1.21beta2 ...' 'go1.21beta1' Yes
866-
// 'go version go1.21rc1 ...' 'go1.21beta1' Yes
876+
// 'go version go1.21 ...' 'go1.21rc1' Yes
877+
// 'go version go1.21rc1 ...' 'go1.21rc1' No
878+
// 'go version go1.21rc2 ...' 'go1.21rc1' Yes
879+
// 'go version go1.21rc1 ...' 'go1.21rc1' Yes
867880
// 'go version go1.21rc1 ...' 'go1.21' No
868-
// 'go version devel go1.21-deadbeaf ...' 'go1.21beta1' No (* rare)
881+
// 'go version devel go1.21-deadbeef ...' 'go1.21rc1' No (* rare)
869882
!configuredGoVersion.version.includes(goVersion)
870883
) {
871884
return tool;
@@ -899,7 +912,7 @@ export async function maybeInstallVSCGO(
899912
const execFile = util.promisify(cp.execFile);
900913

901914
const cwd = path.join(extensionPath);
902-
const env = toolExecutionEnvironment();
915+
const env = toolInstallationEnvironment();
903916
env['GOBIN'] = path.dirname(progPath);
904917

905918
const importPath = allToolsInformation['vscgo'].importPath;
@@ -911,9 +924,14 @@ export async function maybeInstallVSCGO(
911924
: `@v${extensionVersion}`;
912925
// build from source acquired from the module proxy if this is a non-preview version.
913926
try {
927+
const goForInstall = await getGoVersionForInstall();
928+
const goBinary = goForInstall?.binaryPath;
929+
if (!goBinary) {
930+
throw new Error('"go" binary is not found');
931+
}
914932
const args = ['install', '-trimpath', `${importPath}${version}`];
915933
console.log(`installing vscgo: ${args.join(' ')}`);
916-
await execFile(getBinPath('go'), args, { cwd, env });
934+
await execFile(goBinary, args, { cwd, env });
917935
return progPath;
918936
} catch (e) {
919937
telemetryReporter.add('vscgo_install_fail', 1);

extension/src/goTools.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ export interface Tool {
2020
replacedByGopls?: boolean;
2121
description: string;
2222

23-
// If true, consider prerelease version in preview mode
24-
// (nightly & dev)
23+
// If true, consider prerelease version in prerelease mode
24+
// (prerelease & dev)
2525
usePrereleaseInPreviewMode?: boolean;
2626
// If set, this string will be used when installing the tool
2727
// instead of the default 'latest'. It can be used when
28-
// we need to pin a tool version (`deadbeaf`) or to use
28+
// we need to pin a tool version (`deadbeef`) or to use
2929
// a dev version available in a branch (e.g. `master`).
3030
defaultVersion?: string;
3131

@@ -53,7 +53,7 @@ export interface ToolAtVersion extends Tool {
5353
export function getImportPathWithVersion(
5454
tool: Tool,
5555
version: semver.SemVer | string | undefined | null,
56-
goVersion: GoVersion
56+
goVersion: GoVersion // This is the Go version to build the project.
5757
): string {
5858
const importPath = tool.importPath;
5959
if (version) {

0 commit comments

Comments
 (0)