src: refactor tools env variables to handle installation and execution

I modified this change to focus only on addressing the differences between environment variables when installing tools vs. when running them. https://golang.org/cl/233325 now handles the issue reports on restart.

Change-Id: I9d03e2c8f9244bade19606e9d9c6892da9fa0c66
GitHub-Last-Rev: e6f4db38c2ae407481b8bc05f689c743a7517efb
GitHub-Pull-Request: golang/vscode-go#28
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/232863
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/src/goBuild.ts b/src/goBuild.ts
index 9b8129d..0aaee96 100644
--- a/src/goBuild.ts
+++ b/src/goBuild.ts
@@ -5,6 +5,7 @@
 
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { buildDiagnosticCollection } from './goMain';
 import { isModSupported } from './goModules';
 import { getNonVendorPackages } from './goPackages';
@@ -16,7 +17,6 @@
 	getGoConfig,
 	getModuleCache,
 	getTempFilePath,
-	getToolsEnvVars,
 	getWorkspaceFolderPath,
 	handleDiagnosticErrors,
 	ICheckResult,
@@ -100,14 +100,14 @@
 		return [];
 	}
 
-	const buildEnv = Object.assign({}, getToolsEnvVars());
+	const buildEnv = toolExecutionEnvironment();
 	const tmpPath = getTempFilePath('go-code-check');
 	const isTestFile = fileUri && fileUri.fsPath.endsWith('_test.go');
 	const buildFlags: string[] = isTestFile
 		? getTestFlags(goConfig)
 		: Array.isArray(goConfig['buildFlags'])
-		? [...goConfig['buildFlags']]
-		: [];
+			? [...goConfig['buildFlags']]
+			: [];
 	const buildArgs: string[] = isTestFile ? ['test', '-c'] : ['build'];
 
 	if (goConfig['installDependenciesWhenBuilding'] === true && !isMod) {
diff --git a/src/goDebugConfiguration.ts b/src/goDebugConfiguration.ts
index d09f2b1..a69087a 100644
--- a/src/goDebugConfiguration.ts
+++ b/src/goDebugConfiguration.ts
@@ -7,10 +7,11 @@
 
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
 import { packagePathToGoModPathMap } from './goModules';
 import { getFromGlobalState, updateGlobalState } from './stateUtils';
-import { getBinPath, getCurrentGoPath, getGoConfig, getToolsEnvVars } from './util';
+import { getBinPath, getCurrentGoPath, getGoConfig } from './util';
 
 export class GoDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
 	public provideDebugConfigurations(
@@ -61,7 +62,7 @@
 		}
 
 		const goConfig = getGoConfig(folder && folder.uri);
-		const goToolsEnvVars = getToolsEnvVars();
+		const goToolsEnvVars = toolExecutionEnvironment();
 		Object.keys(goToolsEnvVars).forEach((key) => {
 			if (!debugConfiguration['env'].hasOwnProperty(key)) {
 				debugConfiguration['env'][key] = goToolsEnvVars[key];
diff --git a/src/goDeclaration.ts b/src/goDeclaration.ts
index 798b014..7eb03ee 100644
--- a/src/goDeclaration.ts
+++ b/src/goDeclaration.ts
@@ -8,6 +8,7 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
 import { getModFolderPath, promptToUpdateToolForModules } from './goModules';
 import {
@@ -16,7 +17,6 @@
 	getFileArchive,
 	getGoConfig,
 	getModuleCache,
-	getToolsEnvVars,
 	getWorkspaceFolderPath,
 	goKeywords,
 	isPositionInString,
@@ -132,7 +132,7 @@
 		return Promise.reject(missingToolMsg + godefTool);
 	}
 	const offset = byteOffsetAt(input.document, input.position);
-	const env = getToolsEnvVars();
+	const env = toolExecutionEnvironment();
 	let p: cp.ChildProcess;
 	if (token) {
 		token.onCancellationRequested(() => killTree(p.pid));
@@ -223,7 +223,7 @@
 		return Promise.reject(missingToolMsg + 'gogetdoc');
 	}
 	const offset = byteOffsetAt(input.document, input.position);
-	const env = getToolsEnvVars();
+	const env = toolExecutionEnvironment();
 	let p: cp.ChildProcess;
 	if (token) {
 		token.onCancellationRequested(() => killTree(p.pid));
@@ -297,7 +297,7 @@
 		return Promise.reject(missingToolMsg + 'guru');
 	}
 	const offset = byteOffsetAt(input.document, input.position);
-	const env = getToolsEnvVars();
+	const env = toolExecutionEnvironment();
 	let p: cp.ChildProcess;
 	if (token) {
 		token.onCancellationRequested(() => killTree(p.pid));
diff --git a/src/goDoctor.ts b/src/goDoctor.ts
index 9833596..b8a396b 100644
--- a/src/goDoctor.ts
+++ b/src/goDoctor.ts
@@ -8,8 +8,9 @@
 import cp = require('child_process');
 import { dirname, isAbsolute } from 'path';
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
-import { getBinPath, getToolsEnvVars } from './util';
+import { getBinPath } from './util';
 
 /**
  * Extracts function out of current selection and replaces the current selection with a call to the extracted function.
@@ -78,7 +79,7 @@
 				'-w',
 				'-pos',
 				`${selection.start.line + 1},${selection.start.character + 1}:${selection.end.line + 1},${
-					selection.end.character
+				selection.end.character
 				}`,
 				'-file',
 				fileName,
@@ -86,7 +87,7 @@
 				newName
 			],
 			{
-				env: getToolsEnvVars(),
+				env: toolExecutionEnvironment(),
 				cwd: dirname(fileName)
 			},
 			(err, stdout, stderr) => {
diff --git a/src/goEnv.ts b/src/goEnv.ts
new file mode 100644
index 0000000..aa02546
--- /dev/null
+++ b/src/goEnv.ts
@@ -0,0 +1,69 @@
+/*---------------------------------------------------------
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+'use strict';
+
+import path = require('path');
+import vscode = require('vscode');
+import { getCurrentGoPath, getGoConfig, getToolsGopath, resolvePath } from './util';
+
+// toolInstallationEnvironment returns the environment in which tools should
+// be installed. It always returns a new object.
+export function toolInstallationEnvironment(): NodeJS.Dict<string> {
+	const env = newEnvironment();
+
+	// If the go.toolsGopath is set, use its value as the GOPATH for `go` processes.
+	// Else use the Current Gopath
+	let toolsGopath = getToolsGopath();
+	if (toolsGopath) {
+		// User has explicitly chosen to use toolsGopath, so ignore GOBIN.
+		env['GOBIN'] = '';
+	} else {
+		toolsGopath = getCurrentGoPath();
+	}
+	if (!toolsGopath) {
+		const msg = 'Cannot install Go tools. Set either go.gopath or go.toolsGopath in settings.';
+		vscode.window.showInformationMessage(msg, 'Open User Settings', 'Open Workspace Settings').then((selected) => {
+			switch (selected) {
+				case 'Open User Settings':
+					vscode.commands.executeCommand('workbench.action.openGlobalSettings');
+					break;
+				case 'Open Workspace Settings':
+					vscode.commands.executeCommand('workbench.action.openWorkspaceSettings');
+					break;
+			}
+		});
+		return;
+	}
+	env['GOPATH'] = toolsGopath;
+
+	return env;
+}
+
+// toolExecutionEnvironment returns the environment in which tools should
+// be executed. It always returns a new object.
+export function toolExecutionEnvironment(): NodeJS.Dict<string> {
+	const env = newEnvironment();
+	const gopath = getCurrentGoPath();
+	if (gopath) {
+		env['GOPATH'] = gopath;
+	}
+	return env;
+}
+
+function newEnvironment(): NodeJS.Dict<string> {
+	const toolsEnvVars = getGoConfig()['toolsEnvVars'];
+	const env = Object.assign({}, process.env, toolsEnvVars);
+
+	// The http.proxy setting takes precedence over environment variables.
+	const httpProxy = vscode.workspace.getConfiguration('http', null).get('proxy');
+	if (httpProxy && typeof httpProxy === 'string') {
+		env['http_proxy'] = httpProxy;
+		env['HTTP_PROXY'] = httpProxy;
+		env['https_proxy'] = httpProxy;
+		env['HTTPS_PROXY'] = httpProxy;
+	}
+	return env;
+}
diff --git a/src/goFillStruct.ts b/src/goFillStruct.ts
index beb12aa..4e9fba6 100644
--- a/src/goFillStruct.ts
+++ b/src/goFillStruct.ts
@@ -7,8 +7,9 @@
 
 import cp = require('child_process');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
-import { byteOffsetAt, getBinPath, getFileArchive, getToolsEnvVars, makeMemoizedByteOffsetConverter } from './util';
+import { byteOffsetAt, getBinPath, getFileArchive, makeMemoizedByteOffsetConverter } from './util';
 
 // Interface for the output from fillstruct
 interface GoFillStructOutput {
@@ -59,7 +60,7 @@
 	const tabsCount = getTabsCount(editor);
 
 	return new Promise<void>((resolve, reject) => {
-		const p = cp.execFile(fillstruct, args, { env: getToolsEnvVars() }, (err, stdout, stderr) => {
+		const p = cp.execFile(fillstruct, args, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => {
 			try {
 				if (err && (<any>err).code === 'ENOENT') {
 					promptForMissingTool('fillstruct');
diff --git a/src/goFormat.ts b/src/goFormat.ts
index 78a52a3..6321e66 100644
--- a/src/goFormat.ts
+++ b/src/goFormat.ts
@@ -8,8 +8,9 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
-import { getBinPath, getGoConfig, getToolsEnvVars, killTree } from './util';
+import { getBinPath, getGoConfig, killTree } from './util';
 
 export class GoDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider {
 	public provideDocumentFormattingEdits(
@@ -70,7 +71,7 @@
 				return reject();
 			}
 
-			const env = getToolsEnvVars();
+			const env = toolExecutionEnvironment();
 			const cwd = path.dirname(document.fileName);
 			let stdout = '';
 			let stderr = '';
diff --git a/src/goGenerateTests.ts b/src/goGenerateTests.ts
index 9521e7e..a9e71c9 100644
--- a/src/goGenerateTests.ts
+++ b/src/goGenerateTests.ts
@@ -9,10 +9,11 @@
 import path = require('path');
 import vscode = require('vscode');
 
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
 import { GoDocumentSymbolProvider } from './goOutline';
 import { outputChannel } from './goStatus';
-import { getBinPath, getGoConfig, getToolsEnvVars } from './util';
+import { getBinPath, getGoConfig } from './util';
 
 const generatedWord = 'Generated ';
 
@@ -172,7 +173,7 @@
 			args = args.concat(['-all', conf.dir]);
 		}
 
-		cp.execFile(cmd, args, { env: getToolsEnvVars() }, (err, stdout, stderr) => {
+		cp.execFile(cmd, args, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => {
 			outputChannel.appendLine('Generating Tests: ' + cmd + ' ' + args.join(' '));
 
 			try {
diff --git a/src/goImpl.ts b/src/goImpl.ts
index dcaf4f0..3672315 100644
--- a/src/goImpl.ts
+++ b/src/goImpl.ts
@@ -8,8 +8,9 @@
 import cp = require('child_process');
 import { dirname } from 'path';
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
-import { getBinPath, getToolsEnvVars } from './util';
+import { getBinPath } from './util';
 
 // Supports only passing interface, see TODO in implCursor to finish
 const inputRegex = /^(\w+\ \*?\w+\ )?([\w./]+)$/;
@@ -49,7 +50,7 @@
 	const p = cp.execFile(
 		goimpl,
 		args,
-		{ env: getToolsEnvVars(), cwd: dirname(editor.document.fileName) },
+		{ env: toolExecutionEnvironment(), cwd: dirname(editor.document.fileName) },
 		(err, stdout, stderr) => {
 			if (err && (<any>err).code === 'ENOENT') {
 				promptForMissingTool('impl');
diff --git a/src/goImplementations.ts b/src/goImplementations.ts
index 2bc45d5..4ee9e29 100644
--- a/src/goImplementations.ts
+++ b/src/goImplementations.ts
@@ -8,6 +8,7 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
 import { envPath } from './goPath';
 import {
@@ -15,7 +16,6 @@
 	canonicalizeGOPATHPrefix,
 	getBinPath,
 	getGoConfig,
-	getToolsEnvVars,
 	getWorkspaceFolderPath,
 	killTree
 } from './util';
@@ -66,7 +66,7 @@
 			if (token.isCancellationRequested) {
 				return resolve(null);
 			}
-			const env = getToolsEnvVars();
+			const env = toolExecutionEnvironment();
 			const listProcess = cp.execFile(
 				goRuntimePath,
 				['list', '-e', '-json'],
diff --git a/src/goImport.ts b/src/goImport.ts
index c8445c7..fade834 100644
--- a/src/goImport.ts
+++ b/src/goImport.ts
@@ -7,11 +7,12 @@
 
 import cp = require('child_process');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
 import { documentSymbols, GoOutlineImportsOptions } from './goOutline';
 import { getImportablePackages } from './goPackages';
 import { envPath } from './goPath';
-import { getBinPath, getImportPath, getToolsEnvVars, parseFilePrelude } from './util';
+import { getBinPath, getImportPath, parseFilePrelude } from './util';
 
 const missingToolMsg = 'Missing tool: ';
 
@@ -183,7 +184,7 @@
 		);
 		return;
 	}
-	const env = getToolsEnvVars();
+	const env = toolExecutionEnvironment();
 
 	cp.execFile(goRuntimePath, ['list', '-f', '{{.Dir}}', importPath], { env }, (err, stdout, stderr) => {
 		const dirs = (stdout || '').split('\n');
diff --git a/src/goInstall.ts b/src/goInstall.ts
index 32aaa9c..4f351aa 100644
--- a/src/goInstall.ts
+++ b/src/goInstall.ts
@@ -6,10 +6,11 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { isModSupported } from './goModules';
 import { envPath, getCurrentGoWorkspaceFromGOPATH } from './goPath';
 import { outputChannel } from './goStatus';
-import { getBinPath, getCurrentGoPath, getGoConfig, getModuleCache, getToolsEnvVars } from './util';
+import { getBinPath, getCurrentGoPath, getGoConfig, getModuleCache } from './util';
 
 export async function installCurrentPackage(): Promise<void> {
 	const editor = vscode.window.activeTextEditor;
@@ -32,7 +33,7 @@
 		return;
 	}
 
-	const env = Object.assign({}, getToolsEnvVars());
+	const env = toolExecutionEnvironment();
 	const cwd = path.dirname(editor.document.uri.fsPath);
 	const isMod = await isModSupported(editor.document.uri);
 
diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts
index 121d056..1d2bc4e 100644
--- a/src/goInstallTools.ts
+++ b/src/goInstallTools.ts
@@ -11,6 +11,7 @@
 import { SemVer } from 'semver';
 import util = require('util');
 import vscode = require('vscode');
+import { toolInstallationEnvironment } from './goEnv';
 import { getLanguageServerToolPath } from './goLanguageServer';
 import { restartLanguageServer } from './goMain';
 import { envPath, getToolFromToolPath } from './goPath';
@@ -28,12 +29,9 @@
 } from './goTools';
 import {
 	getBinPath,
-	getCurrentGoPath,
 	getGoConfig,
 	getGoVersion,
 	getTempFilePath,
-	getToolsEnvVars,
-	getToolsGopath,
 	GoVersion,
 	resolvePath,
 	rmdirRecursive
@@ -106,54 +104,22 @@
 		return;
 	}
 
-	// http.proxy setting takes precedence over environment variables
-	const httpProxy = vscode.workspace.getConfiguration('http', null).get('proxy');
-	const envForTools = Object.assign({}, process.env, getToolsEnvVars());
-	if (httpProxy) {
-		envForTools['http_proxy'] = httpProxy;
-		envForTools['HTTP_PROXY'] = httpProxy;
-		envForTools['https_proxy'] = httpProxy;
-		envForTools['HTTPS_PROXY'] = httpProxy;
-	}
-
 	outputChannel.show();
 	outputChannel.clear();
 
-	// If the go.toolsGopath is set, use its value as the GOPATH for the "go get" child process.
-	// Else use the Current Gopath
-	let toolsGopath = getToolsGopath();
-	if (toolsGopath) {
-		// User has explicitly chosen to use toolsGopath, so ignore GOBIN
-		envForTools['GOBIN'] = '';
-		outputChannel.appendLine(`Using the value ${toolsGopath} from the go.toolsGopath setting.`);
-	} else {
-		toolsGopath = getCurrentGoPath();
-		outputChannel.appendLine(`go.toolsGopath setting is not set. Using GOPATH ${toolsGopath}`);
+	const envForTools = toolInstallationEnvironment();
+	const toolsGopath = envForTools['GOPATH'];
+	let envMsg = `Tools environment: GOPATH=${toolsGopath}`;
+	if (envForTools['GOBIN']) {
+		envMsg += `, GOBIN=${envForTools['GOBIN']}`;
 	}
-	if (toolsGopath) {
-		const paths = toolsGopath.split(path.delimiter);
-		toolsGopath = paths[0];
-		envForTools['GOPATH'] = toolsGopath;
-	} else {
-		const msg = 'Cannot install Go tools. Set either go.gopath or go.toolsGopath in settings.';
-		vscode.window.showInformationMessage(msg, 'Open User Settings', 'Open Workspace Settings').then((selected) => {
-			switch (selected) {
-				case 'Open User Settings':
-					vscode.commands.executeCommand('workbench.action.openGlobalSettings');
-					break;
-				case 'Open Workspace Settings':
-					vscode.commands.executeCommand('workbench.action.openWorkspaceSettings');
-					break;
-			}
-		});
-		return;
-	}
+	outputChannel.appendLine(envMsg);
 
 	let installingMsg = `Installing ${missing.length} ${missing.length > 1 ? 'tools' : 'tool'} at `;
 	if (envForTools['GOBIN']) {
 		installingMsg += `the configured GOBIN: ${envForTools['GOBIN']}`;
 	} else {
-		installingMsg += toolsGopath + path.sep + 'bin';
+		installingMsg += `${toolsGopath}${path.sep}bin`;
 	}
 
 	// If the user is on Go >= 1.11, tools should be installed with modules enabled.
@@ -258,6 +224,7 @@
 	args.push(importPath);
 
 	let output: string;
+	let result: string = '';
 	try {
 		const opts = {
 			env,
@@ -281,13 +248,13 @@
 		outputChannel.appendLine(`Installing ${importPath} SUCCEEDED`);
 	} catch (e) {
 		outputChannel.appendLine(`Installing ${importPath} FAILED`);
-		return `failed to install ${tool}: ${e} ${output} `;
+		result = `failed to install ${tool}: ${e} ${output} `;
 	}
 
 	// Delete the temporary installation directory.
 	rmdirRecursive(toolsTmpDir);
 
-	return '';
+	return result;
 }
 
 export async function promptForMissingTool(toolName: string) {
diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts
index 257ffdb..9de10a2 100644
--- a/src/goLanguageServer.ts
+++ b/src/goLanguageServer.ts
@@ -20,6 +20,7 @@
 } from 'vscode-languageclient';
 import WebRequest = require('web-request');
 import { GoDefinitionProvider } from './goDeclaration';
+import { toolExecutionEnvironment } from './goEnv';
 import { GoHoverProvider } from './goExtraInfo';
 import { GoDocumentFormattingEditProvider } from './goFormat';
 import { GoImplementationProvider } from './goImplementations';
@@ -37,7 +38,7 @@
 import { getTool, Tool } from './goTools';
 import { GoTypeDefinitionProvider } from './goTypeDefinition';
 import { getFromGlobalState, updateGlobalState } from './stateUtils';
-import { getBinPath, getCurrentGoPath, getGoConfig, getToolsEnvVars } from './util';
+import { getBinPath, getCurrentGoPath, getGoConfig } from './util';
 
 interface LanguageServerConfig {
 	serverName: string;
@@ -346,7 +347,6 @@
 
 export function buildLanguageServerConfig(): LanguageServerConfig {
 	const goConfig = getGoConfig();
-	const toolsEnv = getToolsEnvVars();
 	const cfg: LanguageServerConfig = {
 		serverName: '',
 		path: '',
@@ -359,8 +359,8 @@
 			diagnostics: goConfig['languageServerExperimentalFeatures']['diagnostics'],
 			documentLink: goConfig['languageServerExperimentalFeatures']['documentLink']
 		},
-		env: toolsEnv,
-		checkForUpdates: goConfig['useGoProxyToCheckForToolUpdates'],
+		env: toolExecutionEnvironment(),
+		checkForUpdates: goConfig['useGoProxyToCheckForToolUpdates']
 	};
 	// Don't look for the path if the server is not enabled.
 	if (!cfg.enabled) {
@@ -582,11 +582,10 @@
 // installed on the user's machine. This is determined by running the
 // `gopls version` command.
 export async function getLocalGoplsVersion(goplsPath: string): Promise<string> {
-	const env = getToolsEnvVars();
 	const execFile = util.promisify(cp.execFile);
 	let output: any;
 	try {
-		const { stdout } = await execFile(goplsPath, ['version'], { env });
+		const { stdout } = await execFile(goplsPath, ['version'], { env: toolExecutionEnvironment() });
 		output = stdout;
 	} catch (e) {
 		// The "gopls version" command is not supported, or something else went wrong.
@@ -827,7 +826,7 @@
 			// Wait for the command to finish before restarting the
 			// server, but don't bother handling errors.
 			const execFile = util.promisify(cp.execFile);
-			await execFile(latestConfig.path, ['bug'], { env: getToolsEnvVars() });
+			await execFile(latestConfig.path, ['bug'], { env: toolExecutionEnvironment() });
 			break;
 		case 'Next time':
 			break;
diff --git a/src/goLint.ts b/src/goLint.ts
index eca0101..2eb80ea 100644
--- a/src/goLint.ts
+++ b/src/goLint.ts
@@ -5,11 +5,11 @@
 
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { lintDiagnosticCollection } from './goMain';
 import { diagnosticsStatusBarItem, outputChannel } from './goStatus';
 import {
 	getGoConfig,
-	getToolsEnvVars,
 	getToolsGopath,
 	getWorkspaceFolderPath,
 	handleDiagnosticErrors,
@@ -83,7 +83,7 @@
 
 	const lintTool = goConfig['lintTool'] || 'golint';
 	const lintFlags: string[] = goConfig['lintFlags'] || [];
-	const lintEnv = Object.assign({}, getToolsEnvVars());
+	const lintEnv = toolExecutionEnvironment();
 	const args: string[] = [];
 
 	lintFlags.forEach((flag) => {
diff --git a/src/goLiveErrors.ts b/src/goLiveErrors.ts
index 8c18dd4..e6b6cb8 100644
--- a/src/goLiveErrors.ts
+++ b/src/goLiveErrors.ts
@@ -8,10 +8,11 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
 import { buildDiagnosticCollection } from './goMain';
 import { isModSupported } from './goModules';
-import { getBinPath, getGoConfig, getToolsEnvVars } from './util';
+import { getBinPath, getGoConfig } from './util';
 
 // Interface for settings configuration for adding and removing tags
 interface GoLiveErrorsConfig {
@@ -77,7 +78,7 @@
 	const fileContents = e.document.getText();
 	const fileName = e.document.fileName;
 	const args = ['-e', '-a', '-lf=' + fileName, path.dirname(fileName)];
-	const env = getToolsEnvVars();
+	const env = toolExecutionEnvironment();
 	const p = cp.execFile(gotypeLive, args, { env }, (err, stdout, stderr) => {
 		if (err && (<any>err).code === 'ENOENT') {
 			promptForMissingTool('gotype-live');
diff --git a/src/goMain.ts b/src/goMain.ts
index dc16200..3451bd5 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -17,6 +17,7 @@
 } from './goCover';
 import { GoDebugConfigurationProvider } from './goDebugConfiguration';
 import { extractFunction, extractVariable } from './goDoctor';
+import { toolExecutionEnvironment } from './goEnv';
 import { runFillStruct } from './goFillStruct';
 import * as goGenerateTests from './goGenerateTests';
 import { goGetPackage } from './goGetPackage';
@@ -47,7 +48,7 @@
 import { cancelRunningTests, showTestOutput } from './testUtils';
 import {
 	cleanupTempDir, getBinPath, getCurrentGoPath, getExtensionCommands, getGoConfig,
-	getGoVersion, getToolsEnvVars, getToolsGopath, getWorkspaceFolderPath, handleDiagnosticErrors, isGoPathSet
+	getGoVersion, getToolsGopath, getWorkspaceFolderPath, handleDiagnosticErrors, isGoPathSet
 } from './util';
 
 export let buildDiagnosticCollection: vscode.DiagnosticCollection;
@@ -409,7 +410,7 @@
 				updateCodeCoverageDecorators(updatedGoConfig['coverageDecorator']);
 			}
 			if (e.affectsConfiguration('go.toolsEnvVars')) {
-				const env = getToolsEnvVars();
+				const env = toolExecutionEnvironment();
 				if (GO111MODULE !== env['GO111MODULE']) {
 					const reloadMsg =
 						'Reload VS Code window so that the Go tools can respect the change to GO111MODULE';
diff --git a/src/goModifytags.ts b/src/goModifytags.ts
index ac25e88..bfaa5b2 100644
--- a/src/goModifytags.ts
+++ b/src/goModifytags.ts
@@ -7,8 +7,9 @@
 
 import cp = require('child_process');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
-import { byteOffsetAt, getBinPath, getFileArchive, getGoConfig, getToolsEnvVars } from './util';
+import { byteOffsetAt, getBinPath, getFileArchive, getGoConfig } from './util';
 
 // Interface for the output from gomodifytags
 interface GomodifytagsOutput {
@@ -144,7 +145,7 @@
 	const gomodifytags = getBinPath('gomodifytags');
 	const editor = vscode.window.activeTextEditor;
 	const input = getFileArchive(editor.document);
-	const p = cp.execFile(gomodifytags, args, { env: getToolsEnvVars() }, (err, stdout, stderr) => {
+	const p = cp.execFile(gomodifytags, args, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => {
 		if (err && (<any>err).code === 'ENOENT') {
 			promptForMissingTool('gomodifytags');
 			return;
diff --git a/src/goModules.ts b/src/goModules.ts
index 65d8b78..a9b644e 100644
--- a/src/goModules.ts
+++ b/src/goModules.ts
@@ -6,12 +6,12 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { installTools } from './goInstallTools';
-import { restartLanguageServer } from './goMain';
 import { envPath, fixDriveCasingInWindows } from './goPath';
 import { getTool } from './goTools';
 import { getFromGlobalState, updateGlobalState } from './stateUtils';
-import { getBinPath, getGoConfig, getGoVersion, getModuleCache, getToolsEnvVars } from './util';
+import { getBinPath, getGoConfig, getGoVersion, getModuleCache } from './util';
 
 export let GO111MODULE: string;
 
@@ -23,10 +23,10 @@
 		);
 		return;
 	}
-	const env = getToolsEnvVars();
+	const env = toolExecutionEnvironment();
 	GO111MODULE = env['GO111MODULE'];
 	return new Promise((resolve) => {
-		cp.execFile(goExecutable, ['env', 'GOMOD'], { cwd: folderPath, env: getToolsEnvVars() }, (err, stdout) => {
+		cp.execFile(goExecutable, ['env', 'GOMOD'], { cwd: folderPath, env }, (err, stdout) => {
 			if (err) {
 				console.warn(`Error when running go env GOMOD: ${err}`);
 				return resolve();
@@ -169,7 +169,7 @@
 		return;
 	}
 	return new Promise<string>((resolve) => {
-		const childProcess = cp.spawn(goRuntimePath, ['list'], { cwd, env: getToolsEnvVars() });
+		const childProcess = cp.spawn(goRuntimePath, ['list'], { cwd, env: toolExecutionEnvironment() });
 		const chunks: any[] = [];
 		childProcess.stdout.on('data', (stdout) => {
 			chunks.push(stdout);
diff --git a/src/goOutline.ts b/src/goOutline.ts
index 15ad345..97af953 100644
--- a/src/goOutline.ts
+++ b/src/goOutline.ts
@@ -7,12 +7,12 @@
 
 import cp = require('child_process');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
 import {
 	getBinPath,
 	getFileArchive,
 	getGoConfig,
-	getToolsEnvVars,
 	killProcess,
 	makeMemoizedByteOffsetConverter
 } from './util';
@@ -92,7 +92,7 @@
 		}
 
 		// Spawn `go-outline` process
-		p = cp.execFile(gooutline, gooutlineFlags, { env: getToolsEnvVars() }, (err, stdout, stderr) => {
+		p = cp.execFile(gooutline, gooutlineFlags, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => {
 			try {
 				if (err && (<any>err).code === 'ENOENT') {
 					promptForMissingTool('go-outline');
@@ -193,7 +193,7 @@
 }
 
 export class GoDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
-	constructor(private includeImports?: boolean) {}
+	constructor(private includeImports?: boolean) { }
 
 	public provideDocumentSymbols(
 		document: vscode.TextDocument,
diff --git a/src/goPackages.ts b/src/goPackages.ts
index 208d2d2..8965435 100644
--- a/src/goPackages.ts
+++ b/src/goPackages.ts
@@ -6,9 +6,10 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
 import { envPath, fixDriveCasingInWindows, getCurrentGoWorkspaceFromGOPATH } from './goPath';
-import { getBinPath, getCurrentGoPath, getGoVersion, getToolsEnvVars, isVendorSupported } from './util';
+import { getBinPath, getCurrentGoPath, getGoVersion, isVendorSupported } from './util';
 
 type GopkgsDone = (res: Map<string, PackageInfo>) => void;
 interface Cache {
@@ -45,7 +46,7 @@
 			args.push('-workDir', workDir);
 		}
 
-		const cmd = cp.spawn(gopkgsBinPath, args, { env: getToolsEnvVars() });
+		const cmd = cp.spawn(gopkgsBinPath, args, { env: toolExecutionEnvironment() });
 		const chunks: any[] = [];
 		const errchunks: any[] = [];
 		let err: any;
@@ -269,7 +270,7 @@
 		const childProcess = cp.spawn(
 			goRuntimePath,
 			['list', '-f', 'ImportPath: {{.ImportPath}} FolderPath: {{.Dir}}', './...'],
-			{ cwd: currentFolderPath, env: getToolsEnvVars() }
+			{ cwd: currentFolderPath, env: toolExecutionEnvironment() }
 		);
 		const chunks: any[] = [];
 		childProcess.stdout.on('data', (stdout) => {
diff --git a/src/goReferences.ts b/src/goReferences.ts
index bb7acfb..44a86fc 100644
--- a/src/goReferences.ts
+++ b/src/goReferences.ts
@@ -8,6 +8,7 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
 import {
 	byteOffsetAt,
@@ -15,7 +16,6 @@
 	getBinPath,
 	getFileArchive,
 	getGoConfig,
-	getToolsEnvVars,
 	killTree
 } from './util';
 
@@ -51,7 +51,7 @@
 			const filename = canonicalizeGOPATHPrefix(document.fileName);
 			const cwd = path.dirname(filename);
 			const offset = byteOffsetAt(document, wordRange.start);
-			const env = getToolsEnvVars();
+			const env = toolExecutionEnvironment();
 			const buildTags = getGoConfig(document.uri)['buildTags'];
 			const args = buildTags ? ['-tags', buildTags] : [];
 			args.push('-modified', 'referrers', `${filename}:#${offset.toString()}`);
diff --git a/src/goRename.ts b/src/goRename.ts
index bab3e25..55a97f7 100644
--- a/src/goRename.ts
+++ b/src/goRename.ts
@@ -8,9 +8,10 @@
 import cp = require('child_process');
 import vscode = require('vscode');
 import { Edit, FilePatch, getEditsFromUnifiedDiffStr, isDiffToolAvailable } from './diffUtils';
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
 import { outputChannel } from './goStatus';
-import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getGoConfig, getToolsEnvVars, killTree } from './util';
+import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getGoConfig, killTree } from './util';
 
 export class GoRenameProvider implements vscode.RenameProvider {
 	public provideRenameEdits(
@@ -35,7 +36,7 @@
 			const range = document.getWordRangeAtPosition(position);
 			const pos = range ? range.start : position;
 			const offset = byteOffsetAt(document, pos);
-			const env = getToolsEnvVars();
+			const env = toolExecutionEnvironment();
 			const gorename = getBinPath('gorename');
 			const buildTags = getGoConfig(document.uri)['buildTags'];
 			const gorenameArgs = ['-offset', filename + ':#' + offset, '-to', newName];
diff --git a/src/goSuggest.ts b/src/goSuggest.ts
index 139fbc1..73c1263 100644
--- a/src/goSuggest.ts
+++ b/src/goSuggest.ts
@@ -8,6 +8,7 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { getTextEditForAddImport } from './goImport';
 import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
 import { isModSupported } from './goModules';
@@ -19,7 +20,6 @@
 	getCurrentGoPath,
 	getGoConfig,
 	getParametersAndReturnType,
-	getToolsEnvVars,
 	goBuiltinTypes,
 	goKeywords,
 	guessPackageNameFromFile,
@@ -269,7 +269,7 @@
 		const gocodeName = this.isGoMod ? 'gocode-gomod' : 'gocode';
 		const gocode = getBinPath(gocodeName);
 		if (path.isAbsolute(gocode)) {
-			cp.spawn(gocode, ['close'], { env: getToolsEnvVars() });
+			cp.spawn(gocode, ['close'], { env: toolExecutionEnvironment() });
 		}
 	}
 
@@ -293,7 +293,7 @@
 				return reject();
 			}
 
-			const env = getToolsEnvVars();
+			const env = toolExecutionEnvironment();
 			let stdout = '';
 			let stderr = '';
 
@@ -380,8 +380,8 @@
 									config['useCodeSnippetsOnFunctionSuggestWithoutType']) &&
 								((suggest.class === 'func' && lineText.substr(position.character, 2) !== '()') || // Avoids met() -> method()()
 									(suggest.class === 'var' &&
-									suggest.type.startsWith('func(') &&
-									lineText.substr(position.character, 1) !== ')' && // Avoids snippets when typing params in a func call
+										suggest.type.startsWith('func(') &&
+										lineText.substr(position.character, 1) !== ')' && // Avoids snippets when typing params in a func call
 										lineText.substr(position.character, 1) !== ',')) // Avoids snippets when typing params in a func call
 							) {
 								const { params, returnType } = getParametersAndReturnType(suggest.type.substring(4));
@@ -421,22 +421,22 @@
 										const arg = param.substr(0, param.indexOf(' '));
 										paramSnippets.push(
 											'${' +
-												(i + 1) +
-												':' +
-												arg +
-												'}' +
-												param.substr(param.indexOf(' '), param.length)
+											(i + 1) +
+											':' +
+											arg +
+											'}' +
+											param.substr(param.indexOf(' '), param.length)
 										);
 									}
 								}
 								item.insertText = new vscode.SnippetString(
 									suggest.name +
-										'(func(' +
-										paramSnippets.join(', ') +
-										') {\n	$' +
-										(params.length + 1) +
-										'\n})' +
-										returnType
+									'(func(' +
+									paramSnippets.join(', ') +
+									') {\n	$' +
+									(params.length + 1) +
+									'\n})' +
+									returnType
 								);
 							}
 
@@ -504,7 +504,7 @@
 
 		const setGocodeProps = new Promise<void>((resolve, reject) => {
 			const gocode = getBinPath('gocode');
-			const env = getToolsEnvVars();
+			const env = toolExecutionEnvironment();
 
 			cp.execFile(gocode, ['set'], { env }, (err, stdout, stderr) => {
 				if (err && stdout.startsWith('gocode: unknown subcommand:')) {
diff --git a/src/goSymbol.ts b/src/goSymbol.ts
index 2c03f6d..ae8cf8c 100644
--- a/src/goSymbol.ts
+++ b/src/goSymbol.ts
@@ -7,8 +7,9 @@
 
 import cp = require('child_process');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
-import { getBinPath, getGoConfig, getToolsEnvVars, getWorkspaceFolderPath, killTree } from './util';
+import { getBinPath, getGoConfig, getWorkspaceFolderPath, killTree } from './util';
 
 // Keep in sync with github.com/acroca/go-symbols'
 interface GoSymbolDeclaration {
@@ -112,7 +113,7 @@
 
 function callGoSymbols(args: string[], token: vscode.CancellationToken): Promise<GoSymbolDeclaration[]> {
 	const gosyms = getBinPath('go-symbols');
-	const env = getToolsEnvVars();
+	const env = toolExecutionEnvironment();
 	let p: cp.ChildProcess;
 
 	if (token) {
diff --git a/src/goTypeDefinition.ts b/src/goTypeDefinition.ts
index d414559..100deb1 100644
--- a/src/goTypeDefinition.ts
+++ b/src/goTypeDefinition.ts
@@ -9,6 +9,7 @@
 import path = require('path');
 import vscode = require('vscode');
 import { adjustWordPosition, definitionLocation, parseMissingError } from './goDeclaration';
+import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
 import {
 	byteOffsetAt,
@@ -16,7 +17,6 @@
 	getBinPath,
 	getFileArchive,
 	getGoConfig,
-	getToolsEnvVars,
 	goBuiltinTypes,
 	killTree
 } from './util';
@@ -61,7 +61,7 @@
 
 			const filename = canonicalizeGOPATHPrefix(document.fileName);
 			const offset = byteOffsetAt(document, position);
-			const env = getToolsEnvVars();
+			const env = toolExecutionEnvironment();
 			const buildTags = getGoConfig(document.uri)['buildTags'];
 			const args = buildTags ? ['-tags', buildTags] : [];
 			args.push('-json', '-modified', 'describe', `${filename}:#${offset.toString()}`);
diff --git a/src/goVet.ts b/src/goVet.ts
index 1de766b..210213a 100644
--- a/src/goVet.ts
+++ b/src/goVet.ts
@@ -5,12 +5,12 @@
 
 import path = require('path');
 import vscode = require('vscode');
+import { toolExecutionEnvironment } from './goEnv';
 import { vetDiagnosticCollection } from './goMain';
 import { diagnosticsStatusBarItem, outputChannel } from './goStatus';
 import {
 	getGoConfig,
 	getGoVersion,
-	getToolsEnvVars,
 	getWorkspaceFolderPath,
 	handleDiagnosticErrors,
 	ICheckResult,
@@ -81,7 +81,7 @@
 	}
 
 	const vetFlags: string[] = goConfig['vetFlags'] || [];
-	const vetEnv = Object.assign({}, getToolsEnvVars());
+	const vetEnv = toolExecutionEnvironment();
 	const args: string[] = [];
 
 	vetFlags.forEach((flag) => {
diff --git a/src/testUtils.ts b/src/testUtils.ts
index 4bba6f2..c318b0f 100644
--- a/src/testUtils.ts
+++ b/src/testUtils.ts
@@ -8,6 +8,7 @@
 import vscode = require('vscode');
 
 import { applyCodeCoverageToAllEditors } from './goCover';
+import { toolExecutionEnvironment } from './goEnv';
 import { getCurrentPackage } from './goModules';
 import { GoDocumentSymbolProvider } from './goOutline';
 import { getNonVendorPackages } from './goPackages';
@@ -17,7 +18,6 @@
 	getCurrentGoPath,
 	getGoVersion,
 	getTempFilePath,
-	getToolsEnvVars,
 	killTree,
 	LineBuffer,
 	resolvePath
@@ -80,7 +80,7 @@
 }
 
 export function getTestEnvVars(config: vscode.WorkspaceConfiguration): any {
-	const envVars = getToolsEnvVars();
+	const envVars = toolExecutionEnvironment();
 	const testEnvConfig = config['testEnvVars'] || {};
 
 	let fileEnv: { [key: string]: any } = {};
diff --git a/src/util.ts b/src/util.ts
index 4a1ef83..13a2f53 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -11,6 +11,7 @@
 import kill = require('tree-kill');
 import vscode = require('vscode');
 import { NearestNeighborDict, Node } from './avlTree';
+import { toolExecutionEnvironment } from './goEnv';
 import { buildDiagnosticCollection, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
 import { getCurrentPackage } from './goModules';
 import {
@@ -426,24 +427,6 @@
 	return document.fileName + '\n' + Buffer.byteLength(fileContents, 'utf8') + '\n' + fileContents;
 }
 
-export function getToolsEnvVars(): any {
-	const config = getGoConfig();
-	const toolsEnvVars = config['toolsEnvVars'];
-
-	const gopath = getCurrentGoPath();
-	const envVars = Object.assign({}, process.env, gopath ? { GOPATH: gopath } : {});
-
-	if (toolsEnvVars && typeof toolsEnvVars === 'object') {
-		Object.keys(toolsEnvVars).forEach(
-			(key) =>
-				(envVars[key] =
-					typeof toolsEnvVars[key] === 'string' ? resolvePath(toolsEnvVars[key]) : toolsEnvVars[key])
-		);
-	}
-
-	return envVars;
-}
-
 export function substituteEnv(input: string): string {
 	return input.replace(/\${env:([^}]+)}/g, (match, capture) => {
 		return process.env[capture.trim()] || '';
@@ -975,7 +958,7 @@
 				symbol = receiver + '.' + symbol;
 			}
 
-			const env = getToolsEnvVars();
+			const env = toolExecutionEnvironment();
 			const args = ['doc', '-c', '-cmd', '-u', packageImportPath, symbol];
 			const p = cp.execFile(goRuntimePath, args, { env, cwd }, (err, stdout, stderr) => {
 				if (err) {