Skip to content

Commit

Permalink
src/goDebugFactory.ts: run dlv-dap as a server and connect as server
Browse files Browse the repository at this point in the history
Connect to the dlv dap server directly from vscode, bypassing the
thin adapter.

This is accomplished using the vscode.DebugAdapterDescriptorFactory
for launch configurations of type "godlvdap".

Updates #23
Updates #978

Change-Id: I877a1c1b0cf0c40a2ba0602ed1e90a27d8f0159e
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/288954
Trust: Suzy Mueller <[email protected]>
Trust: Hyang-Ah Hana Kim <[email protected]>
Run-TryBot: Suzy Mueller <[email protected]>
TryBot-Result: kokoro <[email protected]>
Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
  • Loading branch information
suzmue committed Feb 8, 2021
1 parent 555b11b commit df853b9
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 44 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,6 @@
{
"type": "godlvdap",
"label": "Go Dlv Dap (Experimental)",
"program": "./dist/debugAdapter2.js",
"runtime": "node",
"languages": [
"go"
Expand Down
98 changes: 55 additions & 43 deletions src/debugAdapter2/goDlvDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as fs from 'fs';
import net = require('net');
import * as os from 'os';
import * as path from 'path';
import { DebugConfiguration } from 'vscode';

import {
logger,
Expand Down Expand Up @@ -39,7 +40,9 @@ interface LoadConfig {
}

// This interface should always match the schema found in `package.json`.
interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
type: string;
name: string;
request: 'launch';
[key: string]: any;
program: string;
Expand Down Expand Up @@ -652,48 +655,8 @@ class DelveClient extends DAPClient {
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');
// 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);
}
log(`Running: ${dlvPath} ${dlvArgs.join(' ')}`);

const dir = parseProgramArgSync(launchArgs).dirname;
this.debugProcess = spawn(dlvPath, dlvArgs, {
cwd: dir,
env
});
this.debugProcess = spawnDapServerProcess(launchArgs, log, logError);

this.debugProcess.stderr.on('data', (chunk) => {
let str = chunk.toString();
Expand Down Expand Up @@ -759,7 +722,7 @@ class DelveClient extends DAPClient {
//
// Throws an exception in case args.program is not a valid file or directory.
// This function can block because it calls a blocking fs function.
function parseProgramArgSync(launchArgs: LaunchRequestArguments
export function parseProgramArgSync(launchArgs: DebugConfiguration
): { program: string, dirname: string, programIsDirectory: boolean } {
const program = launchArgs.program;
if (!program) {
Expand All @@ -777,3 +740,52 @@ function parseProgramArgSync(launchArgs: LaunchRequestArguments
const dirname = programIsDirectory ? program : path.dirname(program);
return {program, dirname, programIsDirectory};
}

export function spawnDapServerProcess(
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
});
}
64 changes: 64 additions & 0 deletions src/goDebugFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*---------------------------------------------------------
* Copyright 2021 The Go Authors. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/

import { ChildProcess } from 'child_process';
import getPort = require('get-port');
import { DebugConfiguration } from 'vscode';
import vscode = require('vscode');
import { spawnDapServerProcess as spawnDlvDapServerProcess } from './debugAdapter2/goDlvDebug';
import { logError, logInfo } from './goLogging';
import { killProcessTree } from './utils/processUtils';

export class GoDebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {

private dlvDapServer?: ChildProcess;

public async createDebugAdapterDescriptor(
session: vscode.DebugSession,
executable: vscode.DebugAdapterExecutable | undefined
): 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} = await this.startDapServer(session.configuration);
return new vscode.DebugAdapterServer(port, host);
}

public async dispose() {
await this.terminateDlvDapServerProcess();
}

private async terminateDlvDapServerProcess() {
if (this.dlvDapServer) {
await killProcessTree(this.dlvDapServer);
this.dlvDapServer = null;
}
}

private async startDapServer(configuration: DebugConfiguration): Promise<{ port: number; host: string; }> {
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();
}

this.dlvDapServer = spawnDlvDapServerProcess(configuration, logInfo, logError);
// Wait to give dlv-dap a chance to start before returning.
return await
new Promise<{ port: number; host: string; }>((resolve) => setTimeout(() => {
resolve({port: configuration.port, host: configuration.host});
}, 500));
}

}
8 changes: 8 additions & 0 deletions src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
toggleCoverageCurrentPackage, trackCodeCoverageRemovalOnFileChange, updateCodeCoverageDecorators
} from './goCover';
import { GoDebugConfigurationProvider } from './goDebugConfiguration';
import { GoDebugAdapterDescriptorFactory } from './goDebugFactory';
import { extractFunction, extractVariable } from './goDoctor';
import { toolExecutionEnvironment } from './goEnv';
import { chooseGoEnvironment, offerToInstallLatestGoVersion, setEnvironmentVariableCollection } from './goEnvironmentStatus';
Expand Down Expand Up @@ -196,6 +197,13 @@ If you would like additional configuration for diagnostics from gopls, please se
return await pickGoProcess();
}));

const factory = new GoDebugAdapterDescriptorFactory();
ctx.subscriptions.push(
vscode.debug.registerDebugAdapterDescriptorFactory('godlvdap', factory));
if ('dispose' in factory) {
ctx.subscriptions.push(factory);
}

buildDiagnosticCollection = vscode.languages.createDiagnosticCollection('go');
ctx.subscriptions.push(buildDiagnosticCollection);
lintDiagnosticCollection = vscode.languages.createDiagnosticCollection('go-lint');
Expand Down

0 comments on commit df853b9

Please sign in to comment.