From bf3863a489a8c920b8b4c4be112a014438da0c82 Mon Sep 17 00:00:00 2001 From: thegecko Date: Fri, 17 Jun 2022 17:17:47 +0100 Subject: [PATCH] Support browser debug Signed-off-by: thegecko --- CHANGELOG.md | 4 + .../debug-adapter-contribution-registry.ts | 4 +- .../{node => common}/debug-adapter-session.ts | 2 +- .../debug/src/{node => common}/debug-model.ts | 4 +- .../src/common/inline-debug-adapter.ts} | 2 +- .../debug/src/node/debug-adapter-factory.ts | 4 +- .../src/node/debug-adapter-session-manager.ts | 4 +- .../debug/src/node/debug-backend-module.ts | 4 +- packages/debug/src/node/debug-service-impl.ts | 2 +- .../debug/src/node/stream-debug-adapter.ts | 2 +- .../vscode-debug-adapter-contribution.ts | 2 +- .../src/hosted/browser/worker/debug-stub.ts | 2 +- .../src/hosted/node/plugin-host-rpc.ts | 2 +- .../debug/debug.ts => debug/debug-ext.ts} | 81 +++------ .../debug/plugin-debug-adapter-creator.ts | 50 ++++++ .../debug/plugin-debug-adapter-session.ts | 4 +- .../debug/plugin-debug-adapter-tracker.ts | 0 ...lugin-debug-adapter-executable-resolver.ts | 58 ------ .../debug/plugin-debug-adapter-starter.ts | 86 --------- .../plugin-node-debug-adapter-creator.ts | 167 ++++++++++++++++++ .../plugin-ext/src/plugin/plugin-context.ts | 4 +- 21 files changed, 267 insertions(+), 221 deletions(-) rename packages/debug/src/{node => common}/debug-adapter-contribution-registry.ts (98%) rename packages/debug/src/{node => common}/debug-adapter-session.ts (98%) rename packages/debug/src/{node => common}/debug-model.ts (98%) rename packages/{plugin-ext/src/plugin/node/debug/plugin-inline-debug-adapter.ts => debug/src/common/inline-debug-adapter.ts} (96%) rename packages/plugin-ext/src/plugin/{node/debug/debug.ts => debug/debug-ext.ts} (83%) create mode 100644 packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-creator.ts rename packages/plugin-ext/src/plugin/{node => }/debug/plugin-debug-adapter-session.ts (95%) rename packages/plugin-ext/src/plugin/{node => }/debug/plugin-debug-adapter-tracker.ts (100%) delete mode 100644 packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-executable-resolver.ts delete mode 100644 packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-starter.ts create mode 100644 packages/plugin-ext/src/plugin/node/debug/plugin-node-debug-adapter-creator.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d4a410b1d5c80..09d193a44c4eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - [plugin] Support `TextEditor#show()` and `TextEditor#hide()` [#11168](https://github.com/eclipse-theia/theia/pull/11168) - Contributed on behalf of STMicroelectronics - [core, monaco] refactored theme initialization to occur within application lifecycle rather than at import time. [#11213](https://github.com/eclipse-theia/theia/pull/11213) - [plugin] Support optional property `TaskPresentationOptions#clear` [#11298](https://github.com/eclipse-theia/theia/pull/11298) - Contributed on behalf of STMicroelectronics +- [plugin] added support for debuggers running in the frontend [#10748](https://github.com/eclipse-theia/theia/pull/10748) [Breaking Changes:](#breaking_changes_1.27.0) @@ -60,6 +61,9 @@ - [core] removed `ThemeService.get()`; inject the `ThemeService` instead. Removed `ColorApplicationContribution.initBackground()`; by default the `editor.background` color variable will be initialized through the normal theme initialization process. It is now expected that the `ThemeService` will call `this.deferredInitializer.resolve()` when the `ThemeService` finishes its initialization. Failure to do so in any overrides may cause failures to apply default themes. [#11213](https://github.com/eclipse-theia/theia/pull/11213) - [monaco] removed static methods `init()`, `register()`, `restore(), `updateBodyUiTheme()` from `MonacoThemingService`; use instance methods `initialize()`, `registerParsedTheme()`, `restore()`, `updateBodyUiTheme()` instead. Removed `MonacoThemeRegistry.SINGLETON`, inject `MonacoThemeRegistry` instead. [#11213](https://github.com/eclipse-theia/theia/pull/11213) - [core] double-click no longer maximizes a tab by default - controllable through `workbench.tab.maximize` preference [#11279](https://github.com/eclipse-theia/theia/pull/11279) +- [plugin-ext] method `registerDebuggersContributions` has an additional parameter in the signature `pluginType` to specify `frontend` or `backend` [#10748](https://github.com/eclipse-theia/theia/pull/10748) +- [plugin-ext] file `debug` renamed to `debug-ext` [#10748](https://github.com/eclipse-theia/theia/pull/10748) +- [debug] debug files not unique to the backend have been moved from `node` to `common` [#10748](https://github.com/eclipse-theia/theia/pull/10748) ## v1.26.0 - 5/26/2022 diff --git a/packages/debug/src/node/debug-adapter-contribution-registry.ts b/packages/debug/src/common/debug-adapter-contribution-registry.ts similarity index 98% rename from packages/debug/src/node/debug-adapter-contribution-registry.ts rename to packages/debug/src/common/debug-adapter-contribution-registry.ts index a26141570e87b..4bf95d904aa0b 100644 --- a/packages/debug/src/node/debug-adapter-contribution-registry.ts +++ b/packages/debug/src/common/debug-adapter-contribution-registry.ts @@ -16,8 +16,8 @@ import { injectable, inject, named } from '@theia/core/shared/inversify'; import { ContributionProvider } from '@theia/core'; -import { DebugConfiguration } from '../common/debug-configuration'; -import { DebuggerDescription, DebugError } from '../common/debug-service'; +import { DebugConfiguration } from './debug-configuration'; +import { DebuggerDescription, DebugError } from './debug-service'; import { DebugAdapterContribution, DebugAdapterExecutable, DebugAdapterSessionFactory } from './debug-model'; import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; diff --git a/packages/debug/src/node/debug-adapter-session.ts b/packages/debug/src/common/debug-adapter-session.ts similarity index 98% rename from packages/debug/src/node/debug-adapter-session.ts rename to packages/debug/src/common/debug-adapter-session.ts index eeabaccee1f43..c0c6b378e77ad 100644 --- a/packages/debug/src/node/debug-adapter-session.ts +++ b/packages/debug/src/common/debug-adapter-session.ts @@ -26,7 +26,7 @@ import { DebugAdapterSession } from './debug-model'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { DebugChannel } from '../common/debug-service'; +import { DebugChannel } from './debug-service'; /** * [DebugAdapterSession](#DebugAdapterSession) implementation. diff --git a/packages/debug/src/node/debug-model.ts b/packages/debug/src/common/debug-model.ts similarity index 98% rename from packages/debug/src/node/debug-model.ts rename to packages/debug/src/common/debug-model.ts index e6cf218ed9f7c..0f23d5d544fc5 100644 --- a/packages/debug/src/node/debug-model.ts +++ b/packages/debug/src/common/debug-model.ts @@ -22,11 +22,11 @@ // Some entities copied and modified from https://github.com/Microsoft/vscode/blob/master/src/vs/vscode.d.ts // Some entities copied and modified from https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/parts/debug/common/debug.ts -import { DebugConfiguration } from '../common/debug-configuration'; +import { DebugConfiguration } from './debug-configuration'; import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import { MaybePromise } from '@theia/core/lib/common/types'; import { Event } from '@theia/core'; -import { DebugChannel } from '../common/debug-service'; +import { DebugChannel } from './debug-service'; // FIXME: break down this file to debug adapter and debug adapter contribution (see Theia file naming conventions) diff --git a/packages/plugin-ext/src/plugin/node/debug/plugin-inline-debug-adapter.ts b/packages/debug/src/common/inline-debug-adapter.ts similarity index 96% rename from packages/plugin-ext/src/plugin/node/debug/plugin-inline-debug-adapter.ts rename to packages/debug/src/common/inline-debug-adapter.ts index 8107bba6b464f..486917e2d2e65 100644 --- a/packages/plugin-ext/src/plugin/node/debug/plugin-inline-debug-adapter.ts +++ b/packages/debug/src/common/inline-debug-adapter.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { Emitter, Event } from '@theia/core/lib/common/event'; -import { DebugAdapter } from '@theia/debug/lib/node/debug-model'; +import { DebugAdapter } from './debug-model'; import * as theia from '@theia/plugin'; /** diff --git a/packages/debug/src/node/debug-adapter-factory.ts b/packages/debug/src/node/debug-adapter-factory.ts index 579318a76cde3..cf0792d1233c2 100644 --- a/packages/debug/src/node/debug-adapter-factory.ts +++ b/packages/debug/src/node/debug-adapter-factory.ts @@ -37,8 +37,8 @@ import { DebugAdapterFactory, DebugAdapterForkExecutable, DebugAdapter -} from './debug-model'; -import { DebugAdapterSessionImpl } from './debug-adapter-session'; +} from '../common/debug-model'; +import { DebugAdapterSessionImpl } from '../common/debug-adapter-session'; import { environment } from '@theia/core/shared/@theia/application-package'; import { ProcessDebugAdapter, SocketDebugAdapter } from './stream-debug-adapter'; diff --git a/packages/debug/src/node/debug-adapter-session-manager.ts b/packages/debug/src/node/debug-adapter-session-manager.ts index cf4f013fb381e..944058741389a 100644 --- a/packages/debug/src/node/debug-adapter-session-manager.ts +++ b/packages/debug/src/node/debug-adapter-session-manager.ts @@ -20,8 +20,8 @@ import { MessagingService } from '@theia/core/lib/node/messaging/messaging-servi import { DebugAdapterPath, ForwardingDebugChannel } from '../common/debug-service'; import { DebugConfiguration } from '../common/debug-configuration'; -import { DebugAdapterSession, DebugAdapterSessionFactory, DebugAdapterFactory } from './debug-model'; -import { DebugAdapterContributionRegistry } from './debug-adapter-contribution-registry'; +import { DebugAdapterSession, DebugAdapterSessionFactory, DebugAdapterFactory } from '../common/debug-model'; +import { DebugAdapterContributionRegistry } from '../common/debug-adapter-contribution-registry'; /** * Debug adapter session manager. diff --git a/packages/debug/src/node/debug-backend-module.ts b/packages/debug/src/node/debug-backend-module.ts index e9c99c07b2b06..32e90d0bf68dd 100644 --- a/packages/debug/src/node/debug-backend-module.ts +++ b/packages/debug/src/node/debug-backend-module.ts @@ -30,9 +30,9 @@ import { DebugAdapterContribution, DebugAdapterSessionFactory, DebugAdapterFactory -} from './debug-model'; +} from '../common/debug-model'; import { DebugServiceImpl } from './debug-service-impl'; -import { DebugAdapterContributionRegistry } from './debug-adapter-contribution-registry'; +import { DebugAdapterContributionRegistry } from '../common/debug-adapter-contribution-registry'; import { DebugAdapterSessionManager } from './debug-adapter-session-manager'; const debugConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => { diff --git a/packages/debug/src/node/debug-service-impl.ts b/packages/debug/src/node/debug-service-impl.ts index ae7c8d51f585a..c4304390b1947 100644 --- a/packages/debug/src/node/debug-service-impl.ts +++ b/packages/debug/src/node/debug-service-impl.ts @@ -20,7 +20,7 @@ import { DebugService, DebuggerDescription } from '../common/debug-service'; import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import { CommandIdVariables } from '@theia/variable-resolver/lib/common/variable-types'; import { DebugAdapterSessionManager } from './debug-adapter-session-manager'; -import { DebugAdapterContributionRegistry } from './debug-adapter-contribution-registry'; +import { DebugAdapterContributionRegistry } from '../common/debug-adapter-contribution-registry'; import { Event } from '@theia/core'; /** diff --git a/packages/debug/src/node/stream-debug-adapter.ts b/packages/debug/src/node/stream-debug-adapter.ts index 6d127013351d7..34775f3c909ea 100644 --- a/packages/debug/src/node/stream-debug-adapter.ts +++ b/packages/debug/src/node/stream-debug-adapter.ts @@ -19,7 +19,7 @@ import { Emitter, Event } from '@theia/core/lib/common/event'; import { ChildProcess } from 'child_process'; import * as stream from 'stream'; import * as net from 'net'; -import { DebugAdapter } from './debug-model'; +import { DebugAdapter } from '../common/debug-model'; abstract class StreamDebugAdapter extends DisposableCollection { private messageReceivedEmitter = new Emitter(); diff --git a/packages/debug/src/node/vscode/vscode-debug-adapter-contribution.ts b/packages/debug/src/node/vscode/vscode-debug-adapter-contribution.ts index fa2be87a6136e..1d3edafe71f5b 100644 --- a/packages/debug/src/node/vscode/vscode-debug-adapter-contribution.ts +++ b/packages/debug/src/node/vscode/vscode-debug-adapter-contribution.ts @@ -18,7 +18,7 @@ import * as fs from '@theia/core/shared/fs-extra'; import * as path from 'path'; -import { DebugAdapterExecutable, DebugAdapterContribution } from '../debug-model'; +import { DebugAdapterExecutable, DebugAdapterContribution } from '../../common/debug-model'; import { isWindows, isOSX } from '@theia/core/lib/common/os'; import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import { deepClone } from '@theia/core/lib/common/objects'; diff --git a/packages/plugin-ext/src/hosted/browser/worker/debug-stub.ts b/packages/plugin-ext/src/hosted/browser/worker/debug-stub.ts index 5a633e9aa3cba..69bdf1b636c8b 100644 --- a/packages/plugin-ext/src/hosted/browser/worker/debug-stub.ts +++ b/packages/plugin-ext/src/hosted/browser/worker/debug-stub.ts @@ -15,7 +15,7 @@ // ***************************************************************************** // eslint-disable-next-line @theia/runtime-import-check -import { DebugExtImpl } from '../../../plugin/node/debug/debug'; +import { DebugExtImpl } from '../../../plugin/debug/debug-ext'; import { RPCProtocol } from '../../../common/rpc-protocol'; /* eslint-disable @typescript-eslint/no-explicit-any */ diff --git a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts index 87634f5434702..95581f0c53738 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts @@ -22,7 +22,7 @@ import { createAPIFactory } from '../../plugin/plugin-context'; import { EnvExtImpl } from '../../plugin/env'; import { PreferenceRegistryExtImpl } from '../../plugin/preference-registry'; import { ExtPluginApi, ExtPluginApiBackendInitializationFn } from '../../common/plugin-ext-api-contribution'; -import { DebugExtImpl } from '../../plugin/node/debug/debug'; +import { DebugExtImpl } from '../../plugin/debug/debug-ext'; import { EditorsAndDocumentsExtImpl } from '../../plugin/editors-and-documents'; import { WorkspaceExtImpl } from '../../plugin/workspace'; import { MessageRegistryExt } from '../../plugin/message-registry'; diff --git a/packages/plugin-ext/src/plugin/node/debug/debug.ts b/packages/plugin-ext/src/plugin/debug/debug-ext.ts similarity index 83% rename from packages/plugin-ext/src/plugin/node/debug/debug.ts rename to packages/plugin-ext/src/plugin/debug/debug-ext.ts index 5bd7b17c30ecb..4c884dbffcd2a 100644 --- a/packages/plugin-ext/src/plugin/node/debug/debug.ts +++ b/packages/plugin-ext/src/plugin/debug/debug-ext.ts @@ -17,22 +17,19 @@ import { Emitter } from '@theia/core/lib/common/event'; import { Path } from '@theia/core/lib/common/path'; import * as theia from '@theia/plugin'; import { URI } from '@theia/core/shared/vscode-uri'; -import { Breakpoint } from '../../../common/plugin-api-rpc-model'; -import { DebugConfigurationProviderTriggerKind, DebugExt, DebugMain, PLUGIN_RPC_CONTEXT as Ext, TerminalOptionsExt } from '../../../common/plugin-api-rpc'; -import { PluginPackageDebuggersContribution } from '../../../common/plugin-protocol'; -import { RPCProtocol } from '../../../common/rpc-protocol'; -import { CommandRegistryImpl } from '../../command-registry'; -import { ConnectionImpl } from '../../../common/connection'; -import { - Disposable, Breakpoint as BreakpointExt, SourceBreakpoint, FunctionBreakpoint, Location, Range, - DebugAdapterServer, DebugAdapterExecutable, DebugAdapterNamedPipeServer, DebugAdapterInlineImplementation -} from '../../types-impl'; -import { resolveDebugAdapterExecutable } from './plugin-debug-adapter-executable-resolver'; +import { Breakpoint } from '../../common/plugin-api-rpc-model'; +import { DebugConfigurationProviderTriggerKind, DebugExt, DebugMain, PLUGIN_RPC_CONTEXT as Ext, TerminalOptionsExt } from '../../common/plugin-api-rpc'; +import { PluginPackageDebuggersContribution } from '../../common/plugin-protocol'; +import { RPCProtocol } from '../../common/rpc-protocol'; +import { CommandRegistryImpl } from '../command-registry'; +import { ConnectionImpl } from '../../common/connection'; +import { Disposable, Breakpoint as BreakpointExt, SourceBreakpoint, FunctionBreakpoint, Location, Range } from '../types-impl'; import { PluginDebugAdapterSession } from './plugin-debug-adapter-session'; -import { connectInlineDebugAdapter, connectPipeDebugAdapter, connectSocketDebugAdapter, startDebugAdapter } from './plugin-debug-adapter-starter'; import { PluginDebugAdapterTracker } from './plugin-debug-adapter-tracker'; import uuid = require('uuid'); -import { DebugAdapter } from '@theia/debug/lib/node/debug-model'; +import { DebugAdapter } from '@theia/debug/lib/common/debug-model'; +import { PluginDebugAdapterCreator } from './plugin-debug-adapter-creator'; +import { NodeDebugAdapterCreator } from '../node/debug/plugin-node-debug-adapter-creator'; interface ConfigurationProviderRecord { handle: number; @@ -42,10 +39,7 @@ interface ConfigurationProviderRecord { } /* eslint-disable @typescript-eslint/no-explicit-any */ -// TODO: rename file to `debug-ext.ts` -/** - * It is supposed to work at node only. - */ + export class DebugExtImpl implements DebugExt { // debug sessions by sessionId private sessions = new Map(); @@ -60,6 +54,7 @@ export class DebugExtImpl implements DebugExt { private descriptorFactories = new Map(); private trackerFactories: [string, theia.DebugAdapterTrackerFactory][] = []; private contributionPaths = new Map(); + private contributionTypes = new Map(); private connectionExt: ConnectionImpl; private commandRegistryExt: CommandRegistryImpl; @@ -77,6 +72,9 @@ export class DebugExtImpl implements DebugExt { private readonly _breakpoints = new Map(); + private frontendAdapterCreator = new PluginDebugAdapterCreator(); + private backendAdapterCreator = new NodeDebugAdapterCreator(); + get breakpoints(): theia.Breakpoint[] { return [...this._breakpoints.values()]; } @@ -102,11 +100,13 @@ export class DebugExtImpl implements DebugExt { /** * Registers contributions. * @param pluginFolder plugin folder path + * @param pluginType plugin type * @param contributions available debuggers contributions */ - registerDebuggersContributions(pluginFolder: string, contributions: PluginPackageDebuggersContribution[]): void { + registerDebuggersContributions(pluginFolder: string, pluginType: theia.PluginType, contributions: PluginPackageDebuggersContribution[]): void { contributions.forEach(contribution => { this.contributionPaths.set(contribution.type, pluginFolder); + this.contributionTypes.set(contribution.type, pluginType); this.debuggersContributions.set(contribution.type, contribution); this.proxy.$registerDebuggerContribution({ type: contribution.type, @@ -397,43 +397,7 @@ export class DebugExtImpl implements DebugExt { protected async createDebugAdapter(session: theia.DebugSession, debugConfiguration: theia.DebugConfiguration): Promise { const executable = await this.resolveDebugAdapterExecutable(debugConfiguration); const descriptorFactory = this.descriptorFactories.get(session.type); - if (descriptorFactory) { - // 'createDebugAdapterDescriptor' 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 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: - // createDebugAdapter(session: DebugSession, executable: DebugAdapterExecutable) { - // if (typeof session.configuration.debugServer === 'number') { - // return new DebugAdapterServer(session.configuration.debugServer); - // } - // 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). - const descriptor = await descriptorFactory.createDebugAdapterDescriptor(session, executable); - if (descriptor) { - if (DebugAdapterServer.is(descriptor)) { - return connectSocketDebugAdapter(descriptor); - } else if (DebugAdapterExecutable.is(descriptor)) { - return startDebugAdapter(descriptor); - } else if (DebugAdapterNamedPipeServer.is(descriptor)) { - return connectPipeDebugAdapter(descriptor); - } else if (DebugAdapterInlineImplementation.is(descriptor)) { - return connectInlineDebugAdapter(descriptor); - } - } - } - - if ('debugServer' in debugConfiguration) { - return connectSocketDebugAdapter({ port: debugConfiguration.debugServer }); - } else { - if (!executable) { - throw new Error('It is not possible to provide debug adapter executable.'); - } - return startDebugAdapter(executable); - } + return this.getAdapterCreator(debugConfiguration).createDebugAdapter(session, debugConfiguration, executable, descriptorFactory); } protected async resolveDebugAdapterExecutable(debugConfiguration: theia.DebugConfiguration): Promise { @@ -448,7 +412,7 @@ export class DebugExtImpl implements DebugExt { } else { const contributionPath = this.contributionPaths.get(type); if (contributionPath) { - return resolveDebugAdapterExecutable(contributionPath, contribution); + return this.getAdapterCreator(debugConfiguration).resolveDebugAdapterExecutable(contributionPath, contribution); } } } @@ -469,4 +433,9 @@ export class DebugExtImpl implements DebugExt { index: 0 }; } + + private getAdapterCreator(debugConfiguration: theia.DebugConfiguration): PluginDebugAdapterCreator { + const pluginType = this.contributionTypes.get(debugConfiguration.type); + return pluginType === 'frontend' ? this.frontendAdapterCreator : this.backendAdapterCreator; + } } diff --git a/packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-creator.ts b/packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-creator.ts new file mode 100644 index 0000000000000..b5c6f450bee76 --- /dev/null +++ b/packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-creator.ts @@ -0,0 +1,50 @@ +// ***************************************************************************** +// Copyright (C) 2022 Arm and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +// ***************************************************************************** + +import * as theia from '@theia/plugin'; +import { DebugAdapter } from '@theia/debug/lib/common/debug-model'; +import { PluginPackageDebuggersContribution } from '../../common'; +import { DebugAdapterInlineImplementation } from '../types-impl'; +import { InlineDebugAdapter } from '@theia/debug/lib/common/inline-debug-adapter'; + +export class PluginDebugAdapterCreator { + public async resolveDebugAdapterExecutable(_pluginPath: string, _debuggerContribution: PluginPackageDebuggersContribution): Promise { + // Node is required to run the default executable + return undefined; + } + + public async createDebugAdapter( + session: theia.DebugSession, + _debugConfiguration: theia.DebugConfiguration, + executable: theia.DebugAdapterExecutable | undefined, + descriptorFactory: theia.DebugAdapterDescriptorFactory | undefined + ): Promise { + if (descriptorFactory) { + const descriptor = await descriptorFactory.createDebugAdapterDescriptor(session, executable); + if (descriptor) { + if (DebugAdapterInlineImplementation.is(descriptor)) { + return this.connectInlineDebugAdapter(descriptor); + } + } + } + + throw new Error('It is not possible to provide debug adapter executable.'); + } + + public connectInlineDebugAdapter(adapter: DebugAdapterInlineImplementation): InlineDebugAdapter { + return new InlineDebugAdapter(adapter.implementation); + } +} diff --git a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-session.ts b/packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-session.ts similarity index 95% rename from packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-session.ts rename to packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-session.ts index 44afd9dfc7abe..e1a498183f451 100644 --- a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-session.ts +++ b/packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-session.ts @@ -14,9 +14,9 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -import { DebugAdapterSessionImpl } from '@theia/debug/lib/node/debug-adapter-session'; +import { DebugAdapterSessionImpl } from '@theia/debug/lib/common/debug-adapter-session'; import * as theia from '@theia/plugin'; -import { DebugAdapter } from '@theia/debug/lib/node/debug-model'; +import { DebugAdapter } from '@theia/debug/lib/common/debug-model'; import { DebugChannel } from '@theia/debug/lib/common/debug-service'; /* eslint-disable @typescript-eslint/no-explicit-any */ diff --git a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-tracker.ts b/packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-tracker.ts similarity index 100% rename from packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-tracker.ts rename to packages/plugin-ext/src/plugin/debug/plugin-debug-adapter-tracker.ts diff --git a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-executable-resolver.ts b/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-executable-resolver.ts deleted file mode 100644 index dd56931ce6201..0000000000000 --- a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-executable-resolver.ts +++ /dev/null @@ -1,58 +0,0 @@ -// ***************************************************************************** -// Copyright (C) 2018 Red Hat, Inc. and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0. -// -// This Source Code may also be made available under the following Secondary -// Licenses when the conditions for such availability set forth in the Eclipse -// Public License v. 2.0 are satisfied: GNU General Public License, version 2 -// with the GNU Classpath Exception which is available at -// https://www.gnu.org/software/classpath/license.html. -// -// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 -// ***************************************************************************** - -import * as path from 'path'; -import * as theia from '@theia/plugin'; -import { PlatformSpecificAdapterContribution, PluginPackageDebuggersContribution } from '../../../common'; -import { isWindows, isOSX } from '@theia/core/lib/common/os'; - -/** - * Resolves [DebugAdapterExecutable](#DebugAdapterExecutable) based on contribution. - */ -export async function resolveDebugAdapterExecutable( - pluginPath: string, debuggerContribution: PluginPackageDebuggersContribution): Promise { - const info = toPlatformInfo(debuggerContribution); - let program = (info && info.program || debuggerContribution.program); - if (!program) { - return undefined; - } - program = path.join(pluginPath, program); - const programArgs = info && info.args || debuggerContribution.args || []; - let runtime = info && info.runtime || debuggerContribution.runtime; - if (runtime && runtime.indexOf('./') === 0) { - runtime = path.join(pluginPath, runtime); - } - const runtimeArgs = info && info.runtimeArgs || debuggerContribution.runtimeArgs || []; - const command = runtime ? runtime : program; - const args = runtime ? [...runtimeArgs, program, ...programArgs] : programArgs; - return { - command, - args - }; -} - -function toPlatformInfo(executable: PluginPackageDebuggersContribution): PlatformSpecificAdapterContribution | undefined { - if (isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) { - return executable.winx86 || executable.win || executable.windows; - } - if (isWindows) { - return executable.win || executable.windows; - } - if (isOSX) { - return executable.osx; - } - return executable.linux; -} diff --git a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-starter.ts b/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-starter.ts deleted file mode 100644 index 1f58ea0ce92ea..0000000000000 --- a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-starter.ts +++ /dev/null @@ -1,86 +0,0 @@ -// ***************************************************************************** -// Copyright (C) 2018 Red Hat, Inc. and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0. -// -// This Source Code may also be made available under the following Secondary -// Licenses when the conditions for such availability set forth in the Eclipse -// Public License v. 2.0 are satisfied: GNU General Public License, version 2 -// with the GNU Classpath Exception which is available at -// https://www.gnu.org/software/classpath/license.html. -// -// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 -// ***************************************************************************** - -import * as net from 'net'; -import { ChildProcess, spawn, fork, ForkOptions } from 'child_process'; -import { DebugAdapter } from '@theia/debug/lib/node/debug-model'; -import { DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer } from '../../types-impl'; -import { InlineDebugAdapter } from './plugin-inline-debug-adapter'; -import { ProcessDebugAdapter, SocketDebugAdapter } from '@theia/debug/lib/node/stream-debug-adapter'; -const isElectron = require('is-electron'); - -/** - * Starts debug adapter process. - */ -export function startDebugAdapter(executable: DebugAdapterExecutable): DebugAdapter { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const options: any = { stdio: ['pipe', 'pipe', 2] }; - - if (executable.options) { - options.cwd = executable.options.cwd; - - // 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. - options.env = Object.assign({}, process.env); - Object.assign(options.env, executable.options.env); - } - - let childProcess: ChildProcess; - const { command, args } = executable; - if (command === 'node') { - if (Array.isArray(args) && args.length > 0) { - const forkOptions: ForkOptions = { - env: options.env, - // When running in Electron, fork will automatically add ELECTRON_RUN_AS_NODE=1 to the env, - // but this will cause issues when debugging Electron apps, so we'll remove it. - execArgv: isElectron() - ? ['-e', 'delete process.env.ELECTRON_RUN_AS_NODE;require(process.argv[1])'] - : [], - silent: true - }; - if (options.cwd) { - forkOptions.cwd = options.cwd; - } - options.stdio.push('ipc'); - forkOptions.stdio = options.stdio; - childProcess = fork(args[0], args.slice(1), forkOptions); - } else { - throw new Error(`It is not possible to launch debug adapter with the command: ${JSON.stringify(executable)}`); - } - } else { - childProcess = spawn(command, args, options); - } - - return new ProcessDebugAdapter(childProcess); -} - -/** - * Connects to a remote debug server. - */ -export function connectSocketDebugAdapter(server: DebugAdapterServer): SocketDebugAdapter { - const socket = net.createConnection(server.port, server.host); - return new SocketDebugAdapter(socket); -} - -export function connectPipeDebugAdapter(adapter: DebugAdapterNamedPipeServer): SocketDebugAdapter { - const socket = net.createConnection(adapter.path); - return new SocketDebugAdapter(socket); -} - -export function connectInlineDebugAdapter(adapter: DebugAdapterInlineImplementation): InlineDebugAdapter { - return new InlineDebugAdapter(adapter.implementation); -} diff --git a/packages/plugin-ext/src/plugin/node/debug/plugin-node-debug-adapter-creator.ts b/packages/plugin-ext/src/plugin/node/debug/plugin-node-debug-adapter-creator.ts new file mode 100644 index 0000000000000..ffda0f04e4ddd --- /dev/null +++ b/packages/plugin-ext/src/plugin/node/debug/plugin-node-debug-adapter-creator.ts @@ -0,0 +1,167 @@ +// ***************************************************************************** +// Copyright (C) 2022 Arm and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { PluginDebugAdapterCreator } from '../../debug/plugin-debug-adapter-creator'; +import * as path from 'path'; +import * as theia from '@theia/plugin'; +import { PlatformSpecificAdapterContribution, PluginPackageDebuggersContribution } from '../../../common'; +import { isWindows, isOSX } from '@theia/core/lib/common/os'; +import * as net from 'net'; +import { ChildProcess, spawn, fork, ForkOptions } from 'child_process'; +import { DebugAdapter } from '@theia/debug/lib/common/debug-model'; +import { DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer } from '../../types-impl'; +import { ProcessDebugAdapter, SocketDebugAdapter } from '@theia/debug/lib/node/stream-debug-adapter'; +const isElectron = require('is-electron'); + +export class NodeDebugAdapterCreator extends PluginDebugAdapterCreator { + public override async resolveDebugAdapterExecutable( + pluginPath: string, + debuggerContribution: PluginPackageDebuggersContribution + ): Promise { + const info = this.toPlatformInfo(debuggerContribution); + let program = (info && info.program || debuggerContribution.program); + if (!program) { + return undefined; + } + program = path.join(pluginPath, program); + const programArgs = info && info.args || debuggerContribution.args || []; + let runtime = info && info.runtime || debuggerContribution.runtime; + if (runtime && runtime.indexOf('./') === 0) { + runtime = path.join(pluginPath, runtime); + } + const runtimeArgs = info && info.runtimeArgs || debuggerContribution.runtimeArgs || []; + const command = runtime ? runtime : program; + const args = runtime ? [...runtimeArgs, program, ...programArgs] : programArgs; + return { + command, + args + }; + } + + public override async createDebugAdapter( + session: theia.DebugSession, + debugConfiguration: theia.DebugConfiguration, + executable: theia.DebugAdapterExecutable | undefined, + descriptorFactory: theia.DebugAdapterDescriptorFactory | undefined + ): Promise { + if (descriptorFactory) { + // 'createDebugAdapterDescriptor' 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 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: + // createDebugAdapter(session: DebugSession, executable: DebugAdapterExecutable) { + // if (typeof session.configuration.debugServer === 'number') { + // return new DebugAdapterServer(session.configuration.debugServer); + // } + // 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). + const descriptor = await descriptorFactory.createDebugAdapterDescriptor(session, executable); + if (descriptor) { + if (DebugAdapterServer.is(descriptor)) { + return this.connectSocketDebugAdapter(descriptor); + } else if (DebugAdapterExecutable.is(descriptor)) { + return this.startDebugAdapter(descriptor); + } else if (DebugAdapterNamedPipeServer.is(descriptor)) { + return this.connectPipeDebugAdapter(descriptor); + } else if (DebugAdapterInlineImplementation.is(descriptor)) { + return this.connectInlineDebugAdapter(descriptor); + } + } + } + + if ('debugServer' in debugConfiguration) { + return this.connectSocketDebugAdapter({ port: debugConfiguration.debugServer }); + } else { + if (!executable) { + throw new Error('It is not possible to provide debug adapter executable.'); + } + return this.startDebugAdapter(executable); + } + } + + protected toPlatformInfo(executable: PluginPackageDebuggersContribution): PlatformSpecificAdapterContribution | undefined { + if (isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) { + return executable.winx86 || executable.win || executable.windows; + } + if (isWindows) { + return executable.win || executable.windows; + } + if (isOSX) { + return executable.osx; + } + return executable.linux; + } + + public startDebugAdapter(executable: DebugAdapterExecutable): DebugAdapter { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const options: any = { stdio: ['pipe', 'pipe', 2] }; + + if (executable.options) { + options.cwd = executable.options.cwd; + + // 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. + options.env = Object.assign({}, process.env); + Object.assign(options.env, executable.options.env); + } + + let childProcess: ChildProcess; + const { command, args } = executable; + if (command === 'node') { + if (Array.isArray(args) && args.length > 0) { + const forkOptions: ForkOptions = { + env: options.env, + // When running in Electron, fork will automatically add ELECTRON_RUN_AS_NODE=1 to the env, + // but this will cause issues when debugging Electron apps, so we'll remove it. + execArgv: isElectron() + ? ['-e', 'delete process.env.ELECTRON_RUN_AS_NODE;require(process.argv[1])'] + : [], + silent: true + }; + if (options.cwd) { + forkOptions.cwd = options.cwd; + } + options.stdio.push('ipc'); + forkOptions.stdio = options.stdio; + childProcess = fork(args[0], args.slice(1), forkOptions); + } else { + throw new Error(`It is not possible to launch debug adapter with the command: ${JSON.stringify(executable)}`); + } + } else { + childProcess = spawn(command, args, options); + } + + return new ProcessDebugAdapter(childProcess); + } + + /** + * Connects to a remote debug server. + */ + public connectSocketDebugAdapter(server: DebugAdapterServer): SocketDebugAdapter { + const socket = net.createConnection(server.port, server.host); + return new SocketDebugAdapter(socket); + } + + public connectPipeDebugAdapter(adapter: DebugAdapterNamedPipeServer): SocketDebugAdapter { + const socket = net.createConnection(adapter.path); + return new SocketDebugAdapter(socket); + } +} diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 132c2619575d3..e9a8adcd973c9 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -163,7 +163,7 @@ import { MarkdownString } from './markdown-string'; import { TreeViewsExtImpl } from './tree/tree-views'; import { ConnectionImpl } from '../common/connection'; import { TasksExtImpl } from './tasks/tasks'; -import { DebugExtImpl } from './node/debug/debug'; +import { DebugExtImpl } from './debug/debug-ext'; import { FileSystemExtImpl } from './file-system-ext-impl'; import { QuickPick, QuickPickItem, ResourceLabelFormatter, LineChange } from '@theia/plugin'; import { ScmExtImpl } from './scm'; @@ -811,7 +811,7 @@ export function createAPIFactory( const debuggersContributions = plugin.rawModel.contributes && plugin.rawModel.contributes.debuggers || []; debugExt.assistedInject(connectionExt, commandRegistry); - debugExt.registerDebuggersContributions(plugin.pluginFolder, debuggersContributions); + debugExt.registerDebuggersContributions(plugin.pluginFolder, plugin.model.entryPoint.frontend ? 'frontend' : 'backend', debuggersContributions); const debug: typeof theia.debug = { get activeDebugSession(): theia.DebugSession | undefined { return debugExt.activeDebugSession;