-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathfactory.ts
192 lines (172 loc) · 8.57 KB
/
factory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* eslint-disable @typescript-eslint/naming-convention */
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import * as path from 'path';
import {
DebugAdapterDescriptor,
DebugAdapterExecutable,
DebugAdapterServer,
DebugSession,
l10n,
WorkspaceFolder,
} from 'vscode';
import { AttachRequestArguments, LaunchRequestArguments } from '../../types';
import { IDebugAdapterDescriptorFactory } from '../types';
import { executeCommand, showErrorMessage } from '../../common/vscodeapi';
import { traceLog, traceVerbose } from '../../common/log/logging';
import { EventName } from '../../telemetry/constants';
import { sendTelemetryEvent } from '../../telemetry';
import { getInterpreterDetails, resolveEnvironment, runPythonExtensionCommand } from '../../common/python';
import { Commands, EXTENSION_ROOT_DIR } from '../../common/constants';
import { Common, DebugConfigStrings, Interpreters } from '../../common/utils/localize';
import { IPersistentStateFactory } from '../../common/types';
import { ResolvedEnvironment } from '@vscode/python-extension';
import { fileToCommandArgumentForPythonExt } from '../../common/stringUtils';
// persistent state names, exported to make use of in testing
export enum debugStateKeys {
doNotShowAgain = 'doNotShowPython36DebugDeprecatedAgain',
}
export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFactory {
constructor(private persistentState: IPersistentStateFactory) {}
public async createDebugAdapterDescriptor(
session: DebugSession,
_executable: DebugAdapterExecutable | undefined,
): Promise<DebugAdapterDescriptor | undefined> {
const configuration = session.configuration as LaunchRequestArguments | AttachRequestArguments;
// There are four distinct scenarios here:
//
// 1. "launch";
// 2. "attach" with "processId";
// 3. "attach" with "listen";
// 4. "attach" with "connect" (or legacy "host"/"port");
//
// For the first three, we want to spawn the debug adapter directly.
// For the last one, the adapter is already listening on the specified socket.
if (configuration.request === 'attach') {
if (configuration.connect !== undefined) {
traceLog(
`Connecting to DAP Server at: ${configuration.connect.host ?? '127.0.0.1'}:${
configuration.connect.port
}`,
);
return new DebugAdapterServer(
Number(configuration.connect.port),
configuration.connect.host ?? '127.0.0.1',
);
} else if (configuration.port !== undefined) {
traceLog(`Connecting to DAP Server at: ${configuration.host ?? '127.0.0.1'}:${configuration.port}`);
return new DebugAdapterServer(Number(configuration.port), configuration.host ?? '127.0.0.1');
} else if (configuration.listen === undefined && configuration.processId === undefined) {
throw new Error('"request":"attach" requires either "connect", "listen", or "processId"');
}
}
const command = await this.getDebugAdapterPython(configuration, session.workspaceFolder);
if (command.length !== 0) {
if (configuration.request === 'attach' && configuration.processId !== undefined) {
sendTelemetryEvent(EventName.DEBUGGER_ATTACH_TO_LOCAL_PROCESS);
}
let executable = command.shift() ?? 'python';
// "logToFile" is not handled directly by the adapter - instead, we need to pass
// the corresponding CLI switch when spawning it.
const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : [];
if (configuration.debugAdapterPath !== undefined) {
const args = command.concat([configuration.debugAdapterPath, ...logArgs]);
traceLog(`DAP Server launched with command: ${executable} ${args.join(' ')}`);
executable = fileToCommandArgumentForPythonExt(executable);
return new DebugAdapterExecutable(executable, args);
}
const debuggerAdapterPathToUse = path.join(EXTENSION_ROOT_DIR, 'bundled', 'libs', 'debugpy', 'adapter');
const args = command.concat([debuggerAdapterPathToUse, ...logArgs]);
traceLog(`DAP Server launched with command: ${executable} ${args.join(' ')}`);
sendTelemetryEvent(EventName.DEBUG_ADAPTER_USING_WHEELS_PATH, undefined, { usingWheels: true });
return new DebugAdapterExecutable(executable, args);
} else {
throw new Error(DebugConfigStrings.debugStopped);
}
}
/**
* Get the python executable used to launch the Python Debug Adapter.
* In the case of `attach` scenarios, just use the workspace interpreter.
* It is unlike user won't have a Python interpreter
*
* @private
* @param {(LaunchRequestArguments | AttachRequestArguments)} configuration
* @param {WorkspaceFolder} [workspaceFolder]
* @returns {Promise<string>} Path to the python interpreter for this workspace.
* @memberof DebugAdapterDescriptorFactory
*/
private async getDebugAdapterPython(
configuration: LaunchRequestArguments | AttachRequestArguments,
workspaceFolder?: WorkspaceFolder,
): Promise<string[]> {
if (configuration.debugAdapterPython !== undefined) {
return this.getExecutableCommand(await resolveEnvironment(configuration.debugAdapterPython));
} else if (configuration.pythonPath) {
return this.getExecutableCommand(await resolveEnvironment(configuration.pythonPath));
}
const resourceUri = workspaceFolder ? workspaceFolder.uri : undefined;
const interpreter = await getInterpreterDetails(resourceUri);
if (interpreter?.path) {
traceVerbose(`Selecting active interpreter as Python Executable for DA '${interpreter.path[0]}'`);
return this.getExecutableCommand(await resolveEnvironment(interpreter.path[0]));
}
const prompts = [Interpreters.changePythonInterpreter];
const selection = await showErrorMessage(
l10n.t(
'You need to select a Python interpreter before you start debugging.\n\nTip: click on "Select Interpreter" in the status bar.',
),
{ modal: true },
...prompts,
);
if (selection === Interpreters.changePythonInterpreter) {
await executeCommand(Commands.Set_Interpreter);
const interpreter = await getInterpreterDetails(resourceUri);
if (interpreter?.path) {
traceVerbose(`Selecting active interpreter as Python Executable for DA '${interpreter.path[0]}'`);
return this.getExecutableCommand(await resolveEnvironment(interpreter.path[0]));
}
}
return [];
}
private async showDeprecatedPythonMessage() {
sendTelemetryEvent(EventName.DEBUGGER_PYTHON_37_DEPRECATED);
const notificationPromptEnabled = this.persistentState.createGlobalPersistentState(
debugStateKeys.doNotShowAgain,
false,
);
if (notificationPromptEnabled.value) {
return;
}
const prompts = [Interpreters.changePythonInterpreter, Common.doNotShowAgain];
const selection = await showErrorMessage(
l10n.t('The debugger in the python extension no longer supports python versions minor than 3.9.'),
{ modal: true },
...prompts,
);
if (!selection) {
return;
}
if (selection === Interpreters.changePythonInterpreter) {
await runPythonExtensionCommand(Commands.Set_Interpreter);
}
if (selection === Common.doNotShowAgain) {
// Never show the message again
await this.persistentState
.createGlobalPersistentState(debugStateKeys.doNotShowAgain, false)
.updateValue(true);
}
}
private async getExecutableCommand(interpreter: ResolvedEnvironment | undefined): Promise<string[]> {
if (interpreter) {
if (
(interpreter.version?.major ?? 0) < 3 ||
((interpreter.version?.major ?? 0) <= 3 && (interpreter.version?.minor ?? 0) <= 9)
) {
this.showDeprecatedPythonMessage();
}
return interpreter.path.length > 0 ? [interpreter.path] : [];
}
return [];
}
}