| /* eslint-disable @typescript-eslint/no-explicit-any */ | |
| /*--------------------------------------------------------- | |
| * Copyright (C) Microsoft Corporation. All rights reserved. | |
| * Licensed under the MIT License. See LICENSE in the project root for license information. | |
| *--------------------------------------------------------*/ | |
| import cp = require('child_process'); | |
| import path = require('path'); | |
| import util = require('util'); | |
| import vscode = require('vscode'); | |
| import vscodeUri = require('vscode-uri'); | |
| import { toolExecutionEnvironment } from './goEnv'; | |
| import { outputChannel } from './goStatus'; | |
| import { getBinPath, getGoVersion, getModuleCache, getWorkspaceFolderPath } from './util'; | |
| import { getEnvPath, fixDriveCasingInWindows, getCurrentGoRoot } from './utils/pathUtils'; | |
| import { CommandFactory } from './commands'; | |
| export let GO111MODULE: string | undefined; | |
| export async function runGoEnv(uri?: vscode.Uri, envvars: string[] = []): Promise<any> { | |
| const goExecutable = getBinPath('go'); | |
| if (!goExecutable) { | |
| console.warn( | |
| `Failed to run "go env GOMOD" to find mod file as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot()}) or PATH(${getEnvPath()})` | |
| ); | |
| return {}; | |
| } | |
| const env = toolExecutionEnvironment(uri); | |
| GO111MODULE = env['GO111MODULE']; | |
| const args = ['env', '-json'].concat(envvars); | |
| try { | |
| const { stdout, stderr } = await util.promisify(cp.execFile)(goExecutable, args, { cwd: uri?.fsPath, env }); | |
| if (stderr) { | |
| throw new Error(stderr); | |
| } | |
| return JSON.parse(stdout); | |
| } catch (e) { | |
| vscode.window.showErrorMessage(`Failed to run "go env ${args}": ${(e as Error).message}`); | |
| return {}; | |
| } | |
| } | |
| export function isModSupported(fileuri?: vscode.Uri, isDir?: boolean): Promise<boolean> { | |
| return getModFolderPath(fileuri, isDir).then((modPath) => !!modPath); | |
| } | |
| // packagePathToGoModPathMap is a cache that maps from a file path (of a package directory) | |
| // to the module root directory path (directory of `go env GOMOD`) if the file belongs to a module. | |
| export const packagePathToGoModPathMap: { [key: string]: string } = {}; | |
| // getModFolderPath returns the module root of the file. '' or undefined value indicates | |
| // the file is outside of any module or Go module is disabled. | |
| export async function getModFolderPath(fileuri?: vscode.Uri, isDir?: boolean): Promise<string | undefined> { | |
| const pkgUri = isDir ? fileuri : fileuri && vscodeUri.Utils.dirname(fileuri); | |
| const pkgPath = pkgUri?.fsPath ?? ''; | |
| if (pkgPath && packagePathToGoModPathMap[pkgPath]) { | |
| return packagePathToGoModPathMap[pkgPath]; | |
| } | |
| // We never would be using the path under module cache for anything | |
| // So, dont bother finding where exactly is the go.mod file | |
| const moduleCache = getModuleCache(); | |
| if (moduleCache && fixDriveCasingInWindows(fileuri?.fsPath ?? '').startsWith(moduleCache)) { | |
| return moduleCache; | |
| } | |
| const goVersion = await getGoVersion(); | |
| if (!goVersion || goVersion.lt('1.11')) { | |
| return; | |
| } | |
| const goModEnvJSON = await runGoEnv(pkgUri, ['GOMOD']); | |
| let goModEnvResult = | |
| goModEnvJSON['GOMOD'] === '/dev/null' || goModEnvJSON['GOMOD'] === 'NUL' ? '' : goModEnvJSON['GOMOD']; | |
| if (goModEnvResult) { | |
| goModEnvResult = path.dirname(goModEnvResult); | |
| } | |
| packagePathToGoModPathMap[pkgPath] = goModEnvResult; | |
| return goModEnvResult; | |
| } | |
| const folderToPackageMapping: { [key: string]: string } = {}; | |
| export async function getCurrentPackage(cwd: string): Promise<string> { | |
| if (folderToPackageMapping[cwd]) { | |
| return folderToPackageMapping[cwd]; | |
| } | |
| const moduleCache = getModuleCache(); | |
| if (moduleCache && cwd.startsWith(moduleCache)) { | |
| let importPath = cwd.substr(moduleCache.length + 1); | |
| const matches = /@v\d+(\.\d+)?(\.\d+)?/.exec(importPath); | |
| if (matches) { | |
| importPath = importPath.substr(0, matches.index); | |
| } | |
| folderToPackageMapping[cwd] = importPath; | |
| return importPath; | |
| } | |
| const goRuntimePath = getBinPath('go'); | |
| if (!goRuntimePath) { | |
| console.warn( | |
| `Failed to run "go list" to find current package as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot()}) or PATH(${getEnvPath()})` | |
| ); | |
| return ''; | |
| } | |
| return new Promise<string>((resolve) => { | |
| const childProcess = cp.spawn(goRuntimePath, ['list'], { cwd, env: toolExecutionEnvironment() }); | |
| const chunks: any[] = []; | |
| childProcess.stdout.on('data', (stdout) => { | |
| chunks.push(stdout); | |
| }); | |
| childProcess.on('close', () => { | |
| // Ignore lines that are empty or those that have logs about updating the module cache | |
| const pkgs = chunks | |
| .join('') | |
| .toString() | |
| .split('\n') | |
| .filter((line) => line && line.indexOf(' ') === -1); | |
| if (pkgs.length !== 1) { | |
| resolve(''); | |
| return; | |
| } | |
| folderToPackageMapping[cwd] = pkgs[0]; | |
| resolve(pkgs[0]); | |
| }); | |
| }); | |
| } | |
| export const goModInit: CommandFactory = () => async () => { | |
| const moduleName = await vscode.window.showInputBox({ | |
| prompt: 'Enter module name', | |
| value: '', | |
| placeHolder: 'example/project' | |
| }); | |
| if (!moduleName) { | |
| return; | |
| } | |
| const goRuntimePath = getBinPath('go'); | |
| const execFile = util.promisify(cp.execFile); | |
| try { | |
| const env = toolExecutionEnvironment(); | |
| const cwd = getWorkspaceFolderPath() ?? ''; | |
| outputChannel.appendLine(`Running "${goRuntimePath} mod init ${moduleName}"`); | |
| await execFile(goRuntimePath, ['mod', 'init', moduleName], { env, cwd }); | |
| outputChannel.appendLine('Module successfully initialized. You are ready to Go :)'); | |
| vscode.commands.executeCommand('vscode.open', vscode.Uri.file(path.join(cwd, 'go.mod'))); | |
| } catch (e) { | |
| outputChannel.error((e as Error).message); | |
| outputChannel.show(); | |
| vscode.window.showErrorMessage( | |
| `Error running "${goRuntimePath} mod init ${moduleName}": See Go output channel for details` | |
| ); | |
| } | |
| }; |