Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support "pathMappings" in "launch" debug configs. #7024

Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
df3240c
Allow "pathMappings" in "launch" debug configs.
ericsnowcurrently Aug 14, 2019
31563a1
Add "pathMappings" to IKnownLaunchRequestArguments.
ericsnowcurrently Aug 14, 2019
db8da45
Use BaseConfigurationResolver.isLocalHost().
ericsnowcurrently Aug 14, 2019
8c6f39d
Use test-specific path separator rather than the native one.
ericsnowcurrently Aug 14, 2019
c1a7cc4
Do not skip tests for non-windows.
ericsnowcurrently Aug 14, 2019
2f10628
Factor out BaseConfigurationResolver.fixUpPathMappings().
ericsnowcurrently Aug 14, 2019
d1a1055
Factor out AttachConfigurationResolver.resolvePathMappings().
ericsnowcurrently Aug 19, 2019
8b645f4
Fix up pathMappings for launch config.
ericsnowcurrently Aug 19, 2019
832d072
Add a NEWS entry.
ericsnowcurrently Aug 19, 2019
2d93fc7
Add a comment about not having default path mappings for launch.
ericsnowcurrently Aug 19, 2019
234bc5b
Use path.join() in tests.
ericsnowcurrently Aug 21, 2019
18b4f19
Use the OSType enum directly in tests.
ericsnowcurrently Aug 21, 2019
f07944c
Factor out getAvailableOptions().
ericsnowcurrently Aug 21, 2019
a997d3b
Factor out emulateOS().
ericsnowcurrently Aug 21, 2019
5c354d8
emulateOS() -> iterOSes().
ericsnowcurrently Aug 21, 2019
7014a17
Move getOSType() to utils.platform.
ericsnowcurrently Aug 21, 2019
8fd026f
Make the "path" module match the test-specific target OS.
ericsnowcurrently Aug 21, 2019
63925fd
Add comments for iterOSes().
ericsnowcurrently Aug 22, 2019
2d501d3
Make OSTestHelpers an object.
ericsnowcurrently Aug 22, 2019
4298d78
iterOSes() => getHelpersPerOS().
ericsnowcurrently Aug 22, 2019
24cc56c
Factor out setUpOSMocks().
ericsnowcurrently Aug 22, 2019
241316a
Factor out getPathModuleForOS().
ericsnowcurrently Aug 22, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/3568.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for the "pathMappings" setting in "launch" debug configs.
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,31 @@
"description": "IP address of the of the local debug server (default is localhost).",
"default": "localhost"
},
"pathMappings": {
"type": "array",
"label": "Path mappings.",
"items": {
"type": "object",
"label": "Path mapping",
"required": [
"localRoot",
"remoteRoot"
],
"properties": {
"localRoot": {
"type": "string",
"label": "Local source root.",
"default": "${workspaceFolder}"
},
"remoteRoot": {
"type": "string",
"label": "Remote source root.",
"default": ""
}
}
},
"default": []
},
"logToFile": {
"type": "boolean",
"description": "Enable logging of debugger events to a log file.",
Expand Down
26 changes: 12 additions & 14 deletions src/client/common/platform/platformService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as os from 'os';
import { coerce, SemVer } from 'semver';
import { sendTelemetryEvent } from '../../telemetry';
import { EventName, PlatformErrors } from '../../telemetry/constants';
import { OSType } from '../utils/platform';
import { getOSType, OSType } from '../utils/platform';
import { parseVersion } from '../utils/version';
import { NON_WINDOWS_PATH_VARIABLE_NAME, WINDOWS_PATH_VARIABLE_NAME } from './constants';
import { IPlatformService } from './types';
Expand All @@ -16,6 +16,17 @@ import { IPlatformService } from './types';
export class PlatformService implements IPlatformService {
public readonly osType: OSType = getOSType();
public version?: SemVer;
constructor() {
if (this.osType === OSType.Unknown) {
sendTelemetryEvent(
EventName.PLATFORM_INFO,
undefined,
{
failureType: PlatformErrors.FailedToDetermineOS
}
);
}
}
public get pathVariableName() {
return this.isWindows ? WINDOWS_PATH_VARIABLE_NAME : NON_WINDOWS_PATH_VARIABLE_NAME;
}
Expand Down Expand Up @@ -66,16 +77,3 @@ export class PlatformService implements IPlatformService {
return arch() === 'x64';
}
}

function getOSType(platform: string = process.platform): OSType {
if (/^win/.test(platform)) {
return OSType.Windows;
} else if (/^darwin/.test(platform)) {
return OSType.OSX;
} else if (/^linux/.test(platform)) {
return OSType.Linux;
} else {
sendTelemetryEvent(EventName.PLATFORM_INFO, undefined, { failureType: PlatformErrors.FailedToDetermineOS });
return OSType.Unknown;
}
}
13 changes: 13 additions & 0 deletions src/client/common/utils/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,16 @@ export enum OSType {
OSX = 'OSX',
Linux = 'Linux'
}

// Return the OS type for the given platform string.
export function getOSType(platform: string = process.platform): OSType {
if (/^win/.test(platform)) {
return OSType.Windows;
} else if (/^darwin/.test(platform)) {
return OSType.OSX;
} else if (/^linux/.test(platform)) {
return OSType.Linux;
} else {
return OSType.Unknown;
}
}
80 changes: 37 additions & 43 deletions src/client/debugger/extension/configuration/resolvers/attach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import { CancellationToken, Uri, WorkspaceFolder } from 'vscode';
import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types';
import { IPlatformService } from '../../../../common/platform/types';
import { IConfigurationService } from '../../../../common/types';
import { SystemVariables } from '../../../../common/variables/systemVariables';
import { AttachRequestArguments, DebugOptions } from '../../../types';
import { AttachRequestArguments, DebugOptions, PathMapping } from '../../../types';
import { BaseConfigurationResolver } from './base';

@injectable()
export class AttachConfigurationResolver extends BaseConfigurationResolver<AttachRequestArguments> {
constructor(@inject(IWorkspaceService) workspaceService: IWorkspaceService,
constructor(
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
@inject(IDocumentManager) documentManager: IDocumentManager,
@inject(IPlatformService) private readonly platformService: IPlatformService,
@inject(IConfigurationService) configurationService: IConfigurationService) {
super(workspaceService, documentManager, configurationService);
@inject(IPlatformService) platformService: IPlatformService,
@inject(IConfigurationService) configurationService: IConfigurationService
) {
super(workspaceService, documentManager, platformService, configurationService);
}
public async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: AttachRequestArguments, _token?: CancellationToken): Promise<AttachRequestArguments | undefined> {
const workspaceFolder = this.getWorkspaceFolder(folder);
Expand Down Expand Up @@ -83,46 +84,39 @@ export class AttachConfigurationResolver extends BaseConfigurationResolver<Attac
this.debugOption(debugOptions, DebugOptions.ShowReturnValue);
}

if (!debugConfiguration.pathMappings) {
debugConfiguration.pathMappings = [];
}
debugConfiguration.pathMappings = this.resolvePathMappings(
debugConfiguration.pathMappings || [],
debugConfiguration.host,
debugConfiguration.localRoot,
debugConfiguration.remoteRoot,
workspaceFolder
);
this.sendTelemetry('attach', debugConfiguration);
}

private resolvePathMappings(
pathMappings: PathMapping[],
host: string,
localRoot?: string,
remoteRoot?: string,
workspaceFolder?: Uri
) {
// This is for backwards compatibility.
if (debugConfiguration.localRoot && debugConfiguration.remoteRoot) {
debugConfiguration.pathMappings!.push({
localRoot: debugConfiguration.localRoot,
remoteRoot: debugConfiguration.remoteRoot
if (localRoot && remoteRoot) {
pathMappings.push({
localRoot: localRoot,
remoteRoot: remoteRoot
});
}
// If attaching to local host, then always map local root and remote roots.
if (workspaceFolder && debugConfiguration.host &&
['LOCALHOST', '127.0.0.1', '::1'].indexOf(debugConfiguration.host.toUpperCase()) >= 0) {
let configPathMappings;
if (debugConfiguration.pathMappings!.length === 0) {
configPathMappings = [{
localRoot: workspaceFolder.fsPath,
remoteRoot: workspaceFolder.fsPath
}];
} else {
// Expand ${workspaceFolder} variable first if necessary.
const systemVariables = new SystemVariables(workspaceFolder.fsPath);
configPathMappings = debugConfiguration.pathMappings.map(({ localRoot: mappedLocalRoot, remoteRoot }) => ({
localRoot: systemVariables.resolveAny(mappedLocalRoot),
remoteRoot
}));
}
// If on Windows, lowercase the drive letter for path mappings.
let pathMappings = configPathMappings;
if (this.platformService.isWindows) {
pathMappings = configPathMappings.map(({ localRoot: windowsLocalRoot, remoteRoot }) => {
let localRoot = windowsLocalRoot;
if (windowsLocalRoot.match(/^[A-Z]:/)) {
localRoot = `${windowsLocalRoot[0].toLowerCase()}${windowsLocalRoot.substr(1)}`;
}
return { localRoot, remoteRoot };
});
}
debugConfiguration.pathMappings = pathMappings;
}
this.sendTelemetry('attach', debugConfiguration);
if (this.isLocalHost(host)) {
pathMappings = this.fixUpPathMappings(
pathMappings,
workspaceFolder ? workspaceFolder.fsPath : ''
);
}
return pathMappings.length > 0
? pathMappings
: undefined;
}
}
59 changes: 55 additions & 4 deletions src/client/debugger/extension/configuration/resolvers/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,34 @@

'use strict';

// tslint:disable:no-invalid-template-strings
// tslint:disable:no-invalid-template-strings no-suspicious-comment

import { injectable } from 'inversify';
import * as path from 'path';
import { CancellationToken, DebugConfiguration, Uri, WorkspaceFolder } from 'vscode';
import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types';
import { PYTHON_LANGUAGE } from '../../../../common/constants';
import { IPlatformService } from '../../../../common/platform/types';
import { IConfigurationService } from '../../../../common/types';
import { SystemVariables } from '../../../../common/variables/systemVariables';
import { sendTelemetryEvent } from '../../../../telemetry';
import { EventName } from '../../../../telemetry/constants';
import { DebuggerTelemetry } from '../../../../telemetry/types';
import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../../../types';
import {
AttachRequestArguments, DebugOptions, LaunchRequestArguments, PathMapping
} from '../../../types';
import { PythonPathSource } from '../../types';
import { IDebugConfigurationResolver } from '../types';

@injectable()
export abstract class BaseConfigurationResolver<T extends DebugConfiguration> implements IDebugConfigurationResolver<T> {
protected pythonPathSource: PythonPathSource = PythonPathSource.launchJson;
constructor(protected readonly workspaceService: IWorkspaceService,
constructor(
protected readonly workspaceService: IWorkspaceService,
protected readonly documentManager: IDocumentManager,
protected readonly configurationService: IConfigurationService) { }
protected readonly platformService: IPlatformService,
protected readonly configurationService: IConfigurationService
) { }
public abstract resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): Promise<T | undefined>;
protected getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined {
if (folder) {
Expand Down Expand Up @@ -71,6 +78,50 @@ export abstract class BaseConfigurationResolver<T extends DebugConfiguration> im
const LocalHosts = ['localhost', '127.0.0.1', '::1'];
return (hostName && LocalHosts.indexOf(hostName.toLowerCase()) >= 0) ? true : false;
}
protected fixUpPathMappings(
pathMappings: PathMapping[],
defaultLocalRoot?: string,
defaultRemoteRoot?: string
): PathMapping[] {
if (!defaultLocalRoot) {
return [];
}
if (!defaultRemoteRoot) {
defaultRemoteRoot = defaultLocalRoot;
}

if (pathMappings.length === 0) {
pathMappings = [
{
localRoot: defaultLocalRoot,
remoteRoot: defaultRemoteRoot
}
];
} else {
// Expand ${workspaceFolder} variable first if necessary.
const systemVariables = new SystemVariables(defaultLocalRoot);
pathMappings = pathMappings.map(({ localRoot: mappedLocalRoot, remoteRoot }) => ({
localRoot: systemVariables.resolveAny(mappedLocalRoot),
// TODO: Apply to remoteRoot too?
remoteRoot
}));
}

// If on Windows, lowercase the drive letter for path mappings.
// TODO: Apply even if no localRoot?
if (this.platformService.isWindows) {
// TODO: Apply to remoteRoot too?
pathMappings = pathMappings.map(({ localRoot: windowsLocalRoot, remoteRoot }) => {
let localRoot = windowsLocalRoot;
if (windowsLocalRoot.match(/^[A-Z]:/)) {
localRoot = `${windowsLocalRoot[0].toLowerCase()}${windowsLocalRoot.substr(1)}`;
}
return { localRoot, remoteRoot };
});
}

return pathMappings;
}
protected isDebuggingFlask(debugConfiguration: Partial<LaunchRequestArguments & AttachRequestArguments>) {
return (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK') ? true : false;
}
Expand Down
18 changes: 16 additions & 2 deletions src/client/debugger/extension/configuration/resolvers/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver<Launc
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
@inject(IDocumentManager) documentManager: IDocumentManager,
@inject(IDiagnosticsService) @named(InvalidPythonPathInDebuggerServiceId) private readonly invalidPythonPathInDebuggerService: IInvalidPythonPathInDebuggerService,
@inject(IPlatformService) private readonly platformService: IPlatformService,
@inject(IPlatformService) platformService: IPlatformService,
@inject(IConfigurationService) configurationService: IConfigurationService,
@inject(IDebugEnvironmentVariablesService) private readonly debugEnvHelper: IDebugEnvironmentVariablesService
) {
super(workspaceService, documentManager, configurationService);
super(workspaceService, documentManager, platformService, configurationService);
}
public async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: LaunchRequestArguments, _token?: CancellationToken): Promise<LaunchRequestArguments | undefined> {
const workspaceFolder = this.getWorkspaceFolder(folder);
Expand Down Expand Up @@ -123,6 +123,20 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver<Launc
&& debugConfiguration.jinja !== false) {
this.debugOption(debugOptions, DebugOptions.Jinja);
}
// Unlike with attach, we do not set a default path mapping.
// (See: https://github.com/microsoft/vscode-python/issues/3568)
if (debugConfiguration.pathMappings) {
let pathMappings = debugConfiguration.pathMappings;
if (pathMappings.length > 0) {
pathMappings = this.fixUpPathMappings(
pathMappings || [],
workspaceFolder ? workspaceFolder.fsPath : ''
);
}
debugConfiguration.pathMappings = pathMappings.length > 0
? pathMappings
: undefined;
}
this.sendTelemetry(
debugConfiguration.request as 'launch' | 'test',
debugConfiguration
Expand Down
11 changes: 8 additions & 3 deletions src/client/debugger/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export enum DebugOptions {
SubProcess = 'Multiprocess'
}

export type PathMapping = {
localRoot: string;
remoteRoot: string;
};
interface ICommonDebugArguments {
redirectOutput?: boolean;
django?: boolean;
Expand All @@ -36,14 +40,15 @@ interface ICommonDebugArguments {
// Show return values of functions while stepping.
showReturnValue?: boolean;
subProcess?: boolean;
// An absolute path to local directory with source.
pathMappings?: PathMapping[];
}
export interface IKnownAttachDebugArguments extends ICommonDebugArguments {
workspaceFolder?: string;
// An absolute path to local directory with source.
customDebugger?: boolean;
// localRoot and remoteRoot are deprecated (replaced by pathMappings).
ericsnowcurrently marked this conversation as resolved.
Show resolved Hide resolved
localRoot?: string;
remoteRoot?: string;
pathMappings?: { localRoot: string; remoteRoot: string }[];
customDebugger?: boolean;
}

export interface IKnownLaunchRequestArguments extends ICommonDebugArguments {
Expand Down
Loading