Debug API: add DebugAdapterExecutableOptions; for #62977

This commit is contained in:
Andre Weinand 2018-11-16 11:52:12 +01:00
parent 66b963d0cd
commit 61b9a88e52
6 changed files with 110 additions and 77 deletions

View file

@ -505,38 +505,54 @@ declare module 'vscode' {
//#region André: debug //#region André: debug
/** /**
* Represents a debug adapter executable and optional arguments passed to it. * Represents a debug adapter executable and optional arguments and runtime options passed to it.
*/ */
export class DebugAdapterExecutable { export class DebugAdapterExecutable {
/** /**
* The command path of the debug adapter executable. * Creates a description for a debug adapter based on an executable program.
* A command must be either an absolute path or the name of an executable looked up via the PATH environment variable. *
* The special value 'node' will be mapped to VS Code's built-in node runtime. * @param command The command or executable path that implements the debug adapter.
* @param args Optional arguments to be passed to the command or executable.
* @param options Optional options to be used when starting the command or executable.
*/
constructor(command: string, args?: string[], options?: DebugAdapterExecutableOptions);
/**
* The command or path of the debug adapter executable.
* A command must be either an absolute path of an executable or the name of an command to be looked up via the PATH environment variable.
* The special value 'node' will be mapped to VS Code's built-in Node.js runtime.
*/ */
readonly command: string; readonly command: string;
/** /**
* Optional arguments passed to the debug adapter executable. * The arguments passed to the debug adapter executable. Defaults to an empty array.
*/ */
readonly args: string[]; readonly args: string[];
/**
* Optional options to be used when the debug adapter is started.
* Defaults to undefined.
*/
readonly options?: DebugAdapterExecutableOptions;
}
/**
* Options for a debug adapter executable.
*/
export interface DebugAdapterExecutableOptions {
/** /**
* The additional environment of the executed program or shell. If omitted * The additional environment of the executed program or shell. If omitted
* the parent process' environment is used. If provided it is merged with * the parent process' environment is used. If provided it is merged with
* the parent process' environment. * the parent process' environment.
*/ */
readonly env?: { [key: string]: string }; env?: { [key: string]: string };
/** /**
* The working directory for the debug adapter. * The current working directory for the executed debug adapter.
*/ */
readonly cwd?: string; cwd?: string;
/**
* Create a description for a debug adapter based on an executable program.
*/
constructor(command: string, args?: string[], env?: { [key: string]: string }, cwd?: string);
} }
/** /**
@ -576,41 +592,38 @@ declare module 'vscode' {
export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterImplementation; export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterImplementation;
export interface DebugConfigurationProvider {
/**
* Deprecated, use DebugAdapterProvider.provideDebugAdapter instead.
* @deprecated Use DebugAdapterProvider.provideDebugAdapter instead
*/
debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult<DebugAdapterExecutable>;
/**
* The optional method 'provideDebugAdapterTracker' is called at the start of a debug session to provide a tracker that gives access to the communication between VS Code and a Debug Adapter.
* @param session The [debug session](#DebugSession) for which the tracker will be used.
* @param token A cancellation token.
*/
provideDebugAdapterTracker?(session: DebugSession, token?: CancellationToken): ProviderResult<DebugAdapterTracker>;
}
export interface DebugAdapterProvider { export interface DebugAdapterProvider {
/** /**
* Method 'provideDebugAdapter' is called at the start of a debug session to provide details about the debug adapter to use. * 'provideDebugAdapter' is called at the start of a debug session to provide details about the debug adapter to use.
* These details must be returned as objects of type DebugAdapterDescriptor. * These details must be returned as objects of type [DebugAdapterDescriptor](#DebugAdapterDescriptor).
* Currently two types of debug adapters are supported: * Currently two types of debug adapters are supported:
* - a debug adapter executable specified as a command path and arguments (see DebugAdapterExecutable), * - a debug adapter executable is specified as a command path and arguments (see [DebugAdapterExecutable](#DebugAdapterExecutable)),
* - a debug adapter server reachable via a communication port (see DebugAdapterServer). * - a debug adapter server reachable via a communication port (see [DebugAdapterServer](#DebugAdapterServer)).
* If the method is not implemented the default behavior is this: * If the method is not implemented the default behavior is this:
* provideDebugAdapter(session: DebugSession, folder: WorkspaceFolder | undefined, executable: DebugAdapterExecutable, config: DebugConfiguration, token?: CancellationToken) { * provideDebugAdapter(session: DebugSession, executable: DebugAdapterExecutable) {
* if (typeof config.debugServer === 'number') { * if (typeof session.configuration.debugServer === 'number') {
* return new DebugAdapterServer(config.debugServer); * return new DebugAdapterServer(session.configuration.debugServer);
* } * }
* return executable; * return executable;
* } * }
* @param session The [debug session](#DebugSession) for which the debug adapter will be used. * @param session The [debug session](#DebugSession) for which the debug adapter will be used.
* @param executable The debug adapter's executable information as specified in the package.json (or undefined if no such information exists). * @param executable The debug adapter's executable information as specified in the package.json (or undefined if no such information exists).
* @param token A cancellation token. * @return a [debug adapter descriptor](#DebugAdapterDescriptor) or undefined.
* @return a [debug adapter's descriptor](#DebugAdapterDescriptor) or undefined.
*/ */
provideDebugAdapter(session: DebugSession, executable: DebugAdapterExecutable | undefined, token?: CancellationToken): ProviderResult<DebugAdapterDescriptor>; provideDebugAdapter(session: DebugSession, executable: DebugAdapterExecutable | undefined): ProviderResult<DebugAdapterDescriptor>;
}
export namespace debug {
/**
* Register a [debug adapter provider](#DebugAdapterProvider) for a specific debug type.
* An extension is only allowed to register a DebugAdapterProvider for the debug type(s) defined by the extension. Otherwise an error is thrown.
* Registering more than one DebugAdapterProvider for a debug type results in an error.
*
* @param type The debug type for which the provider is registered.
* @param provider The [debug adapter provider](#DebugAdapterProvider) to register.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerDebugAdapterProvider(debugType: string, provider: DebugAdapterProvider): Disposable;
} }
/** /**
@ -628,18 +641,21 @@ declare module 'vscode' {
debugAdapterExit?(code?: number, signal?: string): void; debugAdapterExit?(code?: number, signal?: string): void;
} }
export namespace debug { export interface DebugConfigurationProvider {
/** /**
* Register a [debug adapter provider](#DebugAdapterProvider) for a specific debug type. * Deprecated, use DebugAdapterProvider.provideDebugAdapter instead.
* Only one provider can be registered for the same type. * @deprecated Use DebugAdapterProvider.provideDebugAdapter instead
* An extension is only allowed to register a DebugAdapterProvider for the debug type defined by the extension. Otherwise an error is thrown.
* Registering more than one DebugAdapterProvider for a debug type results in an error.
*
* @param type The debug type for which the provider is registered.
* @param provider The [debug adapter provider](#DebugAdapterProvider) to register.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/ */
export function registerDebugAdapterProvider(debugType: string, provider: DebugAdapterProvider): Disposable; debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult<DebugAdapterExecutable>;
/**
* Preliminary API, do not use in production.
*
* The optional method 'provideDebugAdapterTracker' is called at the start of a debug session to provide a tracker that gives access to the communication between VS Code and a Debug Adapter.
* @param session The [debug session](#DebugSession) for which the tracker will be used.
* @param token A cancellation token.
*/
provideDebugAdapterTracker?(session: DebugSession, workspaceFolder: WorkspaceFolder | undefined, config: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugAdapterTracker>;
} }
//#endregion //#endregion

View file

@ -596,8 +596,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
type: 'executable', type: 'executable',
command: x.command, command: x.command,
args: x.args, args: x.args,
cwd: x.cwd, options: x.options
env: x.env
}; };
} else if (x instanceof DebugAdapterServer) { } else if (x instanceof DebugAdapterServer) {
return <IDebugAdapterServer>{ return <IDebugAdapterServer>{
@ -662,7 +661,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
const type = config.type; const type = config.type;
const promises = this._configProviders const promises = this._configProviders
.filter(pair => pair.provider.provideDebugAdapterTracker && (pair.type === type || pair.type === '*')) .filter(pair => pair.provider.provideDebugAdapterTracker && (pair.type === type || pair.type === '*'))
.map(pair => asThenable(() => (pair.provider as any).provideDebugAdapterTracker(session, session.workspaceFolder, session.configuration, CancellationToken.None)).then(p => p).catch(err => null)); .map(pair => asThenable(() => pair.provider.provideDebugAdapterTracker(session, session.workspaceFolder, session.configuration, CancellationToken.None)).then(p => p).catch(err => null));
return Promise.race([ return Promise.race([
Promise.all(promises).then(trackers => { Promise.all(promises).then(trackers => {
@ -701,7 +700,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
} }
if (adapterProvider) { if (adapterProvider) {
return asThenable(() => adapterProvider.provideDebugAdapter(session, this.daExecutableFromPackage(session), CancellationToken.None)); return asThenable(() => adapterProvider.provideDebugAdapter(session, this.daExecutableFromPackage(session)));
} }
// try deprecated command based extension API "adapterExecutableCommand" to determine the executable // try deprecated command based extension API "adapterExecutableCommand" to determine the executable
@ -719,9 +718,12 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
return Promise.resolve(this.daExecutableFromPackage(session)); return Promise.resolve(this.daExecutableFromPackage(session));
} }
private daExecutableFromPackage(session: ExtHostDebugSession): DebugAdapterExecutable { private daExecutableFromPackage(session: ExtHostDebugSession): DebugAdapterExecutable | undefined {
const dae = ExecutableDebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), session.type); const dae = ExecutableDebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), session.type);
return new DebugAdapterExecutable(dae.command, dae.args, dae.env, dae.cwd); if (dae) {
return new DebugAdapterExecutable(dae.command, dae.args, dae.options);
}
return undefined;
} }
private startBreakpoints() { private startBreakpoints() {

View file

@ -1934,14 +1934,12 @@ export class FunctionBreakpoint extends Breakpoint {
export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable {
readonly command: string; readonly command: string;
readonly args: string[]; readonly args: string[];
readonly env?: { [key: string]: string }; readonly options?: vscode.DebugAdapterExecutableOptions;
readonly cwd?: string;
constructor(command: string, args?: string[], env?: { [key: string]: string }, cwd?: string) { constructor(command: string, args: string[], options?: vscode.DebugAdapterExecutableOptions) {
this.command = command; this.command = command;
this.args = args; this.args = args || [];
this.env = env; this.options = options;
this.cwd = cwd;
} }
} }

View file

@ -472,12 +472,16 @@ export interface IDebugAdapterFactory extends ITerminalLauncher {
substituteVariables(folder: IWorkspaceFolder, config: IConfig): Promise<IConfig>; substituteVariables(folder: IWorkspaceFolder, config: IConfig): Promise<IConfig>;
} }
export interface IDebugAdapterExecutableOptions {
cwd?: string;
env?: { [key: string]: string };
}
export interface IDebugAdapterExecutable { export interface IDebugAdapterExecutable {
readonly type: 'executable'; readonly type: 'executable';
readonly command: string; readonly command: string;
readonly args: string[]; readonly args: string[];
readonly cwd?: string; readonly options?: IDebugAdapterExecutableOptions;
readonly env?: { [key: string]: string };
} }
export interface IDebugAdapterServer { export interface IDebugAdapterServer {

View file

@ -336,14 +336,14 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
if (Array.isArray(this.adapterExecutable.args) && this.adapterExecutable.args.length > 0) { if (Array.isArray(this.adapterExecutable.args) && this.adapterExecutable.args.length > 0) {
const isElectron = !!process.env['ELECTRON_RUN_AS_NODE'] || !!process.versions['electron']; const isElectron = !!process.env['ELECTRON_RUN_AS_NODE'] || !!process.versions['electron'];
const options: cp.ForkOptions = { const options: cp.ForkOptions = {
env: this.adapterExecutable.env env: this.adapterExecutable.options && this.adapterExecutable.options.env
? objects.mixin(objects.mixin({}, process.env), this.adapterExecutable.env) ? objects.mixin(objects.mixin({}, process.env), this.adapterExecutable.options.env)
: process.env, : process.env,
execArgv: isElectron ? ['-e', 'delete process.env.ELECTRON_RUN_AS_NODE;require(process.argv[1])'] : [], execArgv: isElectron ? ['-e', 'delete process.env.ELECTRON_RUN_AS_NODE;require(process.argv[1])'] : [],
silent: true silent: true
}; };
if (this.adapterExecutable.cwd) { if (this.adapterExecutable.options && this.adapterExecutable.options.cwd) {
options.cwd = this.adapterExecutable.cwd; options.cwd = this.adapterExecutable.options.cwd;
} }
const child = cp.fork(this.adapterExecutable.args[0], this.adapterExecutable.args.slice(1), options); const child = cp.fork(this.adapterExecutable.args[0], this.adapterExecutable.args.slice(1), options);
if (!child.pid) { if (!child.pid) {
@ -356,12 +356,12 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
} }
} else { } else {
const options: cp.SpawnOptions = { const options: cp.SpawnOptions = {
env: this.adapterExecutable.env env: this.adapterExecutable.options && this.adapterExecutable.options.env
? objects.mixin(objects.mixin({}, process.env), this.adapterExecutable.env) ? objects.mixin(objects.mixin({}, process.env), this.adapterExecutable.options.env)
: process.env : process.env
}; };
if (this.adapterExecutable.cwd) { if (this.adapterExecutable.options && this.adapterExecutable.options.cwd) {
options.cwd = this.adapterExecutable.cwd; options.cwd = this.adapterExecutable.options.cwd;
} }
this.serverProcess = cp.spawn(this.adapterExecutable.command, this.adapterExecutable.args, options); this.serverProcess = cp.spawn(this.adapterExecutable.command, this.adapterExecutable.args, options);
resolve(null); resolve(null);
@ -474,8 +474,8 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
return result; return result;
} }
static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): IDebugAdapterExecutable { static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): IDebugAdapterExecutable | undefined {
const result: IDebuggerContribution = Object.create(null); let result: IDebuggerContribution = Object.create(null);
debugType = debugType.toLowerCase(); debugType = debugType.toLowerCase();
// merge all contributions into one // merge all contributions into one
@ -488,7 +488,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
const extractedDbg = ExecutableDebugAdapter.extract(dbg, ed.extensionLocation.fsPath); const extractedDbg = ExecutableDebugAdapter.extract(dbg, ed.extensionLocation.fsPath);
// merge // merge
objects.mixin(result, extractedDbg, ed.isBuiltin); result = objects.mixin(result, extractedDbg, ed.isBuiltin);
}); });
} }
} }
@ -519,12 +519,15 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
command: runtime, command: runtime,
args: (runtimeArgs || []).concat([program]).concat(args || []) args: (runtimeArgs || []).concat([program]).concat(args || [])
}; };
} else { } else if (program) {
return { return {
type: 'executable', type: 'executable',
command: program, command: program,
args: args || [] args: args || []
}; };
} }
// nothing found
return undefined;
} }
} }

View file

@ -56,7 +56,13 @@ export class Debugger implements IDebugger {
// TODO@AW: this.inExtHost() should now return true // TODO@AW: this.inExtHost() should now return true
return Promise.resolve(this.configurationManager.createDebugAdapter(session)); return Promise.resolve(this.configurationManager.createDebugAdapter(session));
default: default:
throw new Error('Cannot create debug adapter.'); throw new Error('unknown type');
}
}).catch(err => {
if (err && err.message) {
throw new Error(nls.localize('cannot.create.da.with.err', "Cannot create debug adapter ({0}).", err.message));
} else {
throw new Error(nls.localize('cannot.create.da', "Cannot create debug adapter."));
} }
}); });
} }
@ -92,7 +98,11 @@ export class Debugger implements IDebugger {
} }
// fallback: use executable information from package.json // fallback: use executable information from package.json
return ExecutableDebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type); const ae = ExecutableDebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type);
if (ae === undefined) {
throw new Error('no executable specified in package.json');
}
return ae;
}); });
} }