Skip to content

Commit 93f32bb

Browse files
committed
src/debugAdapter: add substitutePath config for debugging
This change adds a new configuration option to both launch and attach requests. substituePath takes an array that maps from string to string that is used to translate paths passed to the debugger and then back to the client. This allows users to translate their symlinked directories to the files that were actually used to build the binary. In addition this can also be used for remote debugging, and when the location of the files has moved since the program was built. Update #622 Change-Id: I71b081d17a29655c14cd20093dc9f88867fbcc69 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/270017 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: Polina Sokolova <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
1 parent 8b7bc62 commit 93f32bb

File tree

4 files changed

+367
-22
lines changed

4 files changed

+367
-22
lines changed

Diff for: docs/debugging.md

+41-4
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ args | Array of command-line arguments to pass to the program being debugg
110110
showLog | If `true`, Delve logs will be printed in the Debug Console panel.
111111
logOutput | Comma-separated list of Delve components (`debugger`, `gdbwire`, `lldbout`, `debuglineerr`, `rpc`) that should produce debug output when `showLog` is `true`.
112112
buildFlags | Build flags to pass to the Go compiler.
113-
remotePath | If remote debugging (`mode`: `remote`), this should be the absolute path to the package being debugged on the remote machine. See the section on [Remote Debugging](#remote-debugging) for further details. [golang/vscode-go#45](https://github.com/golang/vscode-go/issues/45) is also relevant.
113+
remotePath | If remote debugging (`mode`: `remote`), this should be the absolute path to the package being debugged on the remote machine. See the section on [Remote Debugging](#remote-debugging) for further details. [golang/vscode-go#45](https://github.com/golang/vscode-go/issues/45) is also relevant. Becomes the first mapping in substitutePath.
114+
substitutePath | An array of mappings from an absolute local path to an absolute remote path that is used by the debuggee. The debug adapter will replace the local path with the remote path in all of the calls. The mappings are applied in order, and the first matching mapping is used. This can be used to map files that have moved since the program was built, different remote paths, and symlinked files or directories. This is intended to be equivalent to the [substitute-path]((https://github.com/go-delve/delve/tree/master/Documentation/cli#config)(https://github.com/go-delve/delve/tree/master/Documentation/cli#config)) configuration, and will eventually configure substitute-path in Delve directly.
114115
cwd | The working directory to be used in running the program. If remote debugging (`mode`: `remote`), this should be the absolute path to the working directory being debugged on the local machine. See the section on [Remote Debugging](#remote-debugging) for further details. [golang/vscode-go#45](https://github.com/golang/vscode-go/issues/45) is also relevant.
115116
processId | This is the process ID of the executable you want to debug. Applicable only when using the `attach` request in `local` mode.
116117

@@ -308,7 +309,27 @@ Then, create a remote debug configuration in your `launch.json`.
308309

309310
In the example, the VS Code debugger will run on the same machine as the headless `dlv` server. Make sure to update the `port` and `host` settings to point to your remote machine.
310311

311-
`remotePath` should point to the absolute path of the program being debugged in the remote machine. `cwd` should point to the absolute path of the working directory of the program being debugged on your local machine. This should be the counterpart of the folder in `remotePath`. See [golang/vscode-go#45](https://github.com/golang/vscode-go/issues/45) for updates regarding `remotePath` and `cwd`.
312+
`remotePath` should point to the absolute path of the program being debugged in the remote machine. `cwd` should point to the absolute path of the working directory of the program being debugged on your local machine. This should be the counterpart of the folder in `remotePath`. See [golang/vscode-go#45](https://github.com/golang/vscode-go/issues/45) for updates regarding `remotePath` and `cwd`. You can also use the equivalent `substitutePath` configuration.
313+
314+
```json5
315+
{
316+
"name": "Launch remote",
317+
"type": "go",
318+
"request": "attach",
319+
"mode": "remote",
320+
"substitutePath": [
321+
{
322+
"from": "/absolute/path/dir/on/local/machine",
323+
"to": "/absolute/path/dir/on/remote/machine",
324+
},
325+
],
326+
"port": 2345,
327+
"host": "127.0.0.1",
328+
"cwd": "/absolute/path/dir/on/local/machine",
329+
}
330+
```
331+
332+
If you do not set, `remotePath` or `substitutePath`, then the debug adapter will attempt to infer the path mappings. See [golang/vscode-go#45](https://github.com/golang/vscode-go/issues/45) for more information.
312333

313334
When you run the `Launch remote` target, VS Code will send debugging commands to the `dlv` server you started, instead of launching it's own `dlv` instance against your program.
314335

@@ -405,9 +426,25 @@ This error can show up for Mac users using Delve versions 0.12.2 and above. `xco
405426

406427
### Debugging symlink directories
407428

408-
This extension does not provide support for debugging projects containing symlinks. Make sure that you are setting breakpoints in the files that Go will use to compile your program.
429+
Since the debugger and go compiler use the actual filenames, extra configuration is required to debug symlinked directories. Use the `substitutePath` property to tell the debugAdapter how to properly translate the paths. For example, if your project lives in `/path/to/actual/helloWorld`, but the project is open in vscode under the linked folder `/path/to/hello`, you can add the following to your config to set breakpoints in the files in `/path/to/hello`:
430+
431+
```json5
432+
{
433+
"name": "Launch remote",
434+
"type": "go",
435+
"request": "launch",
436+
"mode": "auto",
437+
"program": "/path/to/hello",
438+
"substitutePath": [
439+
{
440+
"from": "/path/to/hello",
441+
"to": "/path/to/actual/helloWorld",
442+
},
443+
],
444+
}
445+
```
409446

410-
For updates to symlink support reference [golang/vscode-go#622](https://github.com/golang/vscode-go/issues/622).
447+
This extension does not provide general support for debugging projects containing symlinks. If `substitutePath` does not meet your needs, please consider commenting on this issue that contains updates to symlink support reference [golang/vscode-go#622](https://github.com/golang/vscode-go/issues/622).
411448

412449
[Delve]: https://github.com/go-delve/delve
413450
[VS Code variables]: https://code.visualstudio.com/docs/editor/variables-reference

Diff for: package.json

+43-3
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,26 @@
522522
"description": "Environment variables passed to the program.",
523523
"default": {}
524524
},
525+
"substitutePath": {
526+
"type": "array",
527+
"items": {
528+
"type": "object",
529+
"properties": {
530+
"from": {
531+
"type": "string",
532+
"description": "The absolute local path to be replaced when passing paths to the debugger",
533+
"default": ""
534+
},
535+
"to": {
536+
"type": "string",
537+
"description": "The absolute remote path to be replaced when passing paths back to the client",
538+
"default": ""
539+
}
540+
}
541+
},
542+
"description": "An array of mappings from a local path to the remote path that is used by the debuggee. The debug adapter will replace the local path with the remote path in all of the calls. Overriden by remotePath.",
543+
"default": []
544+
},
525545
"buildFlags": {
526546
"type": "string",
527547
"description": "Build flags, to be passed to the Go compiler.",
@@ -534,7 +554,7 @@
534554
},
535555
"remotePath": {
536556
"type": "string",
537-
"description": "Absolute path to the file being debugged on the remote machine in case of remote debugging.",
557+
"description": "Absolute path to the file being debugged on the remote machine in case of remote debugging. If specified, becomes the first entry in substitutePath.",
538558
"default": ""
539559
},
540560
"port": {
@@ -680,7 +700,7 @@
680700
},
681701
"remotePath": {
682702
"type": "string",
683-
"description": "If remote debugging, the path to the source code on the remote machine, if different from the local machine.",
703+
"description": "If remote debugging, the path to the source code on the remote machine, if different from the local machine. If specified, becomes the first entry in substitutePath.",
684704
"default": ""
685705
},
686706
"port": {
@@ -693,6 +713,26 @@
693713
"description": "The host name of the machine the delve debugger will be listening on.",
694714
"default": "127.0.0.1"
695715
},
716+
"substitutePath": {
717+
"type": "array",
718+
"items": {
719+
"type": "object",
720+
"properties": {
721+
"from": {
722+
"type": "string",
723+
"description": "The absolute local path to be replaced when passing paths to the debugger",
724+
"default": ""
725+
},
726+
"to": {
727+
"type": "string",
728+
"description": "The absolute remote path to be replaced when passing paths back to the client",
729+
"default": ""
730+
}
731+
}
732+
},
733+
"description": "An array of mappings from a local path to the remote path that is used by the debuggee. The debug adapter will replace the local path with the remote path in all of the calls.",
734+
"default": []
735+
},
696736
"trace": {
697737
"type": "string",
698738
"enum": [
@@ -1063,7 +1103,7 @@
10631103
},
10641104
"remotePath": {
10651105
"type": "string",
1066-
"description": "If remote debugging, the path to the source code on the remote machine, if different from the local machine.",
1106+
"description": "If remote debugging, the path to the source code on the remote machine, if different from the local machine. If specified, becomes the first entry in substitutePath.",
10671107
"default": ""
10681108
},
10691109
"port": {

Diff for: src/debugAdapter/goDebug.ts

+67-8
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
276276
trace?: 'verbose' | 'log' | 'error';
277277
backend?: string;
278278
output?: string;
279+
substitutePath?: {from: string, to: string}[];
279280
/** Delve LoadConfig parameters */
280281
dlvLoadConfig?: LoadConfig;
281282
dlvToolPath: string;
@@ -307,6 +308,7 @@ interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
307308
host?: string;
308309
trace?: 'verbose' | 'log' | 'error';
309310
backend?: string;
311+
substitutePath?: {from: string, to: string}[];
310312
/** Delve LoadConfig parameters */
311313
dlvLoadConfig?: LoadConfig;
312314
dlvToolPath: string;
@@ -369,6 +371,10 @@ function normalizePath(filePath: string) {
369371
return filePath;
370372
}
371373

374+
function normalizeSeparators(filePath: string): string {
375+
return filePath.replace(/\/|\\/g, '/');
376+
}
377+
372378
function getBaseName(filePath: string) {
373379
return filePath.includes('/') ? path.basename(filePath) : path.win32.basename(filePath);
374380
}
@@ -853,6 +859,9 @@ export class GoDebugSession extends LoggingDebugSession {
853859
private localToRemotePathMapping = new Map<string, string>();
854860
private remoteToLocalPathMapping = new Map<string, string>();
855861

862+
// TODO(suzmue): Use delve's implementation of substitute-path.
863+
private substitutePath: {from: string, to: string}[];
864+
856865
private showGlobalVariables: boolean = false;
857866

858867
private continueEpoch = 0;
@@ -1038,7 +1047,7 @@ export class GoDebugSession extends LoggingDebugSession {
10381047
}
10391048

10401049
protected async toDebuggerPath(filePath: string): Promise<string> {
1041-
if (this.delve.remotePath.length === 0) {
1050+
if (this.substitutePath.length === 0) {
10421051
if (this.delve.isRemoteDebugging) {
10431052
// The user trusts us to infer the remote path mapping!
10441053
await this.initializeRemotePackagesAndSources();
@@ -1047,14 +1056,28 @@ export class GoDebugSession extends LoggingDebugSession {
10471056
return matchedRemoteFile;
10481057
}
10491058
}
1059+
10501060
return this.convertClientPathToDebugger(filePath);
10511061
}
10521062

10531063
// The filePath may have a different path separator than the localPath
1054-
// So, update it to use the same separator as the remote path to ease
1055-
// in replacing the local path in it with remote path
1056-
filePath = filePath.replace(/\/|\\/g, this.remotePathSeparator);
1057-
return filePath.replace(this.delve.program.replace(/\/|\\/g, this.remotePathSeparator), this.delve.remotePath);
1064+
// So, update it to use the same separator for ease in path replacement.
1065+
filePath = normalizeSeparators(filePath);
1066+
let substitutedPath = filePath;
1067+
let substituteRule: {from: string, to: string};
1068+
this.substitutePath.forEach((value) => {
1069+
if (filePath.startsWith(value.from)) {
1070+
if (!!substituteRule) {
1071+
log(`Substitutition rule ${value.from}:${value.to} applies to local path ${filePath} but it was already mapped to debugger path using rule ${substituteRule.from}:${substituteRule.to}`);
1072+
return;
1073+
}
1074+
substitutedPath = filePath.replace(value.from, value.to);
1075+
substituteRule = {from: value.from, to: value.to};
1076+
}
1077+
});
1078+
filePath = substitutedPath;
1079+
1080+
return filePath = filePath.replace(/\/|\\/g, this.remotePathSeparator);
10581081
}
10591082

10601083
/**
@@ -1203,19 +1226,36 @@ export class GoDebugSession extends LoggingDebugSession {
12031226
* have been initialized.
12041227
*/
12051228
protected toLocalPath(pathToConvert: string): string {
1206-
if (this.delve.remotePath.length === 0) {
1229+
if (this.substitutePath.length === 0) {
12071230
// User trusts use to infer the path
12081231
if (this.delve.isRemoteDebugging) {
12091232
const inferredPath = this.inferLocalPathFromRemotePath(pathToConvert);
12101233
if (inferredPath) {
12111234
return inferredPath;
12121235
}
12131236
}
1237+
12141238
return this.convertDebuggerPathToClient(pathToConvert);
12151239
}
12161240

1241+
// If there is a substitutePath mapping, then we replace the path.
1242+
pathToConvert = normalizeSeparators(pathToConvert);
1243+
let substitutedPath = pathToConvert;
1244+
let substituteRule: {from: string, to: string};
1245+
this.substitutePath.forEach((value) => {
1246+
if (pathToConvert.startsWith(value.to)) {
1247+
if (!!substituteRule) {
1248+
log(`Substitutition rule ${value.from}:${value.to} applies to debugger path ${pathToConvert} but it was already mapped to local path using rule ${substituteRule.from}:${substituteRule.to}`);
1249+
return;
1250+
}
1251+
substitutedPath = pathToConvert.replace(value.to, value.from);
1252+
substituteRule = {from: value.from, to: value.to};
1253+
}
1254+
});
1255+
pathToConvert = substitutedPath;
1256+
12171257
// When the pathToConvert is under GOROOT or Go module cache, replace path appropriately
1218-
if (!pathToConvert.startsWith(this.delve.remotePath)) {
1258+
if (!substituteRule) {
12191259
// Fix for https://github.com/Microsoft/vscode-go/issues/1178
12201260
const index = pathToConvert.indexOf(`${this.remotePathSeparator}src${this.remotePathSeparator}`);
12211261
const goroot = this.getGOROOT();
@@ -1239,7 +1279,6 @@ export class GoDebugSession extends LoggingDebugSession {
12391279
}
12401280
}
12411281
return pathToConvert
1242-
.replace(this.delve.remotePath, this.delve.program)
12431282
.split(this.remotePathSeparator)
12441283
.join(this.localPathSeparator);
12451284
}
@@ -1835,6 +1874,7 @@ export class GoDebugSession extends LoggingDebugSession {
18351874
}
18361875

18371876
this.localPathSeparator = findPathSeparator(localPath);
1877+
this.substitutePath = [];
18381878
if (args.remotePath.length > 0) {
18391879
this.remotePathSeparator = findPathSeparator(args.remotePath);
18401880

@@ -1857,6 +1897,25 @@ export class GoDebugSession extends LoggingDebugSession {
18571897
) {
18581898
args.remotePath = args.remotePath.substring(0, args.remotePath.length - 1);
18591899
}
1900+
1901+
// Make the remotePath mapping the first one in substitutePath
1902+
// so that it will take precedence over the other mappings.
1903+
this.substitutePath.push({
1904+
from: normalizeSeparators(localPath),
1905+
to: normalizeSeparators(args.remotePath)
1906+
});
1907+
}
1908+
1909+
if (!!args.substitutePath) {
1910+
args.substitutePath.forEach((value) => {
1911+
if (!this.remotePathSeparator) {
1912+
this.remotePathSeparator = findPathSeparator(value.to);
1913+
}
1914+
this.substitutePath.push({
1915+
from: normalizeSeparators(value.from),
1916+
to: normalizeSeparators(value.to)
1917+
});
1918+
});
18601919
}
18611920

18621921
// Launch the Delve debugger on the program

0 commit comments

Comments
 (0)