From 730ffb82be499c1e40830a6a5992a1ab9edae719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20H=C3=BCsser?= Date: Sat, 11 May 2024 16:12:58 +0200 Subject: [PATCH] Automatically install the probe-rs binaries on request --- package.json | 3 +- src/extension.ts | 87 +++++++++++++++++++++++++++++++++++++++++++++++- src/utils.ts | 32 ++++++++++++++++++ tsconfig.json | 2 +- 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 src/utils.ts diff --git a/package.json b/package.json index 58b9cd3..d1e68cc 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,8 @@ }, "main": "./dist/extension.js", "activationEvents": [ - "onDebug" + "onDebug", + "onStartupFinished" ], "workspaceTrust": { "request": "never" diff --git a/src/extension.ts b/src/extension.ts index 09805f1..2898f40 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,6 +18,7 @@ import { ProviderResult, WorkspaceFolder, } from 'vscode'; +import {probeRsInstalled} from './utils'; export async function activate(context: vscode.ExtensionContext) { const descriptorFactory = new ProbeRSDebugAdapterServerDescriptorFactory(); @@ -29,9 +30,21 @@ export async function activate(context: vscode.ExtensionContext) { vscode.debug.onDidReceiveDebugSessionCustomEvent( descriptorFactory.receivedCustomEvent.bind(descriptorFactory), ), - vscode.debug.onDidTerminateDebugSession(descriptorFactory.dispose.bind(descriptorFactory)), ); + (async () => { + if (!(await probeRsInstalled())) { + const resp = await vscode.window.showInformationMessage( + "probe-rs doesn't seem to be installed. Do you want to install it automatically now?", + 'Install', + ); + + if (resp === 'Install') { + await installProbeRs(); + } + } + })(); + // I cannot find a way to programmatically test for when VSCode is debugging the extension, versus when a user is using the extension to debug their own code, but the following code is useful in the former situation, so I will leave it here to be commented out by extension developers when needed. // const trackerFactory = new ProbeRsDebugAdapterTrackerFactory(); // context.subscriptions.push( @@ -543,6 +556,78 @@ function startDebugServer( }); } +/// Installs probe-rs if it is not present. +function installProbeRs() { + let windows = process.platform === 'win32'; + let done = false; + + vscode.window.withProgress( + { + location: vscode.ProgressLocation.Window, + cancellable: false, + title: 'Installing probe-rs ...', + }, + async (progress) => { + progress.report({increment: 0}); + + const launchedDebugAdapter = childProcess.exec( + windows + ? `powershell.exe -encodedCommand ${Buffer.from( + 'irm https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-installer.ps1 | iex', + 'utf16le', + ).toString('base64')}` + : "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-installer.sh | sh", + (error, stdout, stderr) => { + if (error) { + console.error(`exec error: ${error}`); + done = true; + return; + } + console.log(`stdout: ${stdout}`); + console.log(`stderr: ${stderr}`); + }, + ); + + const errorListener = (error: Error) => { + vscode.window.showInformationMessage( + 'Installation failed: ${err.message}. Check the logs for more info.', + 'Ok', + ); + console.error(error); + done = true; + }; + + const exitListener = (code: number | null, signal: NodeJS.Signals | null) => { + let message; + if (code === 0) { + message = 'Installation successful.'; + } else if (signal) { + message = 'Installation aborted.'; + } else { + message = + 'Installation failed. Go to https://probe.rs to check out the setup and troubleshooting instructions.'; + } + console.error(message); + vscode.window.showInformationMessage(message, 'Ok'); + done = true; + }; + + launchedDebugAdapter.on('error', errorListener); + launchedDebugAdapter.on('exit', exitListener); + + const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + while (!done) { + await delay(100); + } + + launchedDebugAdapter.removeListener('error', errorListener); + launchedDebugAdapter.removeListener('exit', exitListener); + + progress.report({increment: 100}); + }, + ); +} + // Get the name of the debugger executable // // This takes the value from configuration, if set, or diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..00eda40 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,32 @@ +const path = require('path'); +import {promises as fs} from 'fs'; + +/** + * @param {string} exe executable name (without extension if on Windows) + * @return {Promise} executable path if found + * */ +async function findExecutable(executableName: string): Promise { + const envPath = process.env.PATH || ''; + const envExt = process.env.PATHEXT || ''; + const pathDirs = envPath.replace(/["]+/g, '').split(path.delimiter).filter(Boolean); + const extensions = envExt.split(';'); + const candidates = pathDirs.flatMap((d) => + extensions.map((ext) => path.join(d, executableName + ext)), + ); + try { + return await Promise.any(candidates.map(checkFileExists)); + } catch (e) { + return null; + } +} + +async function checkFileExists(filePath): Promise { + if ((await fs.stat(filePath)).isFile()) { + return filePath; + } + return null; +} + +export async function probeRsInstalled() { + return await findExecutable('probe-rs'); +} diff --git a/tsconfig.json b/tsconfig.json index 45ffa0c..fa87d19 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "module": "commonjs", "target": "es6", "outDir": "out", - "lib": ["es6"], + "lib": ["es2021"], "sourceMap": true, "rootDir": "src", "strict": true /* enable all strict type-checking options */,