in src/debugAdapter/goDebug.ts [427:745]
constructor(launchArgs: LaunchRequestArguments | AttachRequestArguments, program: string) {
this.request = launchArgs.request;
this.program = normalizePath(program);
this.remotePath = launchArgs.remotePath;
this.isApiV1 = false;
if (typeof launchArgs.apiVersion === 'number') {
this.isApiV1 = launchArgs.apiVersion === 1;
}
this.stackTraceDepth = typeof launchArgs.stackTraceDepth === 'number' ? launchArgs.stackTraceDepth : 50;
this.connection = new Promise(async (resolve, reject) => {
const mode = launchArgs.mode;
let dlvCwd = path.dirname(program);
let serverRunning = false;
const dlvArgs = new Array<string>();
// Get default LoadConfig values according to delve API:
// https://github.com/go-delve/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc1/server.go#L13
// https://github.com/go-delve/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc2/server.go#L423
this.loadConfig = launchArgs.dlvLoadConfig || {
followPointers: true,
maxVariableRecurse: 1,
maxStringLen: 64,
maxArrayValues: 64,
maxStructFields: -1
};
if (mode === 'remote') {
log(`Start remote debugging: connecting ${launchArgs.host}:${launchArgs.port}`);
this.debugProcess = null;
this.isRemoteDebugging = true;
this.goroot = await queryGOROOT(dlvCwd, process.env);
serverRunning = true; // assume server is running when in remote mode
connectClient(launchArgs.port, launchArgs.host, this.onclose);
return;
}
this.isRemoteDebugging = false;
let env: NodeJS.ProcessEnv;
if (launchArgs.request === 'launch') {
let isProgramDirectory = false;
// Validations on the program
if (!program) {
return reject('The program attribute is missing in the debug configuration in launch.json');
}
try {
const pstats = lstatSync(program);
if (pstats.isDirectory()) {
if (mode === 'exec') {
logError(`The program "${program}" must not be a directory in exec mode`);
return reject('The program attribute must be an executable in exec mode');
}
dlvCwd = program;
isProgramDirectory = true;
} else if (mode !== 'exec' && path.extname(program) !== '.go') {
logError(`The program "${program}" must be a valid go file in debug mode`);
return reject('The program attribute must be a directory or .go file in debug mode');
}
} catch (e) {
logError(`The program "${program}" does not exist: ${e}`);
return reject('The program attribute must point to valid directory, .go file or executable.');
}
// read env from disk and merge into env variables
try {
const fileEnvs = parseEnvFiles(launchArgs.envFile);
const launchArgsEnv = launchArgs.env || {};
env = Object.assign({}, process.env, fileEnvs, launchArgsEnv);
} catch (e) {
return reject(`failed to process 'envFile' and 'env' settings: ${e}`);
}
const dirname = isProgramDirectory ? program : path.dirname(program);
if (!env['GOPATH'] && (mode === 'debug' || mode === 'test')) {
// If no GOPATH is set, then infer it from the file/package path
// Not applicable to exec mode in which case `program` need not point to source code under GOPATH
env['GOPATH'] = getInferredGopath(dirname) || env['GOPATH'];
}
this.dlvEnv = env;
this.goroot = await queryGOROOT(dlvCwd, env);
log(`Using GOPATH: ${env['GOPATH']}`);
log(`Using GOROOT: ${this.goroot}`);
log(`Using PATH: ${env['PATH']}`);
if (launchArgs.noDebug) {
if (mode === 'debug') {
this.noDebug = true;
const build = ['build'];
const output = path.join(os.tmpdir(), correctBinname('out'));
build.push(`-o=${output}`);
const buildOptions: { [key: string]: any } = { cwd: dirname, env };
if (launchArgs.buildFlags) {
build.push(launchArgs.buildFlags);
}
if (isProgramDirectory) {
build.push('.');
} else {
build.push(program);
}
const goExe = getBinPathWithPreferredGopathGoroot('go', []);
log(`Current working directory: ${dirname}`);
log(`Building: ${goExe} ${build.join(' ')}`);
// Use spawnSync to ensure that the binary exists before running it.
const buffer = spawnSync(goExe, build, buildOptions);
if (buffer.stderr && buffer.stderr.length > 0) {
const str = buffer.stderr.toString();
if (this.onstderr) {
this.onstderr(str);
}
}
if (buffer.stdout && buffer.stdout.length > 0) {
const str = buffer.stdout.toString();
if (this.onstdout) {
this.onstdout(str);
}
}
if (buffer.status) {
logError(`Build process exiting with code: ${buffer.status} signal: ${buffer.signal}`);
if (this.onclose) {
this.onclose(buffer.status);
}
} else {
log(`Build process exiting normally ${buffer.signal}`);
}
if (buffer.error) {
reject(buffer.error);
}
// Run the built binary
let wd = dirname;
if (launchArgs.cwd) {
wd = launchArgs.cwd;
}
const runOptions: { [key: string]: any } = { cwd: wd, env };
const run = [];
if (launchArgs.args) {
run.push(...launchArgs.args);
}
log(`Current working directory: ${wd}`);
log(`Running: ${output} ${run.join(' ')}`);
this.debugProcess = spawn(output, run, runOptions);
this.debugProcess.stderr.on('data', (chunk) => {
const str = chunk.toString();
if (this.onstderr) {
this.onstderr(str);
}
});
this.debugProcess.stdout.on('data', (chunk) => {
const str = chunk.toString();
if (this.onstdout) {
this.onstdout(str);
}
});
this.debugProcess.on('close', (code) => {
if (code) {
logError(`Process exiting with code: ${code} signal: ${this.debugProcess.killed}`);
} else {
log(`Process exiting normally ${this.debugProcess.killed}`);
}
if (this.onclose) {
this.onclose(code);
}
});
this.debugProcess.on('error', (err) => {
reject(err);
});
resolve(null);
return;
}
}
this.noDebug = false;
if (!existsSync(launchArgs.dlvToolPath)) {
log(
`Couldn't find dlv at the Go tools path, ${process.env['GOPATH']}${
env['GOPATH'] ? ', ' + env['GOPATH'] : ''
} or ${envPath}`
);
return reject(
'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 currentGOWorkspace = getCurrentGoWorkspaceFromGOPATH(env['GOPATH'], dirname);
if (!launchArgs.packagePathToGoModPathMap) {
launchArgs.packagePathToGoModPathMap = {};
}
dlvArgs.push(mode || 'debug');
if (mode === 'exec' || (mode === 'debug' && !isProgramDirectory)) {
dlvArgs.push(program);
} else if (currentGOWorkspace && !launchArgs.packagePathToGoModPathMap[dirname]) {
dlvArgs.push(dirname.substr(currentGOWorkspace.length + 1));
}
// 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('--headless=true', `--listen=${launchArgs.host}:${launchArgs.port}`);
if (!this.isApiV1) {
dlvArgs.push('--api-version=2');
}
if (launchArgs.showLog) {
dlvArgs.push('--log=' + launchArgs.showLog.toString());
// Only add the log output flag if we have already added the log flag.
// Otherwise, delve complains.
if (launchArgs.logOutput) {
dlvArgs.push('--log-output=' + launchArgs.logOutput);
}
}
if (launchArgs.cwd) {
dlvArgs.push('--wd=' + launchArgs.cwd);
}
if (launchArgs.buildFlags) {
dlvArgs.push('--build-flags=' + launchArgs.buildFlags);
}
if (launchArgs.backend) {
dlvArgs.push('--backend=' + launchArgs.backend);
}
if (launchArgs.output && (mode === 'debug' || mode === 'test')) {
dlvArgs.push('--output=' + launchArgs.output);
}
if (launchArgs.args && launchArgs.args.length > 0) {
dlvArgs.push('--', ...launchArgs.args);
}
this.localDebugeePath = this.getLocalDebugeePath(launchArgs.output);
} else if (launchArgs.request === 'attach') {
if (!launchArgs.processId) {
return reject('Missing process ID');
}
if (!existsSync(launchArgs.dlvToolPath)) {
return reject(
'Cannot find Delve debugger. Install from https://github.com/go-delve/delve & ensure it is in your Go tools path, "GOPATH/bin" or "PATH".'
);
}
dlvArgs.push('attach', `${launchArgs.processId}`);
// 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('--headless=true', '--listen=' + launchArgs.host + ':' + launchArgs.port.toString());
if (!this.isApiV1) {
dlvArgs.push('--api-version=2');
}
if (launchArgs.showLog) {
dlvArgs.push('--log=' + launchArgs.showLog.toString());
}
if (launchArgs.logOutput) {
dlvArgs.push('--log-output=' + launchArgs.logOutput);
}
if (launchArgs.cwd) {
dlvArgs.push('--wd=' + launchArgs.cwd);
}
if (launchArgs.backend) {
dlvArgs.push('--backend=' + launchArgs.backend);
}
}
log(`Current working directory: ${dlvCwd}`);
log(`Running: ${launchArgs.dlvToolPath} ${dlvArgs.join(' ')}`);
this.debugProcess = spawn(launchArgs.dlvToolPath, dlvArgs, {
cwd: dlvCwd,
env
});
function connectClient(port: number, host: string, onClose?: Delve['onclose']) {
// Add a slight delay to avoid issues on Linux with
// Delve failing calls made shortly after connection.
setTimeout(() => {
const conn = Client.$create(port, host).connectSocket();
conn.on('connect', () => resolve(conn))
.on('error', reject)
.on('close', (hadError) => {
logError('Socket connection to remote was closed');
onClose?.(hadError ? 1 : 0);
});
}, 200);
}
this.debugProcess.stderr.on('data', (chunk) => {
const str = chunk.toString();
if (this.onstderr) {
this.onstderr(str);
}
});
this.debugProcess.stdout.on('data', (chunk) => {
const str = chunk.toString();
if (this.onstdout) {
this.onstdout(str);
}
if (!serverRunning) {
serverRunning = true;
connectClient(launchArgs.port, launchArgs.host, this.onclose);
}
});
this.debugProcess.on('close', (code) => {
// TODO: Report `dlv` crash to user.
logError('Process exiting with code: ' + code);
if (this.onclose) {
this.onclose(code);
}
});
this.debugProcess.on('error', (err) => {
reject(err);
});
});