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:
Родитель
952df18238
Коммит
53cfeec1e9
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
25
gulpfile.js
25
gulpfile.js
|
@ -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, {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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' };
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче