| /*--------------------------------------------------------- | |
| * Copyright 2020 The Go Authors. All rights reserved. | |
| * Licensed under the MIT License. See LICENSE in the project root for license information. | |
| *--------------------------------------------------------*/ | |
| // NOTE: This debug adapter is experimental, in-development code. If you | |
| // actually need to debug Go code, please use the default adapter. | |
| import { ChildProcess, spawn } from 'child_process'; | |
| import * as fs from 'fs'; | |
| import net = require('net'); | |
| import * as os from 'os'; | |
| import * as path from 'path'; | |
| import { | |
| logger, | |
| Logger, | |
| LoggingDebugSession, | |
| OutputEvent, | |
| TerminatedEvent | |
| } from 'vscode-debugadapter'; | |
| import { DebugProtocol } from 'vscode-debugprotocol'; | |
| import { | |
| envPath, | |
| getBinPathWithPreferredGopathGoroot, | |
| parseEnvFile | |
| } from '../utils/goPath'; | |
| import {killProcessTree} from '../utils/processUtils'; | |
| import { DAPClient } from './dapClient'; | |
| interface LoadConfig { | |
| // FollowPointers requests pointers to be automatically dereferenced. | |
| followPointers: boolean; | |
| // MaxVariableRecurse is how far to recurse when evaluating nested types. | |
| maxVariableRecurse: number; | |
| // MaxStringLen is the maximum number of bytes read from a string | |
| maxStringLen: number; | |
| // MaxArrayValues is the maximum number of elements read from an array, a slice or a map. | |
| maxArrayValues: number; | |
| // MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields. | |
| maxStructFields: number; | |
| } | |
| // This interface should always match the schema found in `package.json`. | |
| interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { | |
| request: 'launch'; | |
| [key: string]: any; | |
| program: string; | |
| stopOnEntry?: boolean; | |
| args?: string[]; | |
| showLog?: boolean; | |
| logOutput?: string; | |
| cwd?: string; | |
| env?: { [key: string]: string }; | |
| mode?: 'auto' | 'debug' | 'remote' | 'test' | 'exec'; | |
| remotePath?: string; | |
| port?: number; | |
| host?: string; | |
| buildFlags?: string; | |
| init?: string; | |
| trace?: 'verbose' | 'log' | 'error'; | |
| /** Optional path to .env file. */ | |
| envFile?: string | string[]; | |
| backend?: string; | |
| output?: string; | |
| /** Delve LoadConfig parameters */ | |
| dlvLoadConfig?: LoadConfig; | |
| dlvToolPath: string; | |
| /** Delve Version */ | |
| apiVersion: number; | |
| /** Delve maximum stack trace depth */ | |
| stackTraceDepth: number; | |
| showGlobalVariables?: boolean; | |
| packagePathToGoModPathMap: { [key: string]: string }; | |
| } | |
| interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { | |
| request: 'attach'; | |
| processId?: number; | |
| stopOnEntry?: boolean; | |
| showLog?: boolean; | |
| logOutput?: string; | |
| cwd?: string; | |
| mode?: 'local' | 'remote'; | |
| remotePath?: string; | |
| port?: number; | |
| host?: string; | |
| trace?: 'verbose' | 'log' | 'error'; | |
| backend?: string; | |
| /** Delve LoadConfig parameters */ | |
| dlvLoadConfig?: LoadConfig; | |
| dlvToolPath: string; | |
| /** Delve Version */ | |
| apiVersion: number; | |
| /** Delve maximum stack trace depth */ | |
| stackTraceDepth: number; | |
| showGlobalVariables?: boolean; | |
| } | |
| process.on('uncaughtException', (err: any) => { | |
| const errMessage = err && (err.stack || err.message); | |
| logger.error(`Unhandled error in debug adapter: ${errMessage}`); | |
| throw err; | |
| }); | |
| function logArgsToString(args: any[]): string { | |
| return args | |
| .map((arg) => { | |
| return typeof arg === 'string' ? arg : JSON.stringify(arg); | |
| }) | |
| .join(' '); | |
| } | |
| function log(...args: any[]) { | |
| logger.warn(logArgsToString(args)); | |
| } | |
| function logError(...args: any[]) { | |
| logger.error(logArgsToString(args)); | |
| } | |
| // GoDlvDapDebugSession implements a DAP debug adapter to talk to the editor. | |
| // | |
| // This adapter serves as a DAP proxy between the editor and the DAP server | |
| // inside Delve. It relies on functionality inherited from DebugSession to | |
| // implement the server side interfacing the editor, and on DapClient to | |
| // implement the client side interfacing Delve: | |
| // | |
| // Editor GoDlvDapDebugSession Delve | |
| // +------------+ +--------------+-----------+ +------------+ | |
| // | DAP Client | <====> | DebugSession | DAPClient | <====> | DAP Server | | |
| // +------------+ +--------------+-----------+ +------------+ | |
| export class GoDlvDapDebugSession extends LoggingDebugSession { | |
| private readonly DEFAULT_DELVE_HOST = '127.0.0.1'; | |
| private readonly DEFAULT_DELVE_PORT = 42042; | |
| private logLevel: Logger.LogLevel = Logger.LogLevel.Error; | |
| private dlvClient: DelveClient = null; | |
| // Child process used to track debugee launched without debugging (noDebug | |
| // mode). Either debugProcess or dlvClient are null. | |
| private debugProcess: ChildProcess = null; | |
| public constructor() { | |
| super(); | |
| // Invoke logger.init here because we want logging to work in 'inline' | |
| // DA mode. It's typically called in the start() method of our parent | |
| // class, but this method isn't called in 'inline' mode. | |
| logger.init((e) => this.sendEvent(e)); | |
| // this debugger uses zero-based lines and columns | |
| this.setDebuggerLinesStartAt1(false); | |
| this.setDebuggerColumnsStartAt1(false); | |
| } | |
| protected initializeRequest( | |
| response: DebugProtocol.InitializeResponse, | |
| args: DebugProtocol.InitializeRequestArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| log('InitializeRequest'); | |
| response.body.supportsConfigurationDoneRequest = true; | |
| // We respond to InitializeRequest here, because Delve hasn't been | |
| // launched yet. Delve will start responding to DAP requests after | |
| // LaunchRequest is received, which tell us how to start it. | |
| // TODO: we could send an InitializeRequest to Delve when | |
| // it launches, wait for its response and sanity check the capabilities | |
| // it reports. Once DAP support in Delve is complete, this can be part | |
| // of making sure that the "dlv" binary we find is sufficiently | |
| // up-to-date to talk DAP with us. | |
| this.sendResponse(response); | |
| log('InitializeResponse'); | |
| } | |
| protected launchRequest( | |
| response: DebugProtocol.LaunchResponse, | |
| args: LaunchRequestArguments, | |
| request: DebugProtocol.Request | |
| ): void { | |
| // Setup logger now that we have the 'trace' level passed in from | |
| // LaunchRequestArguments. | |
| this.logLevel = | |
| args.trace === 'verbose' | |
| ? Logger.LogLevel.Verbose | |
| : args.trace === 'log' | |
| ? Logger.LogLevel.Log | |
| : Logger.LogLevel.Error; | |
| const logPath = | |
| this.logLevel !== Logger.LogLevel.Error ? path.join(os.tmpdir(), 'vscode-godlvdapdebug.txt') : undefined; | |
| logger.setup(this.logLevel, logPath); | |
| log('launchRequest'); | |
| // In noDebug mode with the 'debug' launch mode, we don't launch Delve | |
| // but run the debugee directly. | |
| // For other launch modes we currently still defer to Delve, for | |
| // compatibility with the old debugAdapter. | |
| // See https://github.com/golang/vscode-go/issues/336 | |
| if (args.noDebug && args.mode === 'debug') { | |
| try { | |
| this.launchNoDebug(args); | |
| } catch (e) { | |
| logError(`launchNoDebug failed: "${e}"`); | |
| // TODO: define error constants | |
| // https://github.com/golang/vscode-go/issues/305 | |
| this.sendErrorResponse( | |
| response, | |
| 3000, | |
| `Failed to launch "${e}"`); | |
| } | |
| return; | |
| } | |
| if (!args.port) { | |
| args.port = this.DEFAULT_DELVE_PORT; | |
| } | |
| if (!args.host) { | |
| args.host = this.DEFAULT_DELVE_HOST; | |
| } | |
| this.dlvClient = new DelveClient(args); | |
| this.dlvClient.on('stdout', (str) => { | |
| log('dlv stdout:', str); | |
| }); | |
| this.dlvClient.on('stderr', (str) => { | |
| log('dlv stderr:', str); | |
| }); | |
| this.dlvClient.on('connected', () => { | |
| // Once the client is connected to Delve, forward it the launch | |
| // request to begin the actual debugging session. | |
| this.dlvClient.send(request); | |
| }); | |
| this.dlvClient.on('close', (rc) => { | |
| if (rc !== 0) { | |
| // TODO: define error constants | |
| // https://github.com/golang/vscode-go/issues/305 | |
| this.sendErrorResponse( | |
| response, | |
| 3000, | |
| 'Failed to continue: Check the debug console for details.'); | |
| } | |
| log('Sending TerminatedEvent as delve is closed'); | |
| this.sendEvent(new TerminatedEvent()); | |
| }); | |
| // Relay events and responses back to vscode. In the future we will | |
| // add middleware here to intercept specific kinds of responses/events | |
| // for special handling. | |
| this.dlvClient.on('event', (event) => { | |
| this.sendEvent(event); | |
| }); | |
| this.dlvClient.on('response', (resp) => { | |
| this.sendResponse(resp); | |
| }); | |
| } | |
| protected attachRequest( | |
| response: DebugProtocol.AttachResponse, | |
| args: AttachRequestArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected disconnectRequest( | |
| response: DebugProtocol.DisconnectResponse, | |
| args: DebugProtocol.DisconnectArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| log('DisconnectRequest'); | |
| // How we handle DisconnectRequest depends on whether Delve was launched | |
| // at all. | |
| // * In noDebug node, the Go program was spawned directly without | |
| // debugging: this.debugProcess will be non-null, and this.dlvClient | |
| // will be null. | |
| // * Otherwise, Delve was spawned: this.debugProcess will be null, and | |
| // this.dlvClient will be non-null. | |
| if (this.debugProcess !== null) { | |
| log(`killing debugee (pid: ${this.debugProcess.pid})...`); | |
| // Kill the debugee and notify the client when the killing is | |
| // completed, to ensure a clean shutdown sequence. | |
| killProcessTree(this.debugProcess, log).then(() => { | |
| super.disconnectRequest(response, args); | |
| log('DisconnectResponse'); | |
| }); | |
| } else if (this.dlvClient !== null) { | |
| // Forward this DisconnectRequest to Delve. | |
| this.dlvClient.send(request); | |
| } else { | |
| logError(`both debug process and dlv client are null`); | |
| // TODO: define all error codes as constants | |
| // https://github.com/golang/vscode-go/issues/305 | |
| this.sendErrorResponse( | |
| response, | |
| 3000, | |
| 'Failed to disconnect: Check the debug console for details.'); | |
| } | |
| } | |
| protected terminateRequest( | |
| response: DebugProtocol.TerminateResponse, | |
| args: DebugProtocol.TerminateArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected restartRequest( | |
| response: DebugProtocol.RestartResponse, | |
| args: DebugProtocol.RestartArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected setBreakPointsRequest( | |
| response: DebugProtocol.SetBreakpointsResponse, | |
| args: DebugProtocol.SetBreakpointsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected setFunctionBreakPointsRequest( | |
| response: DebugProtocol.SetFunctionBreakpointsResponse, | |
| args: DebugProtocol.SetFunctionBreakpointsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected setExceptionBreakPointsRequest( | |
| response: DebugProtocol.SetExceptionBreakpointsResponse, | |
| args: DebugProtocol.SetExceptionBreakpointsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected configurationDoneRequest( | |
| response: DebugProtocol.ConfigurationDoneResponse, | |
| args: DebugProtocol.ConfigurationDoneArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected continueRequest( | |
| response: DebugProtocol.ContinueResponse, | |
| args: DebugProtocol.ContinueArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected nextRequest( | |
| response: DebugProtocol.NextResponse, | |
| args: DebugProtocol.NextArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected stepInRequest( | |
| response: DebugProtocol.StepInResponse, | |
| args: DebugProtocol.StepInArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected stepOutRequest( | |
| response: DebugProtocol.StepOutResponse, | |
| args: DebugProtocol.StepOutArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected stepBackRequest( | |
| response: DebugProtocol.StepBackResponse, | |
| args: DebugProtocol.StepBackArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected reverseContinueRequest( | |
| response: DebugProtocol.ReverseContinueResponse, | |
| args: DebugProtocol.ReverseContinueArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected restartFrameRequest( | |
| response: DebugProtocol.RestartFrameResponse, | |
| args: DebugProtocol.RestartFrameArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected gotoRequest( | |
| response: DebugProtocol.GotoResponse, | |
| args: DebugProtocol.GotoArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected pauseRequest( | |
| response: DebugProtocol.PauseResponse, | |
| args: DebugProtocol.PauseArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected sourceRequest( | |
| response: DebugProtocol.SourceResponse, | |
| args: DebugProtocol.SourceArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected threadsRequest( | |
| response: DebugProtocol.ThreadsResponse, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected terminateThreadsRequest( | |
| response: DebugProtocol.TerminateThreadsResponse, | |
| args: DebugProtocol.TerminateThreadsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected stackTraceRequest( | |
| response: DebugProtocol.StackTraceResponse, | |
| args: DebugProtocol.StackTraceArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected scopesRequest( | |
| response: DebugProtocol.ScopesResponse, | |
| args: DebugProtocol.ScopesArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected variablesRequest( | |
| response: DebugProtocol.VariablesResponse, | |
| args: DebugProtocol.VariablesArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected setVariableRequest( | |
| response: DebugProtocol.SetVariableResponse, | |
| args: DebugProtocol.SetVariableArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected setExpressionRequest( | |
| response: DebugProtocol.SetExpressionResponse, | |
| args: DebugProtocol.SetExpressionArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected evaluateRequest( | |
| response: DebugProtocol.EvaluateResponse, | |
| args: DebugProtocol.EvaluateArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected stepInTargetsRequest( | |
| response: DebugProtocol.StepInTargetsResponse, | |
| args: DebugProtocol.StepInTargetsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected gotoTargetsRequest( | |
| response: DebugProtocol.GotoTargetsResponse, | |
| args: DebugProtocol.GotoTargetsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected completionsRequest( | |
| response: DebugProtocol.CompletionsResponse, | |
| args: DebugProtocol.CompletionsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected exceptionInfoRequest( | |
| response: DebugProtocol.ExceptionInfoResponse, | |
| args: DebugProtocol.ExceptionInfoArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected loadedSourcesRequest( | |
| response: DebugProtocol.LoadedSourcesResponse, | |
| args: DebugProtocol.LoadedSourcesArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected dataBreakpointInfoRequest( | |
| response: DebugProtocol.DataBreakpointInfoResponse, | |
| args: DebugProtocol.DataBreakpointInfoArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected setDataBreakpointsRequest( | |
| response: DebugProtocol.SetDataBreakpointsResponse, | |
| args: DebugProtocol.SetDataBreakpointsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected readMemoryRequest( | |
| response: DebugProtocol.ReadMemoryResponse, | |
| args: DebugProtocol.ReadMemoryArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected disassembleRequest( | |
| response: DebugProtocol.DisassembleResponse, | |
| args: DebugProtocol.DisassembleArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected cancelRequest( | |
| response: DebugProtocol.CancelResponse, | |
| args: DebugProtocol.CancelArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected breakpointLocationsRequest( | |
| response: DebugProtocol.BreakpointLocationsResponse, | |
| args: DebugProtocol.BreakpointLocationsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| protected setInstructionBreakpointsRequest( | |
| response: DebugProtocol.SetInstructionBreakpointsResponse, | |
| args: DebugProtocol.SetInstructionBreakpointsArguments, | |
| request?: DebugProtocol.Request | |
| ): void { | |
| this.dlvClient.send(request); | |
| } | |
| // Launch the debugee process without starting a debugger. | |
| // This implements the `Run > Run Without Debugger` functionality in vscode. | |
| // Note: this method currently assumes launchArgs.mode === 'debug'. | |
| private launchNoDebug(launchArgs: LaunchRequestArguments): void { | |
| if (launchArgs.mode !== 'debug') { | |
| throw new Error('launchNoDebug requires "debug" mode'); | |
| } | |
| 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') { | |
| throw new Error('The program attribute must be a directory or .go file in debug mode'); | |
| } | |
| const goRunArgs = ['run']; | |
| if (launchArgs.buildFlags) { | |
| goRunArgs.push(launchArgs.buildFlags); | |
| } | |
| if (programIsDirectory) { | |
| goRunArgs.push('.'); | |
| } else { | |
| goRunArgs.push(program); | |
| } | |
| if (launchArgs.args) { | |
| goRunArgs.push(...launchArgs.args); | |
| } | |
| // Read env from disk and merge into env variables. | |
| const fileEnvs = []; | |
| if (typeof launchArgs.envFile === 'string') { | |
| fileEnvs.push(parseEnvFile(launchArgs.envFile)); | |
| } | |
| if (Array.isArray(launchArgs.envFile)) { | |
| launchArgs.envFile.forEach((envFile) => { | |
| fileEnvs.push(parseEnvFile(envFile)); | |
| }); | |
| } | |
| const launchArgsEnv = launchArgs.env || {}; | |
| const programEnv = Object.assign({}, process.env, ...fileEnvs, launchArgsEnv); | |
| const dirname = programIsDirectory ? program : path.dirname(program); | |
| const goExe = getBinPathWithPreferredGopathGoroot('go', []); | |
| log(`Current working directory: ${dirname}`); | |
| log(`Running: ${goExe} ${goRunArgs.join(' ')}`); | |
| this.debugProcess = spawn(goExe, goRunArgs, { | |
| cwd: dirname, | |
| env: programEnv | |
| }); | |
| this.debugProcess.stderr.on('data', (str) => { | |
| this.sendEvent(new OutputEvent(str.toString(), 'stderr')); | |
| }); | |
| this.debugProcess.stdout.on('data', (str) => { | |
| this.sendEvent(new OutputEvent(str.toString(), 'stdout')); | |
| }); | |
| this.debugProcess.on('close', (rc) => { | |
| this.sendEvent(new TerminatedEvent()); | |
| }); | |
| } | |
| } | |
| // DelveClient provides a DAP client to talk to a DAP server in Delve. | |
| // | |
| // After creation, it emits the following events: | |
| // | |
| // 'connected': client is connected to delve | |
| // 'request (request)': delve sent request | |
| // 'response (response)': delve sent response | |
| // 'event (event)': delve sent event | |
| // 'stdout' (str): delve emitted str to stdout | |
| // 'stderr' (str): delve emitted str to stderr | |
| // 'close' (rc): delve exited with return code rc | |
| class DelveClient extends DAPClient { | |
| private debugProcess: ChildProcess; | |
| private serverStarted: boolean = false; | |
| constructor(launchArgs: LaunchRequestArguments) { | |
| super(); | |
| 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)) { | |
| log( | |
| `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'); | |
| 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); | |
| } | |
| log(`Running: ${dlvPath} ${dlvArgs.join(' ')}`); | |
| this.debugProcess = spawn(dlvPath, dlvArgs, { | |
| cwd: path.dirname(launchArgs.program), | |
| env | |
| }); | |
| this.debugProcess.stderr.on('data', (chunk) => { | |
| const str = chunk.toString(); | |
| this.emit('stderr', str); | |
| }); | |
| this.debugProcess.stdout.on('data', (chunk) => { | |
| const str = chunk.toString(); | |
| this.emit('stdout', str); | |
| if (!this.serverStarted) { | |
| this.serverStarted = true; | |
| this.connectSocketToServer(launchArgs.port, launchArgs.host); | |
| } | |
| }); | |
| this.debugProcess.on('close', (rc) => { | |
| if (rc) { | |
| logError(`Process exiting with code: ${rc} signal: ${this.debugProcess.killed}`); | |
| } else { | |
| log(`Process exiting normally ${this.debugProcess.killed}`); | |
| } | |
| this.emit('close', rc); | |
| }); | |
| this.debugProcess.on('error', (err) => { | |
| throw err; | |
| }); | |
| } | |
| // Connect this client to the server. The server is expected to be listening | |
| // on host:port. | |
| private connectSocketToServer(port: number, host: string) { | |
| // Add a slight delay to ensure that Delve started up the server. | |
| setTimeout(() => { | |
| const socket = net.createConnection( | |
| port, | |
| host, | |
| () => { | |
| this.connect(socket, socket); | |
| this.emit('connected'); | |
| }); | |
| socket.on('error', (err) => { | |
| throw err; | |
| }); | |
| }, 200); | |
| } | |
| } |