Skip to content

Commit

Permalink
Support "pathMappings" in "launch" debug configs. (#7024)
Browse files Browse the repository at this point in the history
(for #3568)
  • Loading branch information
ericsnowcurrently committed Aug 26, 2019
1 parent 24365d1 commit a0094d4
Show file tree
Hide file tree
Showing 12 changed files with 935 additions and 535 deletions.
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).
localRoot?: string;
remoteRoot?: string;
pathMappings?: { localRoot: string; remoteRoot: string }[];
customDebugger?: boolean;
}

export interface IKnownLaunchRequestArguments extends ICommonDebugArguments {
Expand Down
Loading

0 comments on commit a0094d4

Please sign in to comment.