| /* eslint-disable @typescript-eslint/no-explicit-any */ |
| /*--------------------------------------------------------- |
| * Copyright 2021 The Go Authors. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------*/ |
| |
| import { ChildProcess, ChildProcessWithoutNullStreams, spawn } from 'child_process'; |
| import * as fs from 'fs'; |
| import { DebugConfiguration } from 'vscode'; |
| import { logError, logInfo } from './goLogging'; |
| import { envPath } from './utils/pathUtils'; |
| import { killProcessTree } from './utils/processUtils'; |
| import getPort = require('get-port'); |
| import path = require('path'); |
| import vscode = require('vscode'); |
| |
| export class GoDebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory { |
| private dlvDapServer?: ChildProcess; |
| |
| public async createDebugAdapterDescriptor( |
| session: vscode.DebugSession, |
| executable: vscode.DebugAdapterExecutable | undefined |
| ): Promise<vscode.ProviderResult<vscode.DebugAdapterDescriptor>> { |
| if (session.configuration.debugAdapter === 'dlv-dap') { |
| return this.createDebugAdapterDescriptorDlvDap(session.configuration); |
| } |
| // Terminate any running dlv dap server process. |
| await this.terminateDlvDapServerProcess(); |
| return executable; |
| } |
| |
| public async dispose() { |
| await this.terminateDlvDapServerProcess(); |
| } |
| |
| private async createDebugAdapterDescriptorDlvDap( |
| configuration: vscode.DebugConfiguration |
| ): Promise<vscode.ProviderResult<vscode.DebugAdapterDescriptor>> { |
| // The dlv-dap server currently receives certain flags and arguments on startup |
| // and must be started in an appropriate folder for the program to be debugged. |
| // In order to support this, we kill the current dlv-dap server, and start a |
| // new one. |
| await this.terminateDlvDapServerProcess(); |
| |
| const { port, host, dlvDapServer } = await startDapServer(configuration); |
| this.dlvDapServer = dlvDapServer; |
| return new vscode.DebugAdapterServer(port, host); |
| } |
| |
| private async terminateDlvDapServerProcess() { |
| if (this.dlvDapServer) { |
| await killProcessTree(this.dlvDapServer); |
| this.dlvDapServer = null; |
| } |
| } |
| } |
| |
| export async function startDapServer( |
| configuration: DebugConfiguration |
| ): Promise<{ port: number; host: string; dlvDapServer?: ChildProcessWithoutNullStreams }> { |
| if (!configuration.host) { |
| configuration.host = '127.0.0.1'; |
| } |
| |
| if (configuration.port) { |
| // If a port has been specified, assume there is an already |
| // running dap server to connect to. |
| return { port: configuration.port, host: configuration.host }; |
| } else { |
| configuration.port = await getPort(); |
| } |
| const dlvDapServer = spawnDlvDapServerProcess(configuration, logInfo, logError); |
| // Wait to give dlv-dap a chance to start before returning. |
| return await new Promise<{ port: number; host: string; dlvDapServer: ChildProcessWithoutNullStreams }>((resolve) => |
| setTimeout(() => resolve({ port: configuration.port, host: configuration.host, dlvDapServer }), 500) |
| ); |
| } |
| |
| function spawnDlvDapServerProcess( |
| launchArgs: DebugConfiguration, |
| logFn: (...args: any[]) => void, |
| logErrFn: (...args: any[]) => void |
| ) { |
| const launchArgsEnv = launchArgs.env || {}; |
| const env = Object.assign({}, process.env, launchArgsEnv); |
| |
| // Let users override direct path to delve by setting it in the env |
| // map in launch.json; if unspecified, fall back to dlvToolPath. |
| let dlvPath = launchArgsEnv['dlvPath']; |
| if (!dlvPath) { |
| dlvPath = launchArgs.dlvToolPath; |
| } |
| |
| if (!fs.existsSync(dlvPath)) { |
| logErrFn( |
| `Couldn't find dlv at the Go tools path, ${process.env['GOPATH']}${ |
| env['GOPATH'] ? ', ' + env['GOPATH'] : '' |
| } or ${envPath}` |
| ); |
| throw new Error( |
| 'Cannot find Delve debugger. Install from https://github.com/go-delve/delve/ & ensure it is in your Go tools path, "GOPATH/bin" or "PATH".' |
| ); |
| } |
| |
| const dlvArgs = new Array<string>(); |
| dlvArgs.push('dap'); |
| // add user-specified dlv flags first. When duplicate flags are specified, |
| // dlv doesn't mind but accepts the last flag value. |
| if (launchArgs.dlvFlags && launchArgs.dlvFlags.length > 0) { |
| dlvArgs.push(...launchArgs.dlvFlags); |
| } |
| dlvArgs.push(`--listen=${launchArgs.host}:${launchArgs.port}`); |
| if (launchArgs.showLog) { |
| dlvArgs.push('--log=' + launchArgs.showLog.toString()); |
| } |
| if (launchArgs.logOutput) { |
| dlvArgs.push('--log-output=' + launchArgs.logOutput); |
| } |
| logFn(`Running: ${dlvPath} ${dlvArgs.join(' ')}`); |
| |
| const dir = parseProgramArgSync(launchArgs).dirname; |
| return spawn(dlvPath, dlvArgs, { |
| cwd: dir, |
| env |
| }); |
| } |
| |
| function parseProgramArgSync( |
| launchArgs: DebugConfiguration |
| ): { program: string; dirname: string; programIsDirectory: boolean } { |
| const program = launchArgs.program; |
| if (!program) { |
| throw new Error('The program attribute is missing in the debug configuration in launch.json'); |
| } |
| let programIsDirectory = false; |
| try { |
| programIsDirectory = fs.lstatSync(program).isDirectory(); |
| } catch (e) { |
| throw new Error('The program attribute must point to valid directory, .go file or executable.'); |
| } |
| if (!programIsDirectory && (path.extname(program) !== '.go' || launchArgs.mode === 'exec')) { |
| throw new Error('The program attribute must be a directory or .go file in debug and test mode'); |
| } |
| const dirname = programIsDirectory ? program : path.dirname(program); |
| return { program, dirname, programIsDirectory }; |
| } |