Permalink
Fetching contributors…
Cannot retrieve contributors at this time
484 lines (437 sloc) 15.8 KB
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------*/
'use strict';
import vscode = require('vscode');
import fs = require('fs');
import path = require('path');
import cp = require('child_process');
import { showGoStatus, hideGoStatus, outputChannel } from './goStatus';
import { getBinPath, getToolsGopath, getGoVersion, SemVersion, isVendorSupported, getCurrentGoPath, resolvePath } from './util';
import { goLiveErrorsEnabled } from './goLiveErrors';
let updatesDeclinedTools: string[] = [];
let installsDeclinedTools: string[] = [];
const allTools: { [key: string]: string } = {
'gocode': 'github.com/mdempsky/gocode',
'gopkgs': 'github.com/uudashr/gopkgs/cmd/gopkgs',
'go-outline': 'github.com/ramya-rao-a/go-outline',
'go-symbols': 'github.com/acroca/go-symbols',
'guru': 'golang.org/x/tools/cmd/guru',
'gorename': 'golang.org/x/tools/cmd/gorename',
'gomodifytags': 'github.com/fatih/gomodifytags',
'goplay': 'github.com/haya14busa/goplay/cmd/goplay',
'impl': 'github.com/josharian/impl',
'gotype-live': 'github.com/tylerb/gotype-live',
'godef': 'github.com/rogpeppe/godef',
'godoc': 'golang.org/x/tools/cmd/godoc',
'gogetdoc': 'github.com/zmb3/gogetdoc',
'goimports': 'golang.org/x/tools/cmd/goimports',
'goreturns': 'github.com/sqs/goreturns',
'goformat': 'winterdrache.de/goformat/goformat',
'golint': 'github.com/golang/lint/golint',
'gotests': 'github.com/cweill/gotests/...',
'gometalinter': 'github.com/alecthomas/gometalinter',
'megacheck': 'honnef.co/go/tools/...',
'golangci-lint': 'github.com/golangci/golangci-lint/cmd/golangci-lint',
'revive': 'github.com/mgechev/revive',
'go-langserver': 'github.com/sourcegraph/go-langserver',
'dlv': 'github.com/derekparker/delve/cmd/dlv',
'fillstruct': 'github.com/davidrjenni/reftools/cmd/fillstruct'
};
// Tools used explicitly by the basic features of the extension
const importantTools = [
'gocode',
'gopkgs',
'go-outline',
'go-symbols',
'guru',
'gorename',
'godef',
'godoc',
'gogetdoc',
'goreturns',
'goimports',
'golint',
'gometalinter',
'megacheck',
'golangci-lint',
'revive',
'dlv'
];
function getTools(goVersion: SemVersion): string[] {
let goConfig = vscode.workspace.getConfiguration('go', vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.uri : null);
let tools: string[] = [
'gocode',
'gopkgs',
'go-outline',
'go-symbols',
'guru',
'gorename',
'dlv'
];
// Install the doc/def tool that was chosen by the user
if (goConfig['docsTool'] === 'godoc') {
tools.push('godef');
tools.push('godoc');
} else if (goConfig['docsTool'] === 'gogetdoc') {
tools.push('gogetdoc');
}
// Install the formattool that was chosen by the user
if (goConfig['formatTool'] === 'goimports') {
tools.push('goimports');
} else if (goConfig['formatTool'] === 'goformat') {
tools.push('goformat');
} else if (goConfig['formatTool'] === 'goreturns') {
tools.push('goreturns');
}
// golint is not supported in go1.5
if (!goVersion || (goVersion.major > 1 || (goVersion.major === 1 && goVersion.minor > 5))) {
tools.push('golint');
}
if (goConfig['lintTool'] === 'gometalinter') {
tools.push('gometalinter');
}
if (goConfig['lintTool'] === 'megacheck') {
tools.push('megacheck');
}
if (goConfig['lintTool'] === 'golangci-lint') {
tools.push('golangci-lint');
}
if (goConfig['lintTool'] === 'revive') {
tools.push('revive');
}
if (goConfig['useLanguageServer'] && process.platform !== 'win32') {
tools.push('go-langserver');
}
if (goLiveErrorsEnabled()) {
tools.push('gotype-live');
}
// gotests is not supported in go1.5
if (!goVersion || (goVersion.major > 1 || (goVersion.major === 1 && goVersion.minor > 5))) {
tools.push('gotests');
}
tools.push(
'gomodifytags',
'impl',
'fillstruct',
'goplay'
);
return tools;
}
export function installAllTools() {
const allToolsDescription: { [key: string]: string } = {
'gocode': '\t\t(Auto-completion)',
'gopkgs': '\t\t(Auto-completion of unimported packages & Add Import feature)',
'go-outline': '\t(Go to symbol in file)',
'go-symbols': '\t(Go to symbol in workspace)',
'guru': '\t\t(Find all references and Go to implementation of symbols)',
'gorename': '\t(Rename symbols)',
'gomodifytags': '(Modify tags on structs)',
'goplay': '\t\t(The Go playground)',
'impl': '\t\t(Stubs for interfaces)',
'gotype-live': 'Show errors as you type)',
'godef': '\t\t(Go to definition)',
'godoc': '\t\t(For text shown on hover)',
'gogetdoc': '\t(Go to definition & text shown on hover)',
'goimports': '\t(Formatter)',
'goreturns': '\t(Formatter)',
'goformat': '\t(Formatter)',
'golint': '\t\t(Linter)',
'gotests': '\t\t(Generate unit tests)',
'gometalinter': 'Linter)',
'megacheck': '\t(Linter)',
'golangci-lint': 'Linter)',
'revive': '\t\t(Linter)',
'go-langserver': '(Language Server)',
'dlv': '\t\t\t(Debugging)',
'fillstruct': '\t\t(Fill structs with defaults)'
};
getGoVersion().then((goVersion) => {
const allTools = getTools(goVersion);
vscode.window.showQuickPick(allTools.map(x => `${x} ${allToolsDescription[x]}`), {
canPickMany: true,
placeHolder: 'Select the tool to install/update.'
}).then(selectedTools => {
installTools(selectedTools.map(x => x.substr(0, x.indexOf(' '))));
});
});
}
export function promptForMissingTool(tool: string) {
// If user has declined to install, then don't prompt
if (installsDeclinedTools.indexOf(tool) > -1) {
return;
}
getGoVersion().then((goVersion) => {
if (goVersion && goVersion.major === 1 && goVersion.minor < 6) {
if (tool === 'golint') {
vscode.window.showInformationMessage('golint no longer supports go1.5, update your settings to use gometalinter as go.lintTool and install gometalinter');
return;
}
if (tool === 'gotests') {
vscode.window.showInformationMessage('Generate unit tests feature is not supported as gotests tool needs go1.6 or higher.');
return;
}
}
const items = ['Install'];
getMissingTools(goVersion).then(missing => {
if (missing.indexOf(tool) === -1) {
return;
}
missing = missing.filter(x => x === tool || importantTools.indexOf(x) > -1);
if (missing.length > 1) {
items.push('Install All');
}
vscode.window.showInformationMessage(`The "${tool}" command is not available. Use "go get -v ${allTools[tool]}" to install.`, ...items).then(selected => {
if (selected === 'Install') {
installTools([tool]);
} else if (selected === 'Install All') {
installTools(missing);
hideGoStatus();
} else {
installsDeclinedTools.push(tool);
}
});
});
});
}
export function promptForUpdatingTool(tool: string) {
// If user has declined to update, then don't prompt
if (updatesDeclinedTools.indexOf(tool) > -1) {
return;
}
getGoVersion().then((goVersion) => {
vscode.window.showInformationMessage(`The Go extension is better with the latest version of "${tool}". Use "go get -u -v ${allTools[tool]}" to update`, 'Update').then(selected => {
if (selected === 'Update') {
installTools([tool]);
} else {
updatesDeclinedTools.push(tool);
}
});
});
}
/**
* Installs given array of missing tools. If no input is given, the all tools are installed
*
* @param string[] array of tool names to be installed
*/
function installTools(missing: string[]) {
let goRuntimePath = getBinPath('go');
if (!goRuntimePath) {
vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately');
return;
}
if (!missing) {
return;
}
// http.proxy setting takes precedence over environment variables
let httpProxy = vscode.workspace.getConfiguration('http').get('proxy');
let envForTools = Object.assign({}, process.env);
if (httpProxy) {
envForTools = Object.assign({}, process.env, {
http_proxy: httpProxy,
HTTP_PROXY: httpProxy,
https_proxy: httpProxy,
HTTPS_PROXY: httpProxy,
});
}
// 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() || getCurrentGoPath();
if (toolsGopath) {
envForTools['GOPATH'] = toolsGopath;
} else {
vscode.window.showInformationMessage('Cannot install Go tools. Set either go.gopath or go.toolsGopath in settings.', 'Open User Settings', 'Open Workspace Settings').then(selected => {
if (selected === 'Open User Settings') {
vscode.commands.executeCommand('workbench.action.openGlobalSettings');
} else if (selected === 'Open Workspace Settings') {
vscode.commands.executeCommand('workbench.action.openWorkspaceSettings');
}
});
return;
}
outputChannel.show();
outputChannel.clear();
outputChannel.appendLine(`Installing ${missing.length} ${missing.length > 1 ? 'tools' : 'tool'} at ${toolsGopath}${path.sep}bin`);
missing.forEach((missingTool, index, missing) => {
outputChannel.appendLine(' ' + missingTool);
});
outputChannel.appendLine(''); // Blank line for spacing.
missing.reduce((res: Promise<string[]>, tool: string) => {
return res.then(sofar => new Promise<string[]>((resolve, reject) => {
const callback = (err: Error, stdout: string, stderr: string) => {
if (err) {
outputChannel.appendLine('Installing ' + allTools[tool] + ' FAILED');
let failureReason = tool + ';;' + err + stdout.toString() + stderr.toString();
resolve([...sofar, failureReason]);
} else {
outputChannel.appendLine('Installing ' + allTools[tool] + ' SUCCEEDED');
if (tool === 'gometalinter') {
// Gometalinter needs to install all the linters it uses.
outputChannel.appendLine('Installing all linters used by gometalinter....');
let gometalinterBinPath = getBinPath('gometalinter');
cp.execFile(gometalinterBinPath, ['--install'], { env: envForTools }, (err, stdout, stderr) => {
if (!err) {
outputChannel.appendLine('Installing all linters used by gometalinter SUCCEEDED.');
resolve([...sofar, null]);
} else {
let failureReason = `Error while running gometalinter --install;; ${stderr}`;
resolve([...sofar, failureReason]);
}
});
} else {
resolve([...sofar, null]);
}
}
};
let closeToolPromise = Promise.resolve(true);
if (tool === 'gocode') {
closeToolPromise = new Promise<boolean>((innerResolve) => {
cp.execFile(getBinPath('gocode'), ['close'], {}, (err, stdout, stderr) => {
if (stderr && stderr.indexOf('rpc: can\'t find service Server.') > -1) {
outputChannel.appendLine('Installing gocode aborted as existing process cannot be closed. Please kill the running process for gocode and try again.');
return innerResolve(false);
}
innerResolve(true);
});
});
}
closeToolPromise.then((success) => {
if (!success) {
resolve([...sofar, null]);
return;
}
cp.execFile(goRuntimePath, ['get', '-u', '-v', allTools[tool]], { env: envForTools }, (err, stdout, stderr) => {
if (stderr.indexOf('unexpected directory layout:') > -1) {
outputChannel.appendLine(`Installing ${tool} failed with error "unexpected directory layout". Retrying...`);
cp.execFile(goRuntimePath, ['get', '-u', '-v', allTools[tool]], { env: envForTools }, callback);
} else {
callback(err, stdout, stderr);
}
});
});
}));
}, Promise.resolve([])).then(res => {
outputChannel.appendLine(''); // Blank line for spacing
let failures = res.filter(x => x != null);
if (failures.length === 0) {
if (missing.indexOf('go-langserver') > -1) {
outputChannel.appendLine('Reload VS Code window to use the Go language server');
}
outputChannel.appendLine('All tools successfully installed. You\'re ready to Go :).');
return;
}
outputChannel.appendLine(failures.length + ' tools failed to install.\n');
failures.forEach((failure, index, failures) => {
let reason = failure.split(';;');
outputChannel.appendLine(reason[0] + ':');
outputChannel.appendLine(reason[1]);
});
});
}
export function updateGoPathGoRootFromConfig(): Promise<void> {
let goroot = vscode.workspace.getConfiguration('go', vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.uri : null)['goroot'];
if (goroot) {
process.env['GOROOT'] = resolvePath(goroot);
}
if (process.env['GOPATH'] && process.env['GOROOT']) {
return Promise.resolve();
}
// If GOPATH is still not set, then use the one from `go env`
let goRuntimePath = getBinPath('go');
if (!goRuntimePath) {
return Promise.reject(new Error('Cannot find "go" binary. Update PATH or GOROOT appropriately'));
}
return new Promise<void>((resolve, reject) => {
cp.execFile(goRuntimePath, ['env', 'GOPATH', 'GOROOT'], (err, stdout, stderr) => {
if (err) {
return reject();
}
let envOutput = stdout.split('\n');
if (!process.env['GOPATH'] && envOutput[0].trim()) {
process.env['GOPATH'] = envOutput[0].trim();
}
if (!process.env['GOROOT'] && envOutput[1] && envOutput[1].trim()) {
process.env['GOROOT'] = envOutput[1].trim();
}
return resolve();
});
});
}
export function offerToInstallTools() {
isVendorSupported();
getGoVersion().then(goVersion => {
getMissingTools(goVersion).then(missing => {
missing = missing.filter(x => importantTools.indexOf(x) > -1);
if (missing.length > 0) {
showGoStatus('Analysis Tools Missing', 'go.promptforinstall', 'Not all Go tools are available on the GOPATH');
vscode.commands.registerCommand('go.promptforinstall', () => {
promptForInstall(missing);
});
}
});
});
function promptForInstall(missing: string[]) {
let installItem = {
title: 'Install',
command() {
hideGoStatus();
installTools(missing);
}
};
let showItem = {
title: 'Show',
command() {
outputChannel.clear();
outputChannel.appendLine('Below tools are needed for the basic features of the Go extension.');
missing.forEach(x => outputChannel.appendLine(x));
}
};
vscode.window.showInformationMessage('Some Go analysis tools are missing from your GOPATH. Would you like to install them?', installItem, showItem).then(selection => {
if (selection) {
selection.command();
} else {
hideGoStatus();
}
});
}
}
function getMissingTools(goVersion: SemVersion): Promise<string[]> {
let keys = getTools(goVersion);
return Promise.all<string>(keys.map(tool => new Promise<string>((resolve, reject) => {
let toolPath = getBinPath(tool);
fs.exists(toolPath, exists => {
resolve(exists ? null : tool);
});
}))).then(res => {
return res.filter(x => x != null);
});
}
// If langserver needs to be used, but is not installed, this will prompt user to install and Reload
// If langserver needs to be used, and is installed, this will return true
// Returns false in all other cases
export function checkLanguageServer(): boolean {
let latestGoConfig = vscode.workspace.getConfiguration('go');
if (!latestGoConfig['useLanguageServer']) return false;
if (process.platform === 'win32') {
vscode.window.showInformationMessage('The Go language server is not supported on Windows yet.');
return false;
}
if (!allFoldersHaveSameGopath()) {
vscode.window.showInformationMessage('The Go language server is not supported in a multi root set up with different GOPATHs.');
return false;
}
let langServerAvailable = getBinPath('go-langserver') !== 'go-langserver';
if (!langServerAvailable) {
promptForMissingTool('go-langserver');
vscode.window.showInformationMessage('Reload VS Code window after installing the Go language server');
}
return langServerAvailable;
}
function allFoldersHaveSameGopath(): boolean {
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length <= 1) {
return true;
}
let tempGopath = getCurrentGoPath(vscode.workspace.workspaceFolders[0].uri);
return vscode.workspace.workspaceFolders.find(x => tempGopath !== getCurrentGoPath(x.uri)) ? false : true;
}