| /*--------------------------------------------------------- |
| * Copyright (C) Microsoft Corporation. All rights reserved. |
| * Modification copyright 2020 The Go Authors. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------*/ |
| |
| 'use strict'; |
| |
| /** |
| * This file is loaded by both the extension and debug adapter, so it cannot import 'vscode' |
| */ |
| import fs = require('fs'); |
| import os = require('os'); |
| import path = require('path'); |
| import { promisify } from 'util'; |
| |
| let binPathCache: { [bin: string]: string } = {}; |
| |
| export const envPath = process.env['PATH'] || (process.platform === 'win32' ? process.env['Path'] : null); |
| |
| export function getBinPathFromEnvVar(toolName: string, envVarValue: string, appendBinToPath: boolean): string { |
| toolName = correctBinname(toolName); |
| if (envVarValue) { |
| const paths = envVarValue.split(path.delimiter); |
| for (const p of paths) { |
| const binpath = path.join(p, appendBinToPath ? 'bin' : '', toolName); |
| if (executableFileExists(binpath)) { |
| return binpath; |
| } |
| } |
| } |
| return null; |
| } |
| |
| export function getBinPathWithPreferredGopathGoroot( |
| toolName: string, |
| preferredGopaths: string[], |
| preferredGoroot?: string, |
| alternateTool?: string, |
| useCache = true, |
| ): string { |
| if (alternateTool && path.isAbsolute(alternateTool) && executableFileExists(alternateTool)) { |
| binPathCache[toolName] = alternateTool; |
| return alternateTool; |
| } |
| |
| // FIXIT: this cache needs to be invalidated when go.goroot or go.alternateTool is changed. |
| if (useCache && binPathCache[toolName]) { |
| return binPathCache[toolName]; |
| } |
| |
| const binname = alternateTool && !path.isAbsolute(alternateTool) ? alternateTool : toolName; |
| const pathFromGoBin = getBinPathFromEnvVar(binname, process.env['GOBIN'], false); |
| if (pathFromGoBin) { |
| binPathCache[toolName] = pathFromGoBin; |
| return pathFromGoBin; |
| } |
| |
| for (const preferred of preferredGopaths) { |
| if (typeof preferred === 'string') { |
| // Search in the preferred GOPATH workspace's bin folder |
| const pathFrompreferredGoPath = getBinPathFromEnvVar(binname, preferred, true); |
| if (pathFrompreferredGoPath) { |
| binPathCache[toolName] = pathFrompreferredGoPath; |
| return pathFrompreferredGoPath; |
| } |
| } |
| } |
| |
| // Check GOROOT (go, gofmt, godoc would be found here) |
| const pathFromGoRoot = getBinPathFromEnvVar(binname, preferredGoroot || getCurrentGoRoot(), true); |
| if (pathFromGoRoot) { |
| binPathCache[toolName] = pathFromGoRoot; |
| return pathFromGoRoot; |
| } |
| |
| // Finally search PATH parts |
| const pathFromPath = getBinPathFromEnvVar(binname, envPath, false); |
| if (pathFromPath) { |
| binPathCache[toolName] = pathFromPath; |
| return pathFromPath; |
| } |
| |
| // Check default path for go |
| if (toolName === 'go') { |
| const defaultPathForGo = process.platform === 'win32' ? 'C:\\Go\\bin\\go.exe' : '/usr/local/go/bin/go'; |
| if (executableFileExists(defaultPathForGo)) { |
| binPathCache[toolName] = defaultPathForGo; |
| return defaultPathForGo; |
| } |
| return; |
| } |
| |
| // Else return the binary name directly (this will likely always fail downstream) |
| return toolName; |
| } |
| |
| /** |
| * Returns the goroot path if it exists, otherwise returns an empty string |
| */ |
| let currentGoRoot = ''; |
| export function getCurrentGoRoot(): string { |
| return currentGoRoot || process.env['GOROOT'] || ''; |
| } |
| |
| export function setCurrentGoRoot(goroot: string) { |
| currentGoRoot = goroot; |
| } |
| |
| export function correctBinname(toolName: string) { |
| if (process.platform === 'win32') { |
| return toolName + '.exe'; |
| } |
| return toolName; |
| } |
| |
| function executableFileExists(filePath: string): boolean { |
| let exists = true; |
| try { |
| exists = fs.statSync(filePath).isFile(); |
| if (exists) { |
| fs.accessSync(filePath, fs.constants.F_OK | fs.constants.X_OK); |
| } |
| } catch (e) { |
| exists = false; |
| } |
| return exists; |
| } |
| |
| export function fileExists(filePath: string): boolean { |
| try { |
| return fs.statSync(filePath).isFile(); |
| } catch (e) { |
| return false; |
| } |
| } |
| |
| export async function pathExists(p: string): Promise<boolean> { |
| try { |
| const stat = promisify(fs.stat); |
| return (await stat(p)).isDirectory(); |
| } catch (e) { |
| return false; |
| } |
| } |
| |
| export function clearCacheForTools() { |
| binPathCache = {}; |
| } |
| |
| /** |
| * Exapnds ~ to homedir in non-Windows platform |
| */ |
| export function resolveHomeDir(inputPath: string): string { |
| if (!inputPath || !inputPath.trim()) { |
| return inputPath; |
| } |
| return inputPath.startsWith('~') ? path.join(os.homedir(), inputPath.substr(1)) : inputPath; |
| } |
| |
| export function stripBOM(s: string): string { |
| if (s && s[0] === '\uFEFF') { |
| s = s.substr(1); |
| } |
| return s; |
| } |
| |
| export function parseEnvFile(envFilePath: string): { [key: string]: string } { |
| const env: { [key: string]: any } = {}; |
| if (!envFilePath) { |
| return env; |
| } |
| |
| try { |
| const buffer = stripBOM(fs.readFileSync(envFilePath, 'utf8')); |
| buffer.split('\n').forEach((line) => { |
| const r = line.match(/^\s*([\w\.\-]+)\s*=\s*(.*)?\s*$/); |
| if (r !== null) { |
| let value = r[2] || ''; |
| if (value.length > 0 && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') { |
| value = value.replace(/\\n/gm, '\n'); |
| } |
| env[r[1]] = value.replace(/(^['"]|['"]$)/g, ''); |
| } |
| }); |
| return env; |
| } catch (e) { |
| throw new Error(`Cannot load environment variables from file ${envFilePath}`); |
| } |
| } |
| |
| // Walks up given folder path to return the closest ancestor that has `src` as a child |
| export function getInferredGopath(folderPath: string): string { |
| if (!folderPath) { |
| return; |
| } |
| |
| const dirs = folderPath.toLowerCase().split(path.sep); |
| |
| // find src directory closest to given folder path |
| const srcIdx = dirs.lastIndexOf('src'); |
| if (srcIdx > 0) { |
| return folderPath.substr(0, dirs.slice(0, srcIdx).join(path.sep).length); |
| } |
| } |
| |
| /** |
| * Returns the workspace in the given Gopath to which given directory path belongs to |
| * @param gopath string Current Gopath. Can be ; or : separated (as per os) to support multiple paths |
| * @param currentFileDirPath string |
| */ |
| export function getCurrentGoWorkspaceFromGOPATH(gopath: string, currentFileDirPath: string): string { |
| if (!gopath) { |
| return; |
| } |
| const workspaces: string[] = gopath.split(path.delimiter); |
| let currentWorkspace = ''; |
| currentFileDirPath = fixDriveCasingInWindows(currentFileDirPath); |
| |
| // Find current workspace by checking if current file is |
| // under any of the workspaces in $GOPATH |
| for (const workspace of workspaces) { |
| const possibleCurrentWorkspace = path.join(workspace, 'src'); |
| if ( |
| currentFileDirPath.startsWith(possibleCurrentWorkspace) || |
| (process.platform === 'win32' && |
| currentFileDirPath.toLowerCase().startsWith(possibleCurrentWorkspace.toLowerCase())) |
| ) { |
| // In case of nested workspaces, (example: both /Users/me and /Users/me/src/a/b/c are in $GOPATH) |
| // 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); |
| } |
| } |
| } |
| return currentWorkspace; |
| } |
| |
| // Workaround for issue in https://github.com/Microsoft/vscode/issues/9448#issuecomment-244804026 |
| export function fixDriveCasingInWindows(pathToFix: string): string { |
| return process.platform === 'win32' && pathToFix |
| ? pathToFix.substr(0, 1).toUpperCase() + pathToFix.substr(1) |
| : pathToFix; |
| } |
| |
| /** |
| * Returns the tool name from the given path to the tool |
| * @param toolPath |
| */ |
| export function getToolFromToolPath(toolPath: string): string | undefined { |
| if (!toolPath) { |
| return; |
| } |
| let tool = path.basename(toolPath); |
| if (process.platform === 'win32' && tool.endsWith('.exe')) { |
| tool = tool.substr(0, tool.length - 4); |
| } |
| return tool; |
| } |