Skip to content

Commit

Permalink
feat: select path for runner when installing (#1238)
Browse files Browse the repository at this point in the history
Closes #1144

### Summary of Changes

Users must now select a path for the runner during installation, since
the path given by VS Code might not be writable.
  • Loading branch information
lars-reimann authored Sep 25, 2024
1 parent 77ad26c commit 3d9d4db
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 23 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ module.exports = {

extends: '@lars-reimann/svelte',
rules: {
'@typescript-eslint/no-throw-literal': 'off',
'@typescript-eslint/only-throw-error': 'error',
'import/extensions': 'off',
'import/no-extraneous-dependencies': 'off',
'svelte/valid-compile': 'off',
Expand Down
55 changes: 35 additions & 20 deletions packages/safe-ds-vscode/src/extension/actions/installRunner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import vscode, { ExtensionContext, Uri } from 'vscode';
import vscode, { Uri } from 'vscode';
import child_process from 'node:child_process';
import semver from 'semver';
import { dependencies, rpc } from '@safe-ds/lang';
Expand All @@ -11,24 +11,40 @@ const LOWEST_SUPPORTED_PYTHON_VERSION = '3.11.0';
const LOWEST_UNSUPPORTED_PYTHON_VERSION = '3.13.0';
const npmVersionRange = `>=${LOWEST_SUPPORTED_PYTHON_VERSION} <${LOWEST_UNSUPPORTED_PYTHON_VERSION}`;

export const installRunner = (context: ExtensionContext, client: LanguageClient) => {
export const installRunner = (client: LanguageClient) => {
return async () => {
// If the runner is already started, do nothing
if (await client.sendRequest(rpc.IsRunnerReadyRequest.type)) {
vscode.window.showInformationMessage('The runner is already installed and running.');
return;
}

// Ask the user where the virtual environment should be created
const runnerVirtualEnvironmentUris = await vscode.window.showOpenDialog({
canSelectFolders: true,
canSelectMany: false,
title: 'Location for the runner installation',
});

if (!runnerVirtualEnvironmentUris || runnerVirtualEnvironmentUris.length === 0) {
return;
}
const runnerVirtualEnvironmentUri = runnerVirtualEnvironmentUris[0]!;

// Install the runner if it is not already installed
const success = await doInstallRunner(context);
const success = await doInstallRunner(runnerVirtualEnvironmentUri);
if (!success) {
return;
}

// Set the runner command in the configuration
await vscode.workspace
.getConfiguration()
.update('safe-ds.runner.command', getRunnerCommand(context), vscode.ConfigurationTarget.Global);
.update(
'safe-ds.runner.command',
getRunnerCommand(runnerVirtualEnvironmentUri),
vscode.ConfigurationTarget.Global,
);

// Start the runner (needed if the configuration did not change, so no event is fired)
await client.sendNotification(rpc.StartRunnerNotification.type);
Expand All @@ -41,7 +57,7 @@ export const installRunner = (context: ExtensionContext, client: LanguageClient)
/**
* Installs the runner in a virtual environment. Returns true if the installation was successful.
*/
const doInstallRunner = async (context: ExtensionContext): Promise<boolean> => {
const doInstallRunner = async (runnerVirtualEnvironmentUri: Uri): Promise<boolean> => {
// Check if a matching Python interpreter is available
const pythonCommand = await getPythonCommand();
if (!pythonCommand) {
Expand All @@ -58,7 +74,7 @@ const doInstallRunner = async (context: ExtensionContext): Promise<boolean> => {
},
async () => {
try {
await createRunnerVirtualEnvironment(context, pythonCommand);
await createRunnerVirtualEnvironment(runnerVirtualEnvironmentUri, pythonCommand);
return true;
} catch (error) {
vscode.window.showErrorMessage('Failed to create a virtual environment.');
Expand All @@ -79,7 +95,7 @@ const doInstallRunner = async (context: ExtensionContext): Promise<boolean> => {
},
async () => {
try {
await installRunnerInVirtualEnvironment(getPipCommand(context));
await installRunnerInVirtualEnvironment(getPipCommand(runnerVirtualEnvironmentUri));
return true;
} catch (error) {
vscode.window.showErrorMessage('Failed to install the runner.');
Expand Down Expand Up @@ -116,9 +132,12 @@ const isMatchingPython = async (pythonCommand: string): Promise<boolean> => {
});
};

const createRunnerVirtualEnvironment = async (context: ExtensionContext, pythonCommand: string): Promise<void> => {
const createRunnerVirtualEnvironment = async (
runnerVirtualEnvironmentUri: Uri,
pythonCommand: string,
): Promise<void> => {
return new Promise((resolve, reject) => {
child_process.exec(`${pythonCommand} -m venv ${runnerVirtualEnvironmentUri(context).fsPath}`, (error) => {
child_process.exec(`"${pythonCommand}" -m venv "${runnerVirtualEnvironmentUri.fsPath}"`, (error) => {
if (error) {
reject(error);
} else {
Expand All @@ -130,7 +149,7 @@ const createRunnerVirtualEnvironment = async (context: ExtensionContext, pythonC

export const installRunnerInVirtualEnvironment = async (pipCommand: string): Promise<void> => {
return new Promise((resolve, reject) => {
const installCommand = `${pipCommand} install --upgrade "safe-ds-runner${dependencies['safe-ds-runner'].pipVersionRange}"`;
const installCommand = `"${pipCommand}" install --upgrade "safe-ds-runner${dependencies['safe-ds-runner'].pipVersionRange}"`;
const process = child_process.spawn(installCommand, { shell: true });

process.stdout.on('data', (data: Buffer) => {
Expand All @@ -153,22 +172,18 @@ export const installRunnerInVirtualEnvironment = async (pipCommand: string): Pro
});
};

const getPipCommand = (context: ExtensionContext): string => {
const getPipCommand = (runnerVirtualEnvironmentUri: Uri): string => {
if (process.platform === 'win32') {
return `${runnerVirtualEnvironmentUri(context).fsPath}\\Scripts\\pip.exe`;
return `${runnerVirtualEnvironmentUri.fsPath}\\Scripts\\pip.exe`;
} else {
return `${runnerVirtualEnvironmentUri(context).fsPath}/bin/pip`;
return `${runnerVirtualEnvironmentUri.fsPath}/bin/pip`;
}
};

const getRunnerCommand = (context: ExtensionContext): string => {
const getRunnerCommand = (runnerVirtualEnvironmentUri: Uri): string => {
if (process.platform === 'win32') {
return `${runnerVirtualEnvironmentUri(context).fsPath}\\Scripts\\safe-ds-runner.exe`;
return `${runnerVirtualEnvironmentUri.fsPath}\\Scripts\\safe-ds-runner.exe`;
} else {
return `${runnerVirtualEnvironmentUri(context).fsPath}/bin/safe-ds-runner`;
return `${runnerVirtualEnvironmentUri.fsPath}/bin/safe-ds-runner`;
}
};

const runnerVirtualEnvironmentUri = (context: ExtensionContext): Uri => {
return vscode.Uri.joinPath(context.globalStorageUri, 'runnerVenv');
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const updateRunner = (context: ExtensionContext, client: LanguageClient)

// If the runner executable cannot be found at all, install it from scratch
if (!fs.existsSync(await getRunnerCommand())) {
await installRunner(context, client)();
await installRunner(client)();
return;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/safe-ds-vscode/src/extension/mainClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const createLanguageClient = function (context: vscode.ExtensionContext): Langua
const registerNotificationListeners = function (context: vscode.ExtensionContext) {
context.subscriptions.push(
client.onNotification(rpc.InstallRunnerNotification.type, async () => {
await installRunner(context, client)();
await installRunner(client)();
}),
client.onNotification(rpc.RunnerStartedNotification.type, async ({ port }: rpc.RunnerStartedParams) => {
await services.runtime.PythonServer.connectToPort(port);
Expand All @@ -114,7 +114,7 @@ const registerCommands = function (context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('safe-ds.dumpDiagnostics', dumpDiagnostics(context)),
vscode.commands.registerCommand('safe-ds.exploreTable', exploreTable(context)),
vscode.commands.registerCommand('safe-ds.installRunner', installRunner(context, client)),
vscode.commands.registerCommand('safe-ds.installRunner', installRunner(client)),
vscode.commands.registerCommand('safe-ds.openDiagnosticsDumps', openDiagnosticsDumps(context)),
vscode.commands.registerCommand('safe-ds.refreshWebview', refreshWebview(context)),
vscode.commands.registerCommand('safe-ds.updateRunner', updateRunner(context, client)),
Expand Down

0 comments on commit 3d9d4db

Please sign in to comment.