src/goInstallTools.ts: mutate PATH only when necessary

When the extension chooses a different version of go
than the one from the system default - because the user
has configured `go.goroot` or `go.alternateTools.go`, or
used the `Go: Choose Go Environment` command - the extension
mutates the `PATH` (or `Path` on windows) environment variable
so all the underlying tools pick the same go version.
It also changes the environment variable collection used
in the integrated terminal so when the user invokes `go`
from the terminal, they use the go version consistent
with the extension.

But this behavior can conflict with external version
management software. Change the PATH environment only
if the extension is configured to choose the go binary.

Fixes golang/vscode-go#679.
Update golang/vscode-go#544.

Change-Id: I9f7acb26b752ed33dbde2b238a67ed09616b43e5
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/256938
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts
index 63856fc..4065f89 100644
--- a/src/goInstallTools.ts
+++ b/src/goInstallTools.ts
@@ -28,6 +28,7 @@
 	Tool,
 	ToolAtVersion,
 } from './goTools';
+import { getFromWorkspaceState } from './stateUtils';
 import {
 	getBinPath,
 	getGoConfig,
@@ -37,7 +38,7 @@
 	GoVersion,
 	rmdirRecursive,
 } from './util';
-import { envPath, getCurrentGoRoot, getToolFromToolPath, setCurrentGoRoot } from './utils/pathUtils';
+import { correctBinname, envPath, getCurrentGoRoot, getToolFromToolPath, setCurrentGoRoot } from './utils/pathUtils';
 
 // declinedUpdates tracks the tools that the user has declined to update.
 const declinedUpdates: Tool[] = [];
@@ -343,11 +344,11 @@
 }
 
 export function updateGoVarsFromConfig(): Promise<void> {
-	// FIXIT: if updateGoVarsFromConfig is called again after addGoRuntimeBaseToPATH sets PATH,
-	// the go chosen by getBinPath based on PATH will not change.
 	const goRuntimePath = getBinPath('go', false);
 	logVerbose(`updateGoVarsFromConfig: found 'go' in ${goRuntimePath}`);
-	if (!goRuntimePath) {
+	if (!goRuntimePath || !path.isAbsolute(goRuntimePath)) {
+		// getBinPath returns the absolute path to the tool if it exists.
+		// Otherwise, it may return the tool name (e.g. 'go').
 		suggestDownloadGo();
 		return Promise.reject();
 	}
@@ -357,43 +358,81 @@
 			['env', 'GOPATH', 'GOROOT', 'GOPROXY', 'GOBIN', 'GOMODCACHE'],
 			{ env: toolExecutionEnvironment(), cwd: getWorkspaceFolderPath() },
 			(err, stdout, stderr) => {
-			if (err || stderr) {
-				outputChannel.append(`Failed to run '${goRuntimePath} env: ${err}\n${stderr}`);
-				outputChannel.show();
+				if (err || stderr) {
+					outputChannel.append(`Failed to run '${goRuntimePath} env: ${err}\n${stderr}`);
+					outputChannel.show();
 
-				vscode.window.showErrorMessage(`Failed to run '${goRuntimePath} env. The config change may not be applied correctly.`);
-				return reject();
-			}
-			logVerbose(`${goRuntimePath} env ...:\n${stdout}`);
+					vscode.window.showErrorMessage(`Failed to run '${goRuntimePath} env. The config change may not be applied correctly.`);
+					return reject();
+				}
+				logVerbose(`${goRuntimePath} env ...:\n${stdout}`);
+				const envOutput = stdout.split('\n');
+				if (!process.env['GOPATH'] && envOutput[0].trim()) {
+					process.env['GOPATH'] = envOutput[0].trim();
+				}
+				if (envOutput[1] && envOutput[1].trim()) {
+					setCurrentGoRoot(envOutput[1].trim());
+				}
+				if (!process.env['GOPROXY'] && envOutput[2] && envOutput[2].trim()) {
+					process.env['GOPROXY'] = envOutput[2].trim();
+				}
+				if (!process.env['GOBIN'] && envOutput[3] && envOutput[3].trim()) {
+					process.env['GOBIN'] = envOutput[3].trim();
+				}
+				if (!process.env['GOMODCACHE'] && envOutput[4] && envOutput[4].trim()) {
+					process.env['GOMODCACHE'] = envOutput[4].trim();
+				}
 
-			const envOutput = stdout.split('\n');
-			if (!process.env['GOPATH'] && envOutput[0].trim()) {
-				process.env['GOPATH'] = envOutput[0].trim();
-			}
-			if (envOutput[1] && envOutput[1].trim()) {
-				setCurrentGoRoot(envOutput[1].trim());
-			}
-			if (!process.env['GOPROXY'] && envOutput[2] && envOutput[2].trim()) {
-				process.env['GOPROXY'] = envOutput[2].trim();
-			}
-			if (!process.env['GOBIN'] && envOutput[3] && envOutput[3].trim()) {
-				process.env['GOBIN'] = envOutput[3].trim();
-			}
-			if (!process.env['GOMODCACHE'] && envOutput[4] && envOutput[4].trim()) {
-				process.env['GOMODCACHE'] = envOutput[4].trim();
-			}
+				// cgo, gopls, and other underlying tools will inherit the environment and attempt
+				// to locate 'go' from the PATH env var.
+				// Update the PATH only if users configured to use a different
+				// version of go than the system default.
+				if (!!goPickedByExtension()) {
+					addGoRuntimeBaseToPATH(path.join(getCurrentGoRoot(), 'bin'));
+				}
+				initGoStatusBar();
+				// TODO: restart language server or synchronize with language server update.
 
-			// cgo, gopls, and other underlying tools will inherit the environment and attempt
-			// to locate 'go' from the PATH env var.
-			addGoRuntimeBaseToPATH(path.join(getCurrentGoRoot(), 'bin'));
-			initGoStatusBar();
-			// TODO: restart language server or synchronize with language server update.
-
-			return resolve();
-		});
+				return resolve();
+			});
 	});
 }
 
+// The go command is picked up by searching directories in PATH by default.
+// But users can override it and force the extension to pick a different
+// one by configuring
+//
+//   1) with the go.environment.choose command, which stores the selection
+//      in the workspace memento with the key 'selectedGo',
+//   2) with 'go.alternateTools': { 'go': ... } setting, or
+//   3) with 'go.goroot' setting
+//
+// goPickedByExtension returns the chosen path if the default path should
+// be overridden by above methods.
+// TODO: This logic is duplicated in getBinPath. Centralize this logic.
+function goPickedByExtension(): string | undefined {
+	// getFromWorkspaceState('selectedGo')
+	const selectedGoPath: string = getFromWorkspaceState('selectedGo')?.binpath;
+	if (selectedGoPath) {
+		return selectedGoPath;
+	}
+
+	const cfg = getGoConfig();
+
+	// 'go.alternateTools.go'
+	const alternateTools: { [key: string]: string } = cfg.get('alternateTools');
+	const alternateToolPath: string = alternateTools['go'];
+	if (alternateToolPath) {
+		return alternateToolPath;
+	}
+	// 'go.goroot'
+	const goRoot: string = cfg.get('goroot');
+	if (goRoot) {
+		return path.join(goRoot, 'bin', correctBinname('go'));
+	}
+	return undefined;
+}
+
 let alreadyOfferedToInstallTools = false;
 
 export async function offerToInstallTools() {