feat: implement a pure DAP server (#1614)

* wip on implementing a pure dap debug server

* get it in a more working state
This commit is contained in:
Connor Peet 2023-03-24 09:09:42 -07:00 коммит произвёл GitHub
Родитель 952df18238
Коммит 53cfeec1e9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 17986 добавлений и 17472 удалений

Просмотреть файл

@ -38,5 +38,14 @@ extends:
- script: npm install --legacy-peer-deps
displayName: Install dependencies
- script: npm run compile -- dapDebugServer
displayName: Compile DAP Debug Server Bundle
- script: node src/build/archiveDapBundle $(Build.ArtifactStagingDirectory)/dap-server
displayName: Package DAP Debug Server Bundle
- publish: $(Build.ArtifactStagingDirectory)/dap-server
artifact: Publish DAP Debug Server Bundle
- script: npm run compile -- package:prepare
displayName: Package Stable

42
.vscode/launch.json поставляемый
Просмотреть файл

@ -52,25 +52,18 @@
// "preLaunchTask": "npm: watch"
},
{
"name": "Extension",
"type": "pwa-extensionHost",
"type": "node",
"request": "launch",
"name": "Debug Server (DAP)",
"trace": true,
"program": "${workspaceFolder}/dist/src/dapDebugServer.js",
"skipFiles": [
"<node_internals>/**"
],
"args": [
"--enable-proposed-api=ms-vscode.js-debug",
"--extensionDevelopmentPath=${workspaceFolder}/dist",
"--extensionDevelopmentPath=${workspaceFolder}/../vscode-hexeditor",
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js",
"${workspaceFolder}/../vscode/extensions/debug-auto-launch/out/**/*.js"
"<node_internals>/**/*.js"
]
// "preLaunchTask": "npm: watch"
},
{
"name": "Extension (packaged)",
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"skipFiles": [
@ -78,10 +71,29 @@
],
"args": [
"--enable-proposed-api=ms-vscode.js-debug",
"--extensionDevelopmentPath=${workspaceFolder}/dist"
"--extensionDevelopmentPath=${workspaceFolder}/dist",
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
"${workspaceFolder}/dist/**/*.js",
]
// "preLaunchTask": "npm: watch"
},
{
"name": "Extension With Local DAP",
"type": "extensionHost",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"args": [
"--enable-proposed-api=ms-vscode.js-debug",
"--extensionDevelopmentPath=${workspaceFolder}/dist",
],
"env": {
"JS_DEBUG_USE_LOCAL_DAP_PORT": "8123"
},
"outFiles": [
"${workspaceFolder}/dist/**/*.js",
]
// "preLaunchTask": "npm: watch"
},

Просмотреть файл

@ -219,6 +219,11 @@ async function compileTs({
}
await Promise.all(todo);
await fs.promises.appendFile(
path.resolve(buildSrcDir, 'bootloader.js'),
'\n//# sourceURL=bootloader.bundle.cdp',
);
}
/** Run webpack to bundle the extension output files */
@ -243,18 +248,19 @@ gulp.task(
/** Run webpack to bundle into the flat session launcher (for VS or standalone debug server) */
gulp.task('flatSessionBundle:webpack-bundle', async () => {
const packages = [{ entry: `${srcDir}/flatSessionLauncher.ts`, library: true }];
return compileTs({ packages, sourcemap: true });
return compileTs({ packages, sourcemap: isWatch });
});
gulp.task('package:bootloader-as-cdp', done => {
const bootloaderFilePath = path.resolve(buildSrcDir, 'bootloader.js');
fs.appendFile(bootloaderFilePath, '\n//# sourceURL=bootloader.bundle.cdp', done);
/** Run webpack to bundle into the standard DAP debug server */
gulp.task('dapDebugServer:webpack-bundle', async () => {
const packages = [{ entry: `${srcDir}/dapDebugServer.ts`, library: false }];
return compileTs({ packages, sourcemap: isWatch });
});
/** Run webpack to bundle into the VS debug server */
gulp.task('vsDebugServerBundle:webpack-bundle', async () => {
const packages = [{ entry: `${srcDir}/vsDebugServer.ts`, library: true }];
return compileTs({ packages, sourcemap: true });
return compileTs({ packages, sourcemap: isWatch });
});
const vsceUrls = {
@ -281,19 +287,19 @@ gulp.task(
'compile:build-scripts',
'compile:dynamic',
'compile:extension',
'package:bootloader-as-cdp',
'package:createVSIX',
),
);
gulp.task('package', gulp.series('package:prepare', 'package:createVSIX'));
gulp.task('flatSessionBundle', gulp.series('clean', 'compile', 'flatSessionBundle:webpack-bundle'));
gulp.task(
'flatSessionBundle',
gulp.series('clean', 'compile', 'flatSessionBundle:webpack-bundle', 'package:bootloader-as-cdp'),
'dapDebugServer',
gulp.series('clean', 'compile:static', 'dapDebugServer:webpack-bundle'),
);
// for now, this task will build both flat session and debug server until we no longer need flat session
gulp.task(
'vsDebugServerBundle',
gulp.series(
@ -301,7 +307,6 @@ gulp.task(
'compile',
'vsDebugServerBundle:webpack-bundle',
'flatSessionBundle:webpack-bundle',
'package:bootloader-as-cdp',
),
);

Просмотреть файл

@ -41,7 +41,7 @@
"precommit": "npm-run-all --parallel test:lint test:types",
"updatetypes": "cd src/typings && npx vscode-dts dev && npx vscode-dts master",
"updatenodeapi": "python src/build/getNodePdl.py && prettier --write src/build/nodeCustom.ts",
"generateapis": "node dist/src/build/generateDap.js && node dist/src/build/generateCdp.js",
"generateapis": "tsx src/build/generateDap.ts && tsx src/build/generateCdp.ts",
"test": "gulp && npm-run-all --parallel test:unit test:types test:golden test:lint",
"test:types": "tsc --noEmit",
"test:unit": "tsx node_modules/mocha/bin/mocha.js --config .mocharc.unit.js",

Просмотреть файл

@ -76,7 +76,7 @@ export class DebugAdapter implements IDisposable {
});
this.dap = dap;
this.dap.on('initialize', params => this._onInitialize(params));
this.dap.on('initialize', params => this.onInitialize(params));
this.dap.on('setBreakpoints', params => this._onSetBreakpoints(params));
this.dap.on('setExceptionBreakpoints', params => this.setExceptionBreakpoints(params));
this.dap.on('configurationDone', () => this.configurationDone());
@ -196,7 +196,9 @@ export class DebugAdapter implements IDisposable {
return {};
}
async _onInitialize(params: Dap.InitializeParams): Promise<Dap.InitializeResult | Dap.Error> {
public async onInitialize(
params: Dap.InitializeParams,
): Promise<Dap.InitializeResult | Dap.Error> {
console.assert(params.linesStartAt1);
console.assert(params.columnsStartAt1);
const capabilities = DebugAdapter.capabilities(true);

Просмотреть файл

@ -91,9 +91,7 @@ export class Binder implements IDisposable {
filterErrorsReportedToTelemetry();
}
setTimeout(() => {
dap.initialized({});
}, 0);
setTimeout(() => dap.initialized({}), 0);
return capabilities;
});
dap.on('setExceptionBreakpoints', async () => ({}));
@ -115,23 +113,13 @@ export class Binder implements IDisposable {
dap.on('threads', async () => ({ threads: [] }));
dap.on('loadedSources', async () => ({ sources: [] }));
dap.on('breakpointLocations', () => Promise.resolve({ breakpoints: [] }));
dap.on('attach', params =>
this._boot(
applyDefaults(
params as AnyResolvingConfiguration,
this._rootServices.get(ExtensionLocation),
),
dap,
),
);
dap.on('launch', params => {
return this._boot(
applyDefaults(
params as AnyResolvingConfiguration,
this._rootServices.get(ExtensionLocation),
),
dap,
);
dap.on('attach', async params => {
await this.boot(params as AnyResolvingConfiguration, dap);
return {};
});
dap.on('launch', async params => {
await this.boot(params as AnyResolvingConfiguration, dap);
return {};
});
dap.on('pause', async () => {
return {};
@ -211,6 +199,14 @@ export class Binder implements IDisposable {
await delay(0); // next task so that we're sure terminated() sent
}
/**
* Boots the binder with the given API. Used for the dapDebugServer where
* the launch/attach is intercepted before the binder is created.
*/
public async boot(params: AnyResolvingConfiguration, dap: Dap.Api) {
return this._boot(applyDefaults(params, this._rootServices.get(ExtensionLocation)), dap);
}
private async _boot(params: AnyLaunchConfiguration, dap: Dap.Api) {
warnNightly(dap);
this.reportBootTelemetry(params);
@ -404,18 +400,22 @@ export class Binder implements IDisposable {
threadData.resolve({ thread, debugAdapter });
return {};
};
// default disconnect/terminate/restart handlers that can be overridden
// by the delegate in initAdapter()
dap.on('disconnect', () => this.detachTarget(target, container));
dap.on('terminate', () => this.stopTarget(target, container));
dap.on('restart', async () => {
if (target.canRestart()) target.restart();
else await this._restart();
return {};
});
if (await this._delegate.initAdapter(debugAdapter, target, launcher)) {
startThread();
} else {
dap.on('attach', startThread);
dap.on('launch', startThread);
dap.on('disconnect', () => this.detachTarget(target, container));
dap.on('terminate', () => this.stopTarget(target, container));
dap.on('restart', async () => {
if (target.canRestart()) target.restart();
else await this._restart();
return {};
});
}
await target.afterBind();

Просмотреть файл

@ -0,0 +1,21 @@
const cp = require('child_process');
const path = require('path');
const packageJson = require('../../package.json');
const targetDirectory = process.argv[2];
require('fs').mkdirSync(targetDirectory, { recursive: true });
cp.spawnSync(
'tar',
[
'-czvf',
`${targetDirectory}/js-debug-dap-v${packageJson.version}.tar.gz`,
'dist',
'--transform',
's/^dist/js-debug/',
],
{
stdio: 'inherit',
cwd: path.resolve(__dirname, '../..'),
},
);

Просмотреть файл

@ -2,9 +2,9 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { format, Options as PrettierOptions } from 'prettier';
import * as path from 'path';
import { promises as fs } from 'fs';
import * as path from 'path';
import { format, Options as PrettierOptions } from 'prettier';
import { prettier as defaultPrettierOpts } from '../../package.json';
export function writeCodeToFile(
@ -12,7 +12,7 @@ export function writeCodeToFile(
relativeFilePath: string,
prettierOpts: PrettierOptions = {},
) {
const fileName = path.join(__dirname, '..', '..', '..', relativeFilePath);
const fileName = path.join(__dirname, '..', '..', relativeFilePath);
return fs.writeFile(
fileName,
format(code, {

817
src/cdp/api.d.ts поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

24
src/cdp/telemetryClassification.d.ts поставляемый
Просмотреть файл

@ -76,6 +76,10 @@ interface ICDPOperationClassification {
'debugger.scriptparsed': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!debugger.scriptparsed.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
// Domain: DeviceAccess
'deviceaccess.devicerequestprompted': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!deviceaccess.devicerequestprompted.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
// Domain: DeviceOrientation
// Domain: DOM
@ -132,6 +136,10 @@ interface ICDPOperationClassification {
// Domain: EventBreakpoints
// Domain: FedCm
'fedcm.dialogshown': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!fedcm.dialogshown.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
// Domain: Fetch
'fetch.requestpaused': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!fetch.requestpaused.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
@ -325,8 +333,6 @@ interface ICDPOperationClassification {
'!page.lifecycleevent.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'page.backforwardcachenotused': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!page.backforwardcachenotused.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'page.prerenderattemptcompleted': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!page.prerenderattemptcompleted.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'page.loadeventfired': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!page.loadeventfired.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'page.navigatedwithindocument': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
@ -348,6 +354,20 @@ interface ICDPOperationClassification {
'performancetimeline.timelineeventadded': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!performancetimeline.timelineeventadded.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
// Domain: Preload
'preload.rulesetupdated': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!preload.rulesetupdated.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'preload.rulesetremoved': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!preload.rulesetremoved.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'preload.prerenderattemptcompleted': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!preload.prerenderattemptcompleted.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'preload.prefetchstatusupdated': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!preload.prefetchstatusupdated.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'preload.prerenderstatusupdated': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!preload.prerenderstatusupdated.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
'preload.preloadingattemptsourcesupdated': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!preload.preloadingattemptsourcesupdated.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
// Domain: Profiler
'profiler.consoleprofilefinished': { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
'!profiler.consoleprofilefinished.errors': { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };

4
src/dap/api.d.ts поставляемый
Просмотреть файл

@ -3999,7 +3999,7 @@ export namespace Dap {
endColumn?: integer;
/**
* Indicates whether this frame can be restarted with the `restart` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartRequest` is true.
* Indicates whether this frame can be restarted with the `restart` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartRequest` is true. If a debug adapter has this capability, then `canRestart` defaults to `true` if the property is absent.
*/
canRestart?: boolean;
@ -4230,6 +4230,7 @@ export namespace Dap {
* The expression that controls how many hits of the breakpoint are ignored.
* The debug adapter is expected to interpret the expression as needed.
* The attribute is only honored by a debug adapter if the corresponding capability `supportsHitConditionalBreakpoints` is true.
* If both this property and `condition` are specified, `hitCondition` should be evaluated only if the `condition` is met, and the debug adapter should stop only if both conditions are met.
*/
hitCondition?: string;
@ -4237,6 +4238,7 @@ export namespace Dap {
* If this attribute exists and is non-empty, the debug adapter must not 'break' (stop)
* but log the message instead. Expressions within `{}` are interpolated.
* The attribute is only honored by a debug adapter if the corresponding capability `supportsLogPoints` is true.
* If either `hitCondition` or `condition` is specified, then the message should only be logged if those conditions are met.
*/
logMessage?: string;
}

294
src/dapDebugServer.ts Normal file
Просмотреть файл

@ -0,0 +1,294 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as l10n from '@vscode/l10n';
import * as fs from 'fs';
import { Container } from 'inversify';
import * as net from 'net';
import * as os from 'os';
import * as path from 'path';
import 'reflect-metadata';
import { DebugAdapter } from './adapter/debugAdapter';
import { Binder, IBinderDelegate } from './binder';
import { ILogger } from './common/logging';
import { ProxyLogger } from './common/logging/proxyLogger';
import { getDeferred, IDeferred } from './common/promiseUtil';
import { AnyResolvingConfiguration } from './configuration';
import Dap from './dap/api';
import DapConnection from './dap/connection';
import { StreamDapTransport } from './dap/transport';
import { createGlobalContainer, createTopLevelSessionContainer } from './ioc';
import { IInitializeParams } from './ioc-extras';
import { TargetOrigin } from './targets/targetOrigin';
import { ITarget } from './targets/targets';
if (process.env.L10N_FSPATH_TO_BUNDLE) {
l10n.config({ fsPath: process.env.L10N_FSPATH_TO_BUNDLE });
}
const storagePath = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-js-debug-'));
interface IInitializationCollection {
setExceptionBreakpointsParams?: Dap.SetExceptionBreakpointsParams;
setBreakpointsParams: { params: Dap.SetBreakpointsParams; ids: number[] }[];
customBreakpoints: Set<string>;
initializeParams: Dap.InitializeParams;
launchParams: AnyResolvingConfiguration;
/** Promise that should be resolved when the launch is finished */
deferred: IDeferred<Dap.LaunchResult | Dap.AttachResult>;
}
/**
* 'Collects' DAP calls made until the launch/attach request comes in, and
* returns a promise for the DA to resolve when the launch is processed.
*
* This is needed since until the 'launch' comes in, we don't know what session
* the incoming connection refers to.
*/
function collectInitialize(dap: Dap.Api) {
let setExceptionBreakpointsParams: Dap.SetExceptionBreakpointsParams | undefined;
const setBreakpointsParams: { params: Dap.SetBreakpointsParams; ids: number[] }[] = [];
const customBreakpoints = new Set<string>();
const configurationDone = getDeferred<void>();
let lastBreakpointId = 0;
let initializeParams: Dap.InitializeParams;
dap.on('setBreakpoints', async params => {
const ids = params.breakpoints?.map(() => ++lastBreakpointId) ?? [];
setBreakpointsParams.push({ params, ids });
const breakpoints = ids.map(id => ({
id,
verified: false,
message: l10n.t('breakpoint.provisionalBreakpoint', `Unbound breakpoint`),
})); // TODO: Put a useful message here
return { breakpoints };
});
dap.on('setExceptionBreakpoints', async params => {
setExceptionBreakpointsParams = params;
return {};
});
dap.on('enableCustomBreakpoints', async params => {
for (const id of params.ids) customBreakpoints.add(id);
return {};
});
dap.on('disableCustomBreakpoints', async params => {
for (const id of params.ids) customBreakpoints.delete(id);
return {};
});
dap.on('configurationDone', async () => {
configurationDone.resolve();
return {};
});
dap.on('threads', async () => {
return { threads: [] };
});
dap.on('loadedSources', async () => {
return { sources: [] };
});
dap.on('initialize', async params => {
initializeParams = params;
setTimeout(() => dap.initialized({}), 0);
return DebugAdapter.capabilities();
});
return new Promise<IInitializationCollection>(resolve => {
const handle = async (
launchParams: Dap.LaunchParams | Dap.AttachParams,
): Promise<Dap.LaunchResult | Dap.AttachResult> => {
// By spec, clients should not call launch until after ConfigurationDone...
// but VS Code doesn't actually do this, and breakpoints aren't sent
// until ConfigurationDone happens, so make sure to wait on it.
await configurationDone.promise;
const deferred = getDeferred<Dap.LaunchResult | Dap.AttachResult>();
if (!initializeParams) {
throw new Error(`cannot call launch/attach before initialize`);
}
resolve({
initializeParams,
setExceptionBreakpointsParams,
setBreakpointsParams,
customBreakpoints,
launchParams: launchParams as AnyResolvingConfiguration,
deferred,
});
return deferred.promise;
};
dap.on('launch', p => handle(p));
dap.on('attach', handle);
});
}
interface ISessionInfo extends IInitializationCollection {
connection: DapConnection;
}
class DapSessionManager implements IBinderDelegate {
private readonly sessions = new Map<string, IDeferred<ISessionInfo>>();
constructor(private readonly dapRoot: Dap.Api, public readonly services: Container) {}
/** @inheritdoc */
public async acquireDap(target: ITarget): Promise<DapConnection> {
const existing = this.sessions.get(target.id());
if (existing) {
const { connection } = await existing.promise;
return connection;
}
const parent = target.parent();
let dap: Dap.Api;
if (parent) {
const parentCnx = this.sessions.get(parent.id())?.settledValue;
if (!parentCnx) {
throw new Error('Expected parent session to have a settled value');
}
dap = await parentCnx.connection.dap();
} else {
dap = this.dapRoot;
}
const deferred = getDeferred<ISessionInfo>();
this.sessions.set(target.id(), deferred);
// don't await on this, otherwise we deadlock since the promise may not
// resolve until launch is finished, which requires returning from this method
dap
.startDebuggingRequest({
request: target.launchConfig.request,
configuration: {
type: target.launchConfig.type,
name: target.name(),
__pendingTargetId: target.id(),
},
})
.catch(e => deferred.reject(e));
return deferred.promise.then(d => d.connection);
}
/** @inheritdoc */
public async initAdapter(adapter: DebugAdapter, target: ITarget): Promise<boolean> {
const init = this.sessions.get(target.id())?.settledValue;
if (!init) {
throw new Error(`Expected to find pending init for target ${target.id()}`);
}
if (init.setExceptionBreakpointsParams)
await adapter.setExceptionBreakpoints(init.setExceptionBreakpointsParams);
for (const { params, ids } of init.setBreakpointsParams)
await adapter.breakpointManager.setBreakpoints(params, ids);
await adapter.enableCustomBreakpoints({ ids: Array.from(init.customBreakpoints) });
await adapter.onInitialize(init.initializeParams);
await adapter.launchBlocker();
init.deferred.resolve({});
return true;
}
/** @inheritdoc */
public releaseDap(target: ITarget): void {
this.sessions.delete(target.id());
}
/** Gets whether the manager is waiting for a target of the given ID */
public hasPendingTarget(targetId: string) {
return this.sessions.get(targetId)?.hasSettled() === false;
}
/** Processes an incoming connection. */
public handleConnection(info: ISessionInfo) {
if (!('__pendingTargetId' in info.launchParams) || !info.launchParams.__pendingTargetId) {
throw new Error(`Incoming session is missing __pendingTargetId`);
}
const targetId = info.launchParams.__pendingTargetId;
const session = this.sessions.get(targetId);
if (!session) {
throw new Error(`__pendingTargetId ${targetId} not found`);
}
session.resolve(info);
}
}
function startDebugServer(options: net.ListenOptions) {
const services = createGlobalContainer({ storagePath, isVsCode: false });
const managers = new Set<DapSessionManager>();
const server = net
.createServer(async socket => {
try {
const logger = new ProxyLogger();
const transport = new StreamDapTransport(socket, socket, logger);
const connection = new DapConnection(transport, logger);
const dap = await connection.dap();
const initialized = await collectInitialize(dap);
if ('__pendingTargetId' in initialized.launchParams) {
const ptId = initialized.launchParams.__pendingTargetId;
const manager = ptId && [...managers].find(m => m.hasPendingTarget(ptId));
if (!manager) {
throw new Error(`Cannot find pending target for ${ptId}`);
}
logger.connectTo(manager.services.get(ILogger));
manager.handleConnection({ ...initialized, connection });
} else {
const sessionServices = createTopLevelSessionContainer(services);
const manager = new DapSessionManager(dap, sessionServices);
managers.add(manager);
sessionServices.bind(IInitializeParams).toConstantValue(initialized.initializeParams);
logger.connectTo(sessionServices.get(ILogger));
const binder = new Binder(
manager,
connection,
sessionServices,
new TargetOrigin('targetOrigin'),
);
transport.closed(() => {
binder.dispose();
managers.delete(manager);
});
initialized.deferred.resolve(await binder.boot(initialized.launchParams, dap));
}
} catch (e) {
console.error(e);
return socket.destroy();
}
})
.on('error', err => {
console.error(err);
process.exit(1);
})
.listen(options, () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const addr = server.address()!;
console.log(
`Debug server listening at ${
typeof addr === 'string' ? addr : `${addr.address}:${addr.port}`
}`,
);
});
}
const [, argv1, portOrSocket = '8123', host = 'localhost'] = process.argv;
if (process.argv.includes('--help')) {
console.log(`Usage: ${path.basename(argv1)} [port|socket path=8123] [host=localhost]`);
} else if (!isNaN(Number(portOrSocket))) {
startDebugServer({ port: Number(portOrSocket), host });
} else {
startDebugServer({ path: portOrSocket });
}

Просмотреть файл

@ -4,6 +4,7 @@
import { Container } from 'inversify';
import { DebugConfiguration } from 'vscode';
import { DebugAdapter } from './adapter/debugAdapter';
import { Binder, IBinderDelegate } from './binder';
import { DebugType } from './common/contributionUtils';
import { DisposableList } from './common/disposable';
@ -15,7 +16,7 @@ import { IDapTransport } from './dap/transport';
import { createTopLevelSessionContainer } from './ioc';
import { SessionSubStates } from './ioc-extras';
import { TargetOrigin } from './targets/targetOrigin';
import { ITarget } from './targets/targets';
import { ILauncher, ITarget } from './targets/targets';
import { ITelemetryReporter } from './telemetry/telemetryReporter';
/**
@ -45,13 +46,17 @@ export class Session<TSessionImpl extends IDebugSessionLike> implements IDisposa
constructor(
public readonly debugSession: TSessionImpl,
transport: IDapTransport,
transport: IDapTransport | DapConnection,
public readonly logger: ILogger,
public readonly sessionStates: SessionSubStates,
private readonly parent?: Session<TSessionImpl>,
) {
transport.setLogger(logger);
this.connection = new DapConnection(transport, this.logger);
if (transport instanceof DapConnection) {
this.connection = transport;
} else {
transport.setLogger(logger);
this.connection = new DapConnection(transport, this.logger);
}
}
listenToTarget(target: ITarget) {
@ -88,13 +93,18 @@ export class RootSession<TSessionImpl extends IDebugSessionLike> extends Session
constructor(
public readonly debugSession: TSessionImpl,
transport: IDapTransport,
transport: IDapTransport | DapConnection,
private readonly services: Container,
) {
super(debugSession, transport, services.get(ILogger), services.get(SessionSubStates));
this.connection.attachTelemetry(services.get(ITelemetryReporter));
}
get binder() {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this._binder!;
}
createBinder(delegate: IBinderDelegate) {
this._binder = new Binder(
delegate,
@ -134,7 +144,17 @@ export class SessionManager<TSessionImpl extends IDebugSessionLike>
if (session) session.dispose();
}
public createNewRootSession(debugSession: TSessionImpl, transport: IDapTransport) {
/**
* Gets whether the pending target ID exists.
*/
public hasPendingTargetId(targetId: string) {
return this._pendingTarget.has(targetId);
}
public createNewRootSession(
debugSession: TSessionImpl,
transport: IDapTransport | DapConnection,
) {
const root = new RootSession(
debugSession,
transport,
@ -151,7 +171,7 @@ export class SessionManager<TSessionImpl extends IDebugSessionLike>
public createNewChildSession(
debugSession: TSessionImpl,
pendingTargetId: string,
transport: IDapTransport,
transport: IDapTransport | DapConnection,
): Session<TSessionImpl> {
const pending = this._pendingTarget.get(pendingTargetId);
if (!pending) {
@ -235,7 +255,11 @@ export class SessionManager<TSessionImpl extends IDebugSessionLike>
/**
* @inheritdoc
*/
public initAdapter(): Promise<boolean> {
public initAdapter(
_debugAdapter: DebugAdapter,
_target: ITarget,
_launcher: ILauncher,
): Promise<boolean> {
return Promise.resolve(false);
}

34105
src/typings/vscode.d.ts поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -60,6 +60,11 @@ export class VSCodeSessionManager implements vscode.DebugAdapterDescriptorFactor
public async createDebugAdapterDescriptor(
debugSession: vscode.DebugSession,
): Promise<vscode.DebugAdapterDescriptor> {
const useLocal = process.env.JS_DEBUG_USE_LOCAL_DAP_PORT;
if (useLocal) {
return new vscode.DebugAdapterServer(+useLocal);
}
const result = await this.sessionServerManager.createDebugServer(debugSession);
return new vscode.DebugAdapterNamedPipeServer(result.server.address() as string);
}

Просмотреть файл

@ -75,7 +75,7 @@ class VsDebugServer implements ISessionLauncher<VSDebugSession> {
}
}
launchRootFromExisting(
private launchRootFromExisting(
deferredConnection: IDeferred<DapConnection>,
session: VSDebugSession,
inputStream: Readable,
@ -95,7 +95,7 @@ class VsDebugServer implements ISessionLauncher<VSDebugSession> {
console.log((result.server.address() as net.AddressInfo).port.toString());
}
launch(
public launch(
parentSession: Session<VSDebugSession>,
target: ITarget,
config: IPseudoAttachConfiguration,