Skip to content

Configless debug #557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 32 additions & 37 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,35 @@
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js",
"!${workspaceFolder}/**/node_modules**/*"
],
"preLaunchTask": "npm: watch",
"presentation": {
"hidden": false,
"group": "",
"order": 2
}
},
{
"name": "Unit Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"./out/test/**/*.unit.test.js",
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/unittest/index"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js",
],
"preLaunchTask": "tasks: watch-tests"
},
]
}
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js", "!${workspaceFolder}/**/node_modules**/*"],
"preLaunchTask": "npm: watch",
"presentation": {
"hidden": false,
"group": "",
"order": 2
}
},
{
"name": "Unit Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"./out/test/**/*.unit.test.js",
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/unittest/index",
//"--grep", "<suite name>",
"--timeout=300000"
],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "tasks: watch-tests"
}
]
}
3 changes: 3 additions & 0 deletions bundled/scripts/noConfigScripts/debugpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#! /bin/bash
# Bash script
python $BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $@
3 changes: 3 additions & 0 deletions bundled/scripts/noConfigScripts/debugpy.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off
:: Bat script
python %BUNDLED_DEBUGPY_PATH% --listen 0 --wait-for-client %*
2 changes: 2 additions & 0 deletions bundled/scripts/noConfigScripts/debugpy.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Fish script
python $BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $argv
2 changes: 2 additions & 0 deletions bundled/scripts/noConfigScripts/debugpy.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# PowerShell script
python $env:BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $args
5 changes: 5 additions & 0 deletions src/extension/extensionInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { IExtensionApi } from './apiTypes';
import { registerHexDebugVisualizationTreeProvider } from './debugger/visualizers/inlineHexDecoder';
import { PythonInlineValueProvider } from './debugger/inlineValue/pythonInlineValueProvider';
import { traceLog } from './common/log/logging';
import { registerNoConfigDebug } from './noConfigDebugInit';

export async function registerDebugger(context: IExtensionContext): Promise<IExtensionApi> {
const childProcessAttachService = new ChildProcessAttachService();
Expand Down Expand Up @@ -247,5 +248,9 @@ export async function registerDebugger(context: IExtensionContext): Promise<IExt
window.activeTextEditor?.document.languageId === 'python',
);

context.subscriptions.push(
await registerNoConfigDebug(context.environmentVariableCollection, context.extensionPath),
);

return buildApi();
}
116 changes: 116 additions & 0 deletions src/extension/noConfigDebugInit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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 * as os from 'os';
import { DebugSessionOptions, Disposable, GlobalEnvironmentVariableCollection, RelativePattern } from 'vscode';
import { createFileSystemWatcher, debugStartDebugging } from './utils';
import { traceError, traceLog, traceVerbose } from './common/log/logging';

/**
* 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: tempDir/noConfigDebugAdapterEndpoints-<randomString>/debuggerAdapterEndpoint.txt
const randomSuffix = crypto.randomBytes(10).toString('hex');
const tempDirName = `noConfigDebugAdapterEndpoints-${randomSuffix}`;
let tempDirPath = path.join(os.tmpdir(), tempDirName);
try {
traceLog('Attempting to use temp directory for noConfigDebugAdapterEndpoints, dir name:', tempDirName);
await fs.promises.mkdir(tempDirPath, { recursive: true });
} catch (error) {
// Handle the error when accessing the temp directory
traceError('Error accessing temp directory:', error, ' Attempt to use extension root dir instead');
// Make new temp directory in extension root dird
tempDirPath = path.join(extPath, '.temp');
await fs.promises.mkdir(tempDirPath, { recursive: true });
}
const tempFilePath = path.join(tempDirPath, 'debuggerAdapterEndpoint.txt');

// Add env vars for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH
collection.replace('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);

// create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written
const fileSystemWatcher = createFileSystemWatcher(new RelativePattern(tempDirPath, '**/*'));
const fileCreationEvent = fileSystemWatcher.onDidCreate(async (uri) => {
const filePath = uri.fsPath;
fs.readFile(filePath, (err, data) => {
const dataParse = data.toString();
if (err) {
traceError(`Error reading debuggerAdapterEndpoint.txt file: ${err}`);
return;
}
try {
// parse the client port
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();
}),
);
}
21 changes: 21 additions & 0 deletions src/extension/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
workspace,
debug,
WorkspaceFolder,
DebugConfiguration,
DebugSession,
DebugSessionOptions,
FileSystemWatcher,
} from 'vscode';

export function createFileSystemWatcher(args: any): FileSystemWatcher {
return workspace.createFileSystemWatcher(args);
}

export async function debugStartDebugging(
folder: WorkspaceFolder | undefined,
nameOrConfiguration: string | DebugConfiguration,
parentSessionOrOptions?: DebugSession | DebugSessionOptions,
): Promise<boolean> {
return debug.startDebugging(folder, nameOrConfiguration, parentSessionOrOptions);
}
Loading
Loading