src/goMain: overwrite process.env[GOROOT] if go.goroot is set

Moreover, if go.goroot is set or process.env[GOROOT] is not empty,
disallow go.environment.choose command.

And, fix addGoRuntimeBaseToPATH on macOS (on MacOS, we depend
on the terminalCreationListener to reflect the update PATH to
the terminal. When it is changed, we should dispose the previous
listener and set up a new one).

Change-Id: I497eadc7f0994c861cf597d8d5c6e9a5e9b1258b
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/310853
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/goEnvironmentStatus.ts b/src/goEnvironmentStatus.ts
index b236e16..1190b31 100644
--- a/src/goEnvironmentStatus.ts
+++ b/src/goEnvironmentStatus.ts
@@ -32,7 +32,7 @@
 	fixDriveCasingInWindows,
 	getBinPathFromEnvVar,
 	getCurrentGoRoot,
-	pathExists
+	dirExists
 } from './utils/pathUtils';
 import vscode = require('vscode');
 import WebRequest = require('web-request');
@@ -63,6 +63,22 @@
 const CLEAR_SELECTION = '$(clear-all) Clear selection';
 const CHOOSE_FROM_FILE_BROWSER = '$(folder) Choose from file browser';
 
+function canChooseGoEnvironment() {
+	// if there is no workspace, show GOROOT with message
+	if (!vscode.workspace.name) {
+		return { ok: false, reason: 'Switching Go version is not yet supported in single-file mode.' };
+	}
+
+	if (getGoConfig().get('goroot')) {
+		return { ok: false, reason: 'Switching Go version when "go.goroot" is set is unsupported.' };
+	}
+
+	if (process.env['GOROOT']) {
+		return { ok: false, reason: 'Switching Go version when process.env["GOROOT"] is set is unsupported.' };
+	}
+
+	return { ok: true };
+}
 /**
  * Present a command palette menu to the user to select their go binary
  */
@@ -70,12 +86,9 @@
 	if (!goEnvStatusbarItem) {
 		return;
 	}
-
-	// if there is no workspace, show GOROOT with message
-	if (!vscode.workspace.name) {
-		vscode.window.showInformationMessage(
-			`GOROOT: ${getCurrentGoRoot()}. Switching Go version is not yet supported in single-file mode.`
-		);
+	const { ok, reason } = canChooseGoEnvironment();
+	if (!ok) {
+		vscode.window.showInformationMessage(`GOROOT: ${getCurrentGoRoot()}. ${reason}`);
 		return;
 	}
 
@@ -265,7 +278,7 @@
 
 			outputChannel.appendLine('Finding newly downloaded Go');
 			const sdkPath = path.join(os.homedir(), 'sdk');
-			if (!(await pathExists(sdkPath))) {
+			if (!(await dirExists(sdkPath))) {
 				outputChannel.appendLine(`SDK path does not exist: ${sdkPath}`);
 				throw new Error(`SDK path does not exist: ${sdkPath}`);
 			}
@@ -343,9 +356,10 @@
 			for (const term of vscode.window.terminals) {
 				updateIntegratedTerminal(term);
 			}
-			if (!terminalCreationListener) {
-				terminalCreationListener = vscode.window.onDidOpenTerminal(updateIntegratedTerminal);
+			if (terminalCreationListener) {
+				terminalCreationListener.dispose();
 			}
+			terminalCreationListener = vscode.window.onDidOpenTerminal(updateIntegratedTerminal);
 		} else {
 			environmentVariableCollection?.prepend(pathEnvVar, newGoRuntimeBase + path.delimiter);
 		}
@@ -439,7 +453,7 @@
 	// get list of Go versions
 	const sdkPath = path.join(os.homedir(), 'sdk');
 
-	if (!(await pathExists(sdkPath))) {
+	if (!(await dirExists(sdkPath))) {
 		return [];
 	}
 	const readdir = promisify(fs.readdir);
diff --git a/src/goMain.ts b/src/goMain.ts
index 0fcd4e5..28e4956 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -96,7 +96,7 @@
 	isGoPathSet,
 	resolvePath
 } from './util';
-import { clearCacheForTools, fileExists, getCurrentGoRoot, setCurrentGoRoot } from './utils/pathUtils';
+import { clearCacheForTools, fileExists, getCurrentGoRoot, dirExists, setCurrentGoRoot } from './utils/pathUtils';
 import { WelcomePanel } from './welcome';
 import semver = require('semver');
 import vscode = require('vscode');
@@ -151,8 +151,11 @@
 
 	const configGOROOT = getGoConfig()['goroot'];
 	if (configGOROOT) {
-		logVerbose(`go.goroot = '${configGOROOT}'`);
-		setCurrentGoRoot(resolvePath(configGOROOT));
+		const goroot = resolvePath(configGOROOT);
+		if (dirExists(goroot)) {
+			logVerbose(`setting GOROOT = ${goroot} because "go.goroot": "${configGOROOT}"`);
+			process.env['GOROOT'] = goroot;
+		}
 	}
 
 	// Present a warning about the deprecation of the go.documentLink setting.
@@ -443,6 +446,17 @@
 			}
 			const updatedGoConfig = getGoConfig();
 
+			if (e.affectsConfiguration('go.goroot')) {
+				const configGOROOT = updatedGoConfig['goroot'];
+				if (configGOROOT) {
+					const goroot = resolvePath(configGOROOT);
+					const oldGoroot = process.env['GOROOT'];
+					if (oldGoroot !== goroot && dirExists(goroot)) {
+						logVerbose(`setting GOROOT = ${goroot} because "go.goroot": "${configGOROOT}"`);
+						process.env['GOROOT'] = goroot;
+					}
+				}
+			}
 			if (
 				e.affectsConfiguration('go.goroot') ||
 				e.affectsConfiguration('go.alternateTools') ||
diff --git a/src/util.ts b/src/util.ts
index 00fe941..b68f378 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -509,15 +509,17 @@
 	const alternateTools: { [key: string]: string } = cfg.get('alternateTools');
 	const alternateToolPath: string = alternateTools[tool];
 
+	const gorootInSetting = resolvePath(cfg.get('goroot'));
+
 	let selectedGoPath: string | undefined;
-	if (tool === 'go') {
+	if (tool === 'go' && !gorootInSetting) {
 		selectedGoPath = getFromWorkspaceState('selectedGo')?.binpath;
 	}
 
 	return getBinPathWithPreferredGopathGorootWithExplanation(
 		tool,
 		tool === 'go' ? [] : [getToolsGopath(), getCurrentGoPath()],
-		tool === 'go' && cfg.get('goroot') ? resolvePath(cfg.get('goroot')) : undefined,
+		tool === 'go' ? gorootInSetting : undefined,
 		selectedGoPath ?? resolvePath(alternateToolPath),
 		useCache
 	);
diff --git a/src/utils/pathUtils.ts b/src/utils/pathUtils.ts
index bbe35d9..7f68ce5 100644
--- a/src/utils/pathUtils.ts
+++ b/src/utils/pathUtils.ts
@@ -163,7 +163,7 @@
 	}
 }
 
-export async function pathExists(p: string): Promise<boolean> {
+export async function dirExists(p: string): Promise<boolean> {
 	try {
 		const stat = promisify(fs.stat);
 		return (await stat(p)).isDirectory();