constructor()

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);
			});
		});