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
/**
* 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 {
/**
* The command path of the debug adapter executable.
* 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.
* Creates a description for a debug adapter based on an executable program.
*
* @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;
/**
* Optional arguments passed to the debug adapter executable.
* The arguments passed to the debug adapter executable. Defaults to an empty array.
*/
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 parent process' environment is used. If provided it is merged with
* 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;
/**
* Create a description for a debug adapter based on an executable program.
*/
constructor(command: string, args?: string[], env?: { [key: string]: string }, cwd?: string);
cwd?: string;
}
/**
@ -576,41 +592,38 @@ declare module 'vscode' {
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 {
/**
* Method '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.
* '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](#DebugAdapterDescriptor).
* Currently two types of debug adapters are supported:
* - a debug adapter executable specified as a command path and arguments (see DebugAdapterExecutable),
* - a debug adapter server reachable via a communication port (see DebugAdapterServer).
* - 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](#DebugAdapterServer)).
* If the method is not implemented the default behavior is this:
* provideDebugAdapter(session: DebugSession, folder: WorkspaceFolder | undefined, executable: DebugAdapterExecutable, config: DebugConfiguration, token?: CancellationToken) {
* if (typeof config.debugServer === 'number') {
* return new DebugAdapterServer(config.debugServer);
* provideDebugAdapter(session: DebugSession, executable: DebugAdapterExecutable) {
* if (typeof session.configuration.debugServer === 'number') {
* 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 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's descriptor](#DebugAdapterDescriptor) or undefined.
* @return a [debug adapter 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;
}
export namespace debug {
export interface DebugConfigurationProvider {
/**
* Register a [debug adapter provider](#DebugAdapterProvider) for a specific debug type.
* Only one provider can be registered for the same type.
* 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.
* Deprecated, use DebugAdapterProvider.provideDebugAdapter instead.
* @deprecated Use DebugAdapterProvider.provideDebugAdapter instead
*/
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

View file

@ -596,8 +596,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
type: 'executable',
command: x.command,
args: x.args,
cwd: x.cwd,
env: x.env
options: x.options
};
} else if (x instanceof DebugAdapterServer) {
return <IDebugAdapterServer>{
@ -662,7 +661,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
const type = config.type;
const promises = this._configProviders
.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([
Promise.all(promises).then(trackers => {
@ -701,7 +700,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
}
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
@ -719,9 +718,12 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
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);
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() {

View file

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

View file

@ -472,12 +472,16 @@ export interface IDebugAdapterFactory extends ITerminalLauncher {
substituteVariables(folder: IWorkspaceFolder, config: IConfig): Promise<IConfig>;
}
export interface IDebugAdapterExecutableOptions {
cwd?: string;
env?: { [key: string]: string };
}
export interface IDebugAdapterExecutable {
readonly type: 'executable';
readonly command: string;
readonly args: string[];
readonly cwd?: string;
readonly env?: { [key: string]: string };
readonly options?: IDebugAdapterExecutableOptions;
}
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) {
const isElectron = !!process.env['ELECTRON_RUN_AS_NODE'] || !!process.versions['electron'];
const options: cp.ForkOptions = {
env: this.adapterExecutable.env
? objects.mixin(objects.mixin({}, process.env), this.adapterExecutable.env)
env: this.adapterExecutable.options && this.adapterExecutable.options.env
? objects.mixin(objects.mixin({}, process.env), this.adapterExecutable.options.env)
: process.env,
execArgv: isElectron ? ['-e', 'delete process.env.ELECTRON_RUN_AS_NODE;require(process.argv[1])'] : [],
silent: true
};
if (this.adapterExecutable.cwd) {
options.cwd = this.adapterExecutable.cwd;
if (this.adapterExecutable.options && this.adapterExecutable.options.cwd) {
options.cwd = this.adapterExecutable.options.cwd;
}
const child = cp.fork(this.adapterExecutable.args[0], this.adapterExecutable.args.slice(1), options);
if (!child.pid) {
@ -356,12 +356,12 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
}
} else {
const options: cp.SpawnOptions = {
env: this.adapterExecutable.env
? objects.mixin(objects.mixin({}, process.env), this.adapterExecutable.env)
env: this.adapterExecutable.options && this.adapterExecutable.options.env
? objects.mixin(objects.mixin({}, process.env), this.adapterExecutable.options.env)
: process.env
};
if (this.adapterExecutable.cwd) {
options.cwd = this.adapterExecutable.cwd;
if (this.adapterExecutable.options && this.adapterExecutable.options.cwd) {
options.cwd = this.adapterExecutable.options.cwd;
}
this.serverProcess = cp.spawn(this.adapterExecutable.command, this.adapterExecutable.args, options);
resolve(null);
@ -474,8 +474,8 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
return result;
}
static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): IDebugAdapterExecutable {
const result: IDebuggerContribution = Object.create(null);
static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): IDebugAdapterExecutable | undefined {
let result: IDebuggerContribution = Object.create(null);
debugType = debugType.toLowerCase();
// merge all contributions into one
@ -488,7 +488,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
const extractedDbg = ExecutableDebugAdapter.extract(dbg, ed.extensionLocation.fsPath);
// 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,
args: (runtimeArgs || []).concat([program]).concat(args || [])
};
} else {
} else if (program) {
return {
type: 'executable',
command: program,
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
return Promise.resolve(this.configurationManager.createDebugAdapter(session));
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
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;
});
}