Skip to content

Commit a0094d4

Browse files
author
Eric Snow
authored
Support "pathMappings" in "launch" debug configs. (microsoft#7024)
(for microsoft#3568)
1 parent 24365d1 commit a0094d4

12 files changed

Lines changed: 935 additions & 535 deletions

File tree

news/2 Fixes/3568.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for the "pathMappings" setting in "launch" debug configs.

package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,31 @@
10241024
"description": "IP address of the of the local debug server (default is localhost).",
10251025
"default": "localhost"
10261026
},
1027+
"pathMappings": {
1028+
"type": "array",
1029+
"label": "Path mappings.",
1030+
"items": {
1031+
"type": "object",
1032+
"label": "Path mapping",
1033+
"required": [
1034+
"localRoot",
1035+
"remoteRoot"
1036+
],
1037+
"properties": {
1038+
"localRoot": {
1039+
"type": "string",
1040+
"label": "Local source root.",
1041+
"default": "${workspaceFolder}"
1042+
},
1043+
"remoteRoot": {
1044+
"type": "string",
1045+
"label": "Remote source root.",
1046+
"default": ""
1047+
}
1048+
}
1049+
},
1050+
"default": []
1051+
},
10271052
"logToFile": {
10281053
"type": "boolean",
10291054
"description": "Enable logging of debugger events to a log file.",

src/client/common/platform/platformService.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as os from 'os';
77
import { coerce, SemVer } from 'semver';
88
import { sendTelemetryEvent } from '../../telemetry';
99
import { EventName, PlatformErrors } from '../../telemetry/constants';
10-
import { OSType } from '../utils/platform';
10+
import { getOSType, OSType } from '../utils/platform';
1111
import { parseVersion } from '../utils/version';
1212
import { NON_WINDOWS_PATH_VARIABLE_NAME, WINDOWS_PATH_VARIABLE_NAME } from './constants';
1313
import { IPlatformService } from './types';
@@ -16,6 +16,17 @@ import { IPlatformService } from './types';
1616
export class PlatformService implements IPlatformService {
1717
public readonly osType: OSType = getOSType();
1818
public version?: SemVer;
19+
constructor() {
20+
if (this.osType === OSType.Unknown) {
21+
sendTelemetryEvent(
22+
EventName.PLATFORM_INFO,
23+
undefined,
24+
{
25+
failureType: PlatformErrors.FailedToDetermineOS
26+
}
27+
);
28+
}
29+
}
1930
public get pathVariableName() {
2031
return this.isWindows ? WINDOWS_PATH_VARIABLE_NAME : NON_WINDOWS_PATH_VARIABLE_NAME;
2132
}
@@ -66,16 +77,3 @@ export class PlatformService implements IPlatformService {
6677
return arch() === 'x64';
6778
}
6879
}
69-
70-
function getOSType(platform: string = process.platform): OSType {
71-
if (/^win/.test(platform)) {
72-
return OSType.Windows;
73-
} else if (/^darwin/.test(platform)) {
74-
return OSType.OSX;
75-
} else if (/^linux/.test(platform)) {
76-
return OSType.Linux;
77-
} else {
78-
sendTelemetryEvent(EventName.PLATFORM_INFO, undefined, { failureType: PlatformErrors.FailedToDetermineOS });
79-
return OSType.Unknown;
80-
}
81-
}

src/client/common/utils/platform.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,16 @@ export enum OSType {
1414
OSX = 'OSX',
1515
Linux = 'Linux'
1616
}
17+
18+
// Return the OS type for the given platform string.
19+
export function getOSType(platform: string = process.platform): OSType {
20+
if (/^win/.test(platform)) {
21+
return OSType.Windows;
22+
} else if (/^darwin/.test(platform)) {
23+
return OSType.OSX;
24+
} else if (/^linux/.test(platform)) {
25+
return OSType.Linux;
26+
} else {
27+
return OSType.Unknown;
28+
}
29+
}

src/client/debugger/extension/configuration/resolvers/attach.ts

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ import { CancellationToken, Uri, WorkspaceFolder } from 'vscode';
88
import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types';
99
import { IPlatformService } from '../../../../common/platform/types';
1010
import { IConfigurationService } from '../../../../common/types';
11-
import { SystemVariables } from '../../../../common/variables/systemVariables';
12-
import { AttachRequestArguments, DebugOptions } from '../../../types';
11+
import { AttachRequestArguments, DebugOptions, PathMapping } from '../../../types';
1312
import { BaseConfigurationResolver } from './base';
1413

1514
@injectable()
1615
export class AttachConfigurationResolver extends BaseConfigurationResolver<AttachRequestArguments> {
17-
constructor(@inject(IWorkspaceService) workspaceService: IWorkspaceService,
16+
constructor(
17+
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
1818
@inject(IDocumentManager) documentManager: IDocumentManager,
19-
@inject(IPlatformService) private readonly platformService: IPlatformService,
20-
@inject(IConfigurationService) configurationService: IConfigurationService) {
21-
super(workspaceService, documentManager, configurationService);
19+
@inject(IPlatformService) platformService: IPlatformService,
20+
@inject(IConfigurationService) configurationService: IConfigurationService
21+
) {
22+
super(workspaceService, documentManager, platformService, configurationService);
2223
}
2324
public async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: AttachRequestArguments, _token?: CancellationToken): Promise<AttachRequestArguments | undefined> {
2425
const workspaceFolder = this.getWorkspaceFolder(folder);
@@ -83,46 +84,39 @@ export class AttachConfigurationResolver extends BaseConfigurationResolver<Attac
8384
this.debugOption(debugOptions, DebugOptions.ShowReturnValue);
8485
}
8586

86-
if (!debugConfiguration.pathMappings) {
87-
debugConfiguration.pathMappings = [];
88-
}
87+
debugConfiguration.pathMappings = this.resolvePathMappings(
88+
debugConfiguration.pathMappings || [],
89+
debugConfiguration.host,
90+
debugConfiguration.localRoot,
91+
debugConfiguration.remoteRoot,
92+
workspaceFolder
93+
);
94+
this.sendTelemetry('attach', debugConfiguration);
95+
}
96+
97+
private resolvePathMappings(
98+
pathMappings: PathMapping[],
99+
host: string,
100+
localRoot?: string,
101+
remoteRoot?: string,
102+
workspaceFolder?: Uri
103+
) {
89104
// This is for backwards compatibility.
90-
if (debugConfiguration.localRoot && debugConfiguration.remoteRoot) {
91-
debugConfiguration.pathMappings!.push({
92-
localRoot: debugConfiguration.localRoot,
93-
remoteRoot: debugConfiguration.remoteRoot
105+
if (localRoot && remoteRoot) {
106+
pathMappings.push({
107+
localRoot: localRoot,
108+
remoteRoot: remoteRoot
94109
});
95110
}
96111
// If attaching to local host, then always map local root and remote roots.
97-
if (workspaceFolder && debugConfiguration.host &&
98-
['LOCALHOST', '127.0.0.1', '::1'].indexOf(debugConfiguration.host.toUpperCase()) >= 0) {
99-
let configPathMappings;
100-
if (debugConfiguration.pathMappings!.length === 0) {
101-
configPathMappings = [{
102-
localRoot: workspaceFolder.fsPath,
103-
remoteRoot: workspaceFolder.fsPath
104-
}];
105-
} else {
106-
// Expand ${workspaceFolder} variable first if necessary.
107-
const systemVariables = new SystemVariables(workspaceFolder.fsPath);
108-
configPathMappings = debugConfiguration.pathMappings.map(({ localRoot: mappedLocalRoot, remoteRoot }) => ({
109-
localRoot: systemVariables.resolveAny(mappedLocalRoot),
110-
remoteRoot
111-
}));
112-
}
113-
// If on Windows, lowercase the drive letter for path mappings.
114-
let pathMappings = configPathMappings;
115-
if (this.platformService.isWindows) {
116-
pathMappings = configPathMappings.map(({ localRoot: windowsLocalRoot, remoteRoot }) => {
117-
let localRoot = windowsLocalRoot;
118-
if (windowsLocalRoot.match(/^[A-Z]:/)) {
119-
localRoot = `${windowsLocalRoot[0].toLowerCase()}${windowsLocalRoot.substr(1)}`;
120-
}
121-
return { localRoot, remoteRoot };
122-
});
123-
}
124-
debugConfiguration.pathMappings = pathMappings;
125-
}
126-
this.sendTelemetry('attach', debugConfiguration);
112+
if (this.isLocalHost(host)) {
113+
pathMappings = this.fixUpPathMappings(
114+
pathMappings,
115+
workspaceFolder ? workspaceFolder.fsPath : ''
116+
);
117+
}
118+
return pathMappings.length > 0
119+
? pathMappings
120+
: undefined;
127121
}
128122
}

src/client/debugger/extension/configuration/resolvers/base.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,34 @@
33

44
'use strict';
55

6-
// tslint:disable:no-invalid-template-strings
6+
// tslint:disable:no-invalid-template-strings no-suspicious-comment
77

88
import { injectable } from 'inversify';
99
import * as path from 'path';
1010
import { CancellationToken, DebugConfiguration, Uri, WorkspaceFolder } from 'vscode';
1111
import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types';
1212
import { PYTHON_LANGUAGE } from '../../../../common/constants';
13+
import { IPlatformService } from '../../../../common/platform/types';
1314
import { IConfigurationService } from '../../../../common/types';
15+
import { SystemVariables } from '../../../../common/variables/systemVariables';
1416
import { sendTelemetryEvent } from '../../../../telemetry';
1517
import { EventName } from '../../../../telemetry/constants';
1618
import { DebuggerTelemetry } from '../../../../telemetry/types';
17-
import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../../../types';
19+
import {
20+
AttachRequestArguments, DebugOptions, LaunchRequestArguments, PathMapping
21+
} from '../../../types';
1822
import { PythonPathSource } from '../../types';
1923
import { IDebugConfigurationResolver } from '../types';
2024

2125
@injectable()
2226
export abstract class BaseConfigurationResolver<T extends DebugConfiguration> implements IDebugConfigurationResolver<T> {
2327
protected pythonPathSource: PythonPathSource = PythonPathSource.launchJson;
24-
constructor(protected readonly workspaceService: IWorkspaceService,
28+
constructor(
29+
protected readonly workspaceService: IWorkspaceService,
2530
protected readonly documentManager: IDocumentManager,
26-
protected readonly configurationService: IConfigurationService) { }
31+
protected readonly platformService: IPlatformService,
32+
protected readonly configurationService: IConfigurationService
33+
) { }
2734
public abstract resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): Promise<T | undefined>;
2835
protected getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined {
2936
if (folder) {
@@ -71,6 +78,50 @@ export abstract class BaseConfigurationResolver<T extends DebugConfiguration> im
7178
const LocalHosts = ['localhost', '127.0.0.1', '::1'];
7279
return (hostName && LocalHosts.indexOf(hostName.toLowerCase()) >= 0) ? true : false;
7380
}
81+
protected fixUpPathMappings(
82+
pathMappings: PathMapping[],
83+
defaultLocalRoot?: string,
84+
defaultRemoteRoot?: string
85+
): PathMapping[] {
86+
if (!defaultLocalRoot) {
87+
return [];
88+
}
89+
if (!defaultRemoteRoot) {
90+
defaultRemoteRoot = defaultLocalRoot;
91+
}
92+
93+
if (pathMappings.length === 0) {
94+
pathMappings = [
95+
{
96+
localRoot: defaultLocalRoot,
97+
remoteRoot: defaultRemoteRoot
98+
}
99+
];
100+
} else {
101+
// Expand ${workspaceFolder} variable first if necessary.
102+
const systemVariables = new SystemVariables(defaultLocalRoot);
103+
pathMappings = pathMappings.map(({ localRoot: mappedLocalRoot, remoteRoot }) => ({
104+
localRoot: systemVariables.resolveAny(mappedLocalRoot),
105+
// TODO: Apply to remoteRoot too?
106+
remoteRoot
107+
}));
108+
}
109+
110+
// If on Windows, lowercase the drive letter for path mappings.
111+
// TODO: Apply even if no localRoot?
112+
if (this.platformService.isWindows) {
113+
// TODO: Apply to remoteRoot too?
114+
pathMappings = pathMappings.map(({ localRoot: windowsLocalRoot, remoteRoot }) => {
115+
let localRoot = windowsLocalRoot;
116+
if (windowsLocalRoot.match(/^[A-Z]:/)) {
117+
localRoot = `${windowsLocalRoot[0].toLowerCase()}${windowsLocalRoot.substr(1)}`;
118+
}
119+
return { localRoot, remoteRoot };
120+
});
121+
}
122+
123+
return pathMappings;
124+
}
74125
protected isDebuggingFlask(debugConfiguration: Partial<LaunchRequestArguments & AttachRequestArguments>) {
75126
return (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK') ? true : false;
76127
}

src/client/debugger/extension/configuration/resolvers/launch.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver<Launc
2121
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
2222
@inject(IDocumentManager) documentManager: IDocumentManager,
2323
@inject(IDiagnosticsService) @named(InvalidPythonPathInDebuggerServiceId) private readonly invalidPythonPathInDebuggerService: IInvalidPythonPathInDebuggerService,
24-
@inject(IPlatformService) private readonly platformService: IPlatformService,
24+
@inject(IPlatformService) platformService: IPlatformService,
2525
@inject(IConfigurationService) configurationService: IConfigurationService,
2626
@inject(IDebugEnvironmentVariablesService) private readonly debugEnvHelper: IDebugEnvironmentVariablesService
2727
) {
28-
super(workspaceService, documentManager, configurationService);
28+
super(workspaceService, documentManager, platformService, configurationService);
2929
}
3030
public async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: LaunchRequestArguments, _token?: CancellationToken): Promise<LaunchRequestArguments | undefined> {
3131
const workspaceFolder = this.getWorkspaceFolder(folder);
@@ -123,6 +123,20 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver<Launc
123123
&& debugConfiguration.jinja !== false) {
124124
this.debugOption(debugOptions, DebugOptions.Jinja);
125125
}
126+
// Unlike with attach, we do not set a default path mapping.
127+
// (See: https://github.com/microsoft/vscode-python/issues/3568)
128+
if (debugConfiguration.pathMappings) {
129+
let pathMappings = debugConfiguration.pathMappings;
130+
if (pathMappings.length > 0) {
131+
pathMappings = this.fixUpPathMappings(
132+
pathMappings || [],
133+
workspaceFolder ? workspaceFolder.fsPath : ''
134+
);
135+
}
136+
debugConfiguration.pathMappings = pathMappings.length > 0
137+
? pathMappings
138+
: undefined;
139+
}
126140
this.sendTelemetry(
127141
debugConfiguration.request as 'launch' | 'test',
128142
debugConfiguration

src/client/debugger/types.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export enum DebugOptions {
2222
SubProcess = 'Multiprocess'
2323
}
2424

25+
export type PathMapping = {
26+
localRoot: string;
27+
remoteRoot: string;
28+
};
2529
interface ICommonDebugArguments {
2630
redirectOutput?: boolean;
2731
django?: boolean;
@@ -36,14 +40,15 @@ interface ICommonDebugArguments {
3640
// Show return values of functions while stepping.
3741
showReturnValue?: boolean;
3842
subProcess?: boolean;
43+
// An absolute path to local directory with source.
44+
pathMappings?: PathMapping[];
3945
}
4046
export interface IKnownAttachDebugArguments extends ICommonDebugArguments {
4147
workspaceFolder?: string;
42-
// An absolute path to local directory with source.
48+
customDebugger?: boolean;
49+
// localRoot and remoteRoot are deprecated (replaced by pathMappings).
4350
localRoot?: string;
4451
remoteRoot?: string;
45-
pathMappings?: { localRoot: string; remoteRoot: string }[];
46-
customDebugger?: boolean;
4752
}
4853

4954
export interface IKnownLaunchRequestArguments extends ICommonDebugArguments {

0 commit comments

Comments
 (0)