diff --git a/Go-latest.vsix b/Go-latest.vsix index febaa09ae..a0ab755e3 100644 Binary files a/Go-latest.vsix and b/Go-latest.vsix differ diff --git a/package.json b/package.json index 2fa00a19d..6a426cc9e 100644 --- a/package.json +++ b/package.json @@ -1063,6 +1063,49 @@ "default": [], "description": "Folder names (not paths) to ignore while using Go to Symbol in Workspace feature", "scope": "resource" + }, + "go.alternateTools": { + "type": "object", + "default": {}, + "description": "Alternate tools or alternate paths for the same tools used by the Go extension. Provide either absolute path or the name of the binary in GOPATH/bin, GOROOT/bin or PATH. Useful when you want to use wrapper script for the Go tools or versioned tools from https://gopkg.in.", + "scope": "resource", + "properties": { + "go": { + "type": "string", + "default": "go", + "description": "Alternate tool to use instead of the go binary or alternate path to use for the go binary." + }, + "gometalinter": { + "type": "string", + "default": "gometalinter", + "description": "Alternate tool to use instead of the gometalinter binary or alternate path to use for the gometalinter binary." + }, + "gocode": { + "type": "string", + "default": "gocode", + "description": "Alternate tool to use instead of the gocode binary or alternate path to use for the gocode binary." + }, + "gopkgs": { + "type": "string", + "default": "gopkgs", + "description": "Alternate tool to use instead of the gopkgs binary or alternate path to use for the gopkgs binary." + }, + "godoc": { + "type": "string", + "default": "godoc", + "description": "Alternate tool to use instead of the godoc binary or alternate path to use for the godoc binary." + }, + "go-outline": { + "type": "string", + "default": "go-outline", + "description": "Alternate tool to use instead of the go-outline binary or alternate path to use for the go-outline binary." + }, + "guru": { + "type": "string", + "default": "guru", + "description": "Alternate tool to use instead of the guru binary or alternate path to use for the guru binary." + } + } } } }, diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index e67e2d56b..e252b991c 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -11,7 +11,7 @@ import { existsSync, lstatSync } from 'fs'; import { basename, dirname, extname } from 'path'; import { spawn, ChildProcess, execSync, spawnSync } from 'child_process'; import { Client, RPCConnection } from 'json-rpc2'; -import { parseEnvFile, getBinPathWithPreferredGopath, resolveHomeDir, getGoRuntimePath, getInferredGopath, getCurrentGoWorkspaceFromGOPATH, envPath, fixDriveCasingInWindows } from '../goPath'; +import { parseEnvFile, getBinPathWithPreferredGopath, resolveHomeDir, getInferredGopath, getCurrentGoWorkspaceFromGOPATH, envPath, fixDriveCasingInWindows } from '../goPath'; import * as logger from 'vscode-debug-logger'; require('console-stamp')(console); @@ -309,7 +309,7 @@ class Delve { if (!!launchArgs.noDebug) { if (mode === 'debug' && !isProgramDirectory) { this.noDebug = true; - this.debugProcess = spawn(getGoRuntimePath(), ['run', program], { env }); + this.debugProcess = spawn(getBinPathWithPreferredGopath('go', []), ['run', program], { env }); this.debugProcess.stderr.on('data', chunk => { let str = chunk.toString(); if (this.onstderr) { this.onstderr(str); } @@ -350,7 +350,7 @@ class Delve { return; } - let dlv = getBinPathWithPreferredGopath('dlv', resolveHomeDir(env['GOPATH']), process.env['GOPATH']); + let dlv = getBinPathWithPreferredGopath('dlv', [resolveHomeDir(env['GOPATH']), process.env['GOPATH']]); if (!existsSync(dlv)) { verbose(`Couldnt find dlv at ${process.env['GOPATH']}${env['GOPATH'] ? ', ' + env['GOPATH'] : ''} or ${envPath}`); diff --git a/src/goBrowsePackage.ts b/src/goBrowsePackage.ts index 9052e623b..d0c5bbe2c 100644 --- a/src/goBrowsePackage.ts +++ b/src/goBrowsePackage.ts @@ -7,10 +7,9 @@ import vscode = require('vscode'); import cp = require('child_process'); -import { getGoRuntimePath } from './goPath'; import path = require('path'); import { getAllPackages } from './goPackages'; -import { getImportPath, getCurrentGoPath } from './util'; +import { getImportPath, getCurrentGoPath, getBinPath } from './util'; export function browsePackages() { let selectedText = ''; @@ -31,7 +30,7 @@ export function browsePackages() { } function showPackageFiles(pkg: string, showAllPkgsIfPkgNotFound: boolean) { - const goRuntimePath = getGoRuntimePath(); + const goRuntimePath = getBinPath('go'); if (!goRuntimePath) { return vscode.window.showErrorMessage('Could not locate Go path. Make sure you have Go installed'); } diff --git a/src/goCheck.ts b/src/goCheck.ts index 63fc6415d..368e0f16b 100644 --- a/src/goCheck.ts +++ b/src/goCheck.ts @@ -8,11 +8,10 @@ import vscode = require('vscode'); import path = require('path'); import os = require('os'); -import { getGoRuntimePath } from './goPath'; import { getCoverage } from './goCover'; import { outputChannel, diagnosticsStatusBarItem } from './goStatus'; import { goTest } from './testUtils'; -import { ICheckResult } from './util'; +import { ICheckResult, getBinPath } from './util'; import { goLint } from './goLint'; import { goVet } from './goVet'; import { goBuild } from './goBuild'; @@ -52,7 +51,7 @@ export function check(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfigurati outputChannel.clear(); let runningToolsPromises = []; let cwd = path.dirname(fileUri.fsPath); - let goRuntimePath = getGoRuntimePath(); + let goRuntimePath = getBinPath('go'); if (!goRuntimePath) { vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately'); diff --git a/src/goGetPackage.ts b/src/goGetPackage.ts index 2becc8cf8..71fc106c2 100644 --- a/src/goGetPackage.ts +++ b/src/goGetPackage.ts @@ -2,8 +2,7 @@ import vscode = require('vscode'); import cp = require('child_process'); -import { getGoRuntimePath } from './goPath'; -import { getImportPath, getCurrentGoPath } from './util'; +import { getImportPath, getCurrentGoPath, getBinPath } from './util'; import { outputChannel } from './goStatus'; export function goGetPackage() { @@ -17,7 +16,7 @@ export function goGetPackage() { return; } - const goRuntimePath = getGoRuntimePath(); + const goRuntimePath = getBinPath('go'); if (!goRuntimePath) { return vscode.window.showErrorMessage('Could not locate Go binaries. Make sure you have Go installed'); } diff --git a/src/goImplementations.ts b/src/goImplementations.ts index 592c88fca..1a468df42 100644 --- a/src/goImplementations.ts +++ b/src/goImplementations.ts @@ -6,7 +6,6 @@ import path = require('path'); import { byteOffsetAt, getBinPath, canonicalizeGOPATHPrefix, getWorkspaceFolderPath } from './util'; import { promptForMissingTool } from './goInstallTools'; import { getToolsEnvVars } from './util'; -import { getGoRuntimePath } from './goPath'; interface GoListOutput { Dir: string; @@ -43,7 +42,7 @@ export class GoImplementationProvider implements vscode.ImplementationProvider { return resolve(null); } let env = getToolsEnvVars(); - let listProcess = cp.execFile(getGoRuntimePath(), ['list', '-e', '-json'], { cwd: root, env }, (err, stdout, stderr) => { + let listProcess = cp.execFile(getBinPath('go'), ['list', '-e', '-json'], { cwd: root, env }, (err, stdout, stderr) => { if (err) { return reject(err); } diff --git a/src/goInstall.ts b/src/goInstall.ts index 482edb7a3..20bb8f650 100644 --- a/src/goInstall.ts +++ b/src/goInstall.ts @@ -1,8 +1,8 @@ import path = require('path'); import vscode = require('vscode'); -import { getToolsEnvVars, getCurrentGoPath } from './util'; +import { getToolsEnvVars, getCurrentGoPath, getBinPath } from './util'; import { outputChannel } from './goStatus'; -import { getCurrentGoWorkspaceFromGOPATH, getGoRuntimePath } from './goPath'; +import { getCurrentGoWorkspaceFromGOPATH } from './goPath'; import cp = require('child_process'); export function installCurrentPackage() { @@ -16,7 +16,7 @@ export function installCurrentPackage() { return; } - let goRuntimePath = getGoRuntimePath(); + let goRuntimePath = getBinPath('go'); if (!goRuntimePath) { vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately'); return; diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts index 975934480..cdeb72827 100644 --- a/src/goInstallTools.ts +++ b/src/goInstallTools.ts @@ -9,9 +9,7 @@ import vscode = require('vscode'); import fs = require('fs'); import path = require('path'); import cp = require('child_process'); -import { showGoStatus, hideGoStatus } from './goStatus'; -import { getGoRuntimePath } from './goPath'; -import { outputChannel } from './goStatus'; +import { showGoStatus, hideGoStatus, outputChannel } from './goStatus'; import { getBinPath, getToolsGopath, getGoVersion, SemVersion, isVendorSupported, getCurrentGoPath, resolvePath } from './util'; import { goLiveErrorsEnabled } from './goLiveErrors'; @@ -201,7 +199,7 @@ export function promptForUpdatingTool(tool: string) { * @param string[] array of tool names to be installed */ function installTools(goVersion: SemVersion, missing?: string[]) { - let goRuntimePath = getGoRuntimePath(); + let goRuntimePath = getBinPath('go'); if (!goRuntimePath) { vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately'); return; @@ -316,7 +314,7 @@ export function updateGoPathGoRootFromConfig(): Promise { } // If GOPATH is still not set, then use the one from `go env` - let goRuntimePath = getGoRuntimePath(); + let goRuntimePath = getBinPath('go'); if (!goRuntimePath) { return Promise.reject(new Error('Cannot find "go" binary. Update PATH or GOROOT appropriately')); } diff --git a/src/goMain.ts b/src/goMain.ts index 43f5a1a47..ec972bb35 100644 --- a/src/goMain.ts +++ b/src/goMain.ts @@ -446,7 +446,8 @@ function sendTelemetryEventForConfig(goConfig: vscode.WorkspaceConfiguration) { "removeTags": { "classification": "CustomerContent", "purpose": "FeatureInsight" }, "editorContextMenuCommands": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "liveErrors": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "codeLens": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "codeLens": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "alternateTools": { "classification": "CustomerContent", "purpose": "FeatureInsight" } } */ sendTelemetryEvent('goConfig', { @@ -484,7 +485,8 @@ function sendTelemetryEventForConfig(goConfig: vscode.WorkspaceConfiguration) { removeTags: JSON.stringify(goConfig['removeTags']), editorContextMenuCommands: JSON.stringify(goConfig['editorContextMenuCommands']), liveErrors: JSON.stringify(goConfig['liveErrors']), - codeLens: JSON.stringify(goConfig['enableCodeLens']) + codeLens: JSON.stringify(goConfig['enableCodeLens']), + alternateTools: JSON.stringify(goConfig['alternateTools']) }); } diff --git a/src/goPackages.ts b/src/goPackages.ts index 1e8264ebc..a25d328e5 100644 --- a/src/goPackages.ts +++ b/src/goPackages.ts @@ -1,7 +1,7 @@ import vscode = require('vscode'); import cp = require('child_process'); import path = require('path'); -import { getGoRuntimePath, getCurrentGoWorkspaceFromGOPATH, fixDriveCasingInWindows } from './goPath'; +import { getCurrentGoWorkspaceFromGOPATH, fixDriveCasingInWindows } from './goPath'; import { isVendorSupported, getCurrentGoPath, getToolsEnvVars, getGoVersion, getBinPath, SemVersion, sendTelemetryEvent } from './util'; import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; @@ -242,7 +242,7 @@ function getRelativePackagePath(currentFileDirPath: string, currentWorkspace: st * Returns import paths for all packages under given folder (vendor will be excluded) */ export function getNonVendorPackages(folderPath: string): Promise { - let goRuntimePath = getGoRuntimePath(); + let goRuntimePath = getBinPath('go'); if (!goRuntimePath) { vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately'); diff --git a/src/goPath.ts b/src/goPath.ts index d5ddd6458..57a1f44f1 100644 --- a/src/goPath.ts +++ b/src/goPath.ts @@ -13,7 +13,7 @@ import path = require('path'); import os = require('os'); let binPathCache: { [bin: string]: string; } = {}; -let runtimePathCache: string = ''; + export const envPath = process.env['PATH'] || (process.platform === 'win32' ? process.env['Path'] : null); export function getBinPathFromEnvVar(toolName: string, envVarValue: string, appendBinToPath: boolean): string { @@ -23,7 +23,6 @@ export function getBinPathFromEnvVar(toolName: string, envVarValue: string, appe for (let i = 0; i < paths.length; i++) { let binpath = path.join(paths[i], appendBinToPath ? 'bin' : '', toolName); if (fileExists(binpath)) { - binPathCache[toolName] = binpath; return binpath; } } @@ -31,14 +30,21 @@ export function getBinPathFromEnvVar(toolName: string, envVarValue: string, appe return null; } -export function getBinPathWithPreferredGopath(binname: string, ...preferredGopaths) { - if (binPathCache[correctBinname(binname)]) return binPathCache[correctBinname(binname)]; +export function getBinPathWithPreferredGopath(toolName: string, preferredGopaths: string[], alternateTools?: { [key: string]: string; }) { + if (binPathCache[toolName]) return binPathCache[toolName]; + + if (alternateTools && alternateTools[toolName] && path.isAbsolute(alternateTools[toolName]) && fileExists(alternateTools[toolName])) { + binPathCache[toolName] = alternateTools[toolName]; + return alternateTools[toolName]; + } + const binname = (alternateTools && alternateTools[toolName] && !path.isAbsolute(alternateTools[toolName])) ? alternateTools[toolName] : toolName; for (let i = 0; i < preferredGopaths.length; i++) { if (typeof preferredGopaths[i] === 'string') { // Search in the preferred GOPATH workspace's bin folder let pathFrompreferredGoPath = getBinPathFromEnvVar(binname, preferredGopaths[i], true); if (pathFrompreferredGoPath) { + binPathCache[toolName] = pathFrompreferredGoPath; return pathFrompreferredGoPath; } } @@ -47,53 +53,36 @@ export function getBinPathWithPreferredGopath(binname: string, ...preferredGopat // Then search PATH parts let pathFromPath = getBinPathFromEnvVar(binname, envPath, false); if (pathFromPath) { + binPathCache[toolName] = pathFromPath; return pathFromPath; } // Finally check GOROOT just in case let pathFromGoRoot = getBinPathFromEnvVar(binname, process.env['GOROOT'], true); if (pathFromGoRoot) { + binPathCache[toolName] = pathFromGoRoot; return pathFromGoRoot; } + // Check default path for go + if (toolName === 'go') { + let defaultPathForGo = process.platform === 'win32' ? 'C:\\Go\\bin\\go.exe' : '/usr/local/go/bin/go'; + if (fileExists(defaultPathForGo)) { + binPathCache[toolName] = defaultPathForGo; + return defaultPathForGo; + } + return; + } + // Else return the binary name directly (this will likely always fail downstream) - return binname; + return toolName; } -function correctBinname(binname: string) { +function correctBinname(toolName: string) { if (process.platform === 'win32') - return binname + '.exe'; + return toolName + '.exe'; else - return binname; -} - -/** - * Returns Go runtime binary path. - * - * @return the path to the Go binary. - */ -export function getGoRuntimePath(): string { - if (runtimePathCache) return runtimePathCache; - let correctBinNameGo = correctBinname('go'); - if (process.env['GOROOT']) { - let runtimePathFromGoRoot = path.join(process.env['GOROOT'], 'bin', correctBinNameGo); - if (fileExists(runtimePathFromGoRoot)) { - runtimePathCache = runtimePathFromGoRoot; - return runtimePathCache; - } - } - - if (envPath) { - let pathparts = (envPath).split(path.delimiter); - runtimePathCache = pathparts.map(dir => path.join(dir, correctBinNameGo)).filter(candidate => fileExists(candidate))[0]; - } - if (!runtimePathCache) { - let defaultPathForGo = process.platform === 'win32' ? 'C:\\Go\\bin\\go.exe' : '/usr/local/go/bin/go'; - if (fileExists(defaultPathForGo)) { - runtimePathCache = defaultPathForGo; - } - } - return runtimePathCache; + return toolName; } function fileExists(filePath: string): boolean { @@ -185,7 +174,7 @@ export function getCurrentGoWorkspaceFromGOPATH(gopath: string, currentFileDirPa // both parent & child workspace in the nested workspaces pair can make it inside the above if block // Therefore, the below check will take longer (more specific to current file) of the two if (possibleCurrentWorkspace.length > currentWorkspace.length) { - currentWorkspace = currentFileDirPath.substr(0, possibleCurrentWorkspace.length); + currentWorkspace = currentFileDirPath.substr(0, possibleCurrentWorkspace.length); } } } diff --git a/src/goSymbol.ts b/src/goSymbol.ts index 8f71d59c7..aefb29e04 100644 --- a/src/goSymbol.ts +++ b/src/goSymbol.ts @@ -7,7 +7,6 @@ import vscode = require('vscode'); import cp = require('child_process'); import { getBinPath, getToolsEnvVars, killProcess } from './util'; -import { getGoRuntimePath } from './goPath'; import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; // Keep in sync with github.com/acroca/go-symbols' @@ -123,7 +122,7 @@ function callGoSymbols(args: string[], token: vscode.CancellationToken): Promise } function getGoroot(): Promise { - let goExecutable = getGoRuntimePath(); + let goExecutable = getBinPath('go'); if (!goExecutable) { return Promise.reject(new Error('Cannot find "go" binary. Update PATH or GOROOT appropriately')); } diff --git a/src/testUtils.ts b/src/testUtils.ts index b96070294..c88f72348 100644 --- a/src/testUtils.ts +++ b/src/testUtils.ts @@ -7,8 +7,8 @@ import cp = require('child_process'); import path = require('path'); import vscode = require('vscode'); import util = require('util'); -import { parseEnvFile, getGoRuntimePath, getCurrentGoWorkspaceFromGOPATH } from './goPath'; -import { getToolsEnvVars, getGoVersion, LineBuffer, SemVersion, resolvePath, getCurrentGoPath } from './util'; +import { parseEnvFile, getCurrentGoWorkspaceFromGOPATH } from './goPath'; +import { getToolsEnvVars, getGoVersion, LineBuffer, SemVersion, resolvePath, getCurrentGoPath, getBinPath } from './util'; import { GoDocumentSymbolProvider } from './goOutline'; import { getNonVendorPackages } from './goPackages'; @@ -151,7 +151,7 @@ export function goTest(testconfig: TestConfig): Thenable { } let testEnvVars = getTestEnvVars(testconfig.goConfig); - let goRuntimePath = getGoRuntimePath(); + let goRuntimePath = getBinPath('go'); if (!goRuntimePath) { vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately'); diff --git a/src/util.ts b/src/util.ts index 73e34a744..70564b6d1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -5,7 +5,7 @@ import vscode = require('vscode'); import path = require('path'); -import { getGoRuntimePath, getBinPathWithPreferredGopath, resolveHomeDir, getInferredGopath, fixDriveCasingInWindows } from './goPath'; +import { getBinPathWithPreferredGopath, resolveHomeDir, getInferredGopath, fixDriveCasingInWindows } from './goPath'; import cp = require('child_process'); import TelemetryReporter from 'vscode-extension-telemetry'; import fs = require('fs'); @@ -182,7 +182,7 @@ export function canonicalizeGOPATHPrefix(filename: string): string { * Returns null if go is being used from source/tip in which case `go version` will not return release tag like go1.6.3 */ export function getGoVersion(): Promise { - let goRuntimePath = getGoRuntimePath(); + let goRuntimePath = getBinPath('go'); if (!goRuntimePath) { vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately'); @@ -334,7 +334,7 @@ function resolveToolsGopath(): string { } export function getBinPath(tool: string): string { - return getBinPathWithPreferredGopath(tool, getToolsGopath(), getCurrentGoPath()); + return getBinPathWithPreferredGopath(tool, tool === 'go' ? [] : [getToolsGopath(), getCurrentGoPath()], vscode.workspace.getConfiguration('go', null).get('alternateTools')); } export function getFileArchive(document: vscode.TextDocument): string { @@ -569,7 +569,7 @@ export interface ICheckResult { * @param printUnexpectedOutput If true, then output that doesnt match expected format is printed to the output channel */ export function runTool(args: string[], cwd: string, severity: string, useStdErr: boolean, toolName: string, env: any, printUnexpectedOutput: boolean, token?: vscode.CancellationToken): Promise { - let goRuntimePath = getGoRuntimePath(); + let goRuntimePath = getBinPath('go'); let cmd; if (toolName) { cmd = getBinPath(toolName);