-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathnoConfigDebugInit.ts
150 lines (135 loc) · 5.95 KB
/
noConfigDebugInit.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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import {
DebugSessionOptions,
Disposable,
GlobalEnvironmentVariableCollection,
l10n,
RelativePattern,
workspace,
} from 'vscode';
import { createFileSystemWatcher, debugStartDebugging } from './utils';
import { traceError, traceVerbose } from './common/log/logging';
import { sendTelemetryEvent } from './telemetry';
import { EventName } from './telemetry/constants';
import { TriggerType } from './types';
/**
* Registers the configuration-less debugging setup for the extension.
*
* This function sets up environment variables and a file system watcher to
* facilitate debugging without requiring a pre-configured launch.json file.
*
* @param envVarCollection - The collection of environment variables to be modified.
* @param extPath - The path to the extension directory.
*
* Environment Variables:
* - `DEBUGPY_ADAPTER_ENDPOINTS`: Path to the file containing the debugger adapter endpoint.
* - `BUNDLED_DEBUGPY_PATH`: Path to the bundled debugpy library.
* - `PATH`: Appends the path to the noConfigScripts directory.
*/
export async function registerNoConfigDebug(
envVarCollection: GlobalEnvironmentVariableCollection,
extPath: string,
): Promise<Disposable> {
const collection = envVarCollection;
// create a temp directory for the noConfigDebugAdapterEndpoints
// file path format: extPath/.noConfigDebugAdapterEndpoints/endpoint-stableWorkspaceHash.txt
let workspaceString = workspace.workspaceFile?.fsPath;
if (!workspaceString) {
workspaceString = workspace.workspaceFolders?.map((e) => e.uri.fsPath).join(';');
}
if (!workspaceString) {
traceError('No workspace folder found');
return Promise.resolve(new Disposable(() => {}));
}
// create a stable hash for the workspace folder, reduce terminal variable churn
const hash = crypto.createHash('sha256');
hash.update(workspaceString.toString());
const stableWorkspaceHash = hash.digest('hex').slice(0, 16);
const tempDirPath = path.join(extPath, '.noConfigDebugAdapterEndpoints');
const tempFilePath = path.join(tempDirPath, `endpoint-${stableWorkspaceHash}.txt`);
// create the temp directory if it doesn't exist
if (!fs.existsSync(tempDirPath)) {
fs.mkdirSync(tempDirPath, { recursive: true });
} else {
// remove endpoint file in the temp directory if it exists
if (fs.existsSync(tempFilePath)) {
fs.unlinkSync(tempFilePath);
}
}
// clear the env var collection to remove any existing env vars
collection.clear();
// Add env var for PYDEVD_DISABLE_FILE_VALIDATION to disable extra output in terminal when starting the debug session.
collection.replace('PYDEVD_DISABLE_FILE_VALIDATION', '1');
// Add env vars for VSCODE_DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH
collection.replace('VSCODE_DEBUGPY_ADAPTER_ENDPOINTS', tempFilePath);
const noConfigScriptsDir = path.join(extPath, 'bundled', 'scripts', 'noConfigScripts');
const pathSeparator = process.platform === 'win32' ? ';' : ':';
collection.append('PATH', `${pathSeparator}${noConfigScriptsDir}`);
const bundledDebugPath = path.join(extPath, 'bundled', 'libs', 'debugpy');
collection.replace('BUNDLED_DEBUGPY_PATH', bundledDebugPath);
envVarCollection.description = l10n.t(
'Enables use of [no-config debugging](https://github.com/microsoft/vscode-python-debugger/wiki/No%E2%80%90Config-Debugging), `debugpy <script.py>`, in the terminal.',
);
// create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written
const fileSystemWatcher = createFileSystemWatcher(new RelativePattern(tempDirPath, '**/*.txt'));
const fileCreationEvent = fileSystemWatcher.onDidCreate(async (uri) => {
sendTelemetryEvent(EventName.DEBUG_SESSION_START, undefined, {
trigger: 'noConfig' as TriggerType,
});
const filePath = uri.fsPath;
fs.readFile(filePath, (err, data) => {
if (err) {
traceError(`Error reading debuggerAdapterEndpoint.txt file: ${err}`);
return;
}
try {
// parse the client port
const dataParse = data.toString();
const jsonData = JSON.parse(dataParse);
const clientPort = jsonData.client?.port;
traceVerbose(`Parsed client port: ${clientPort}`);
const options: DebugSessionOptions = {
noDebug: false,
};
// start debug session with the client port
debugStartDebugging(
undefined,
{
type: 'python',
request: 'attach',
name: 'Attach to Python',
connect: {
port: clientPort,
host: 'localhost',
},
},
options,
).then(
(started) => {
if (started) {
traceVerbose('Successfully started debug session');
} else {
traceError('Error starting debug session, session not started.');
}
},
(error) => {
traceError(`Error starting debug session: ${error}`);
},
);
} catch (parseErr) {
traceError(`Error parsing JSON: ${parseErr}`);
}
});
JSON.parse;
});
return Promise.resolve(
new Disposable(() => {
fileSystemWatcher.dispose();
fileCreationEvent.dispose();
}),
);
}