Skip to content

Commit

Permalink
feat: updater for the runner (#1042)
Browse files Browse the repository at this point in the history
### Summary of Changes

Add a command to automatically update the runner to a suitable version.
  • Loading branch information
lars-reimann authored Apr 13, 2024
1 parent 622740d commit 610d45b
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 11 deletions.
9 changes: 8 additions & 1 deletion packages/safe-ds-lang/src/language/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { pipVersionRange, RPC_RUNNER_INSTALL, RPC_RUNNER_START, RPC_RUNNER_STARTED } from './runner/safe-ds-runner.js';
import {
pipVersionRange,
RPC_RUNNER_INSTALL,
RPC_RUNNER_START,
RPC_RUNNER_STARTED,
RPC_RUNNER_UPDATE,
} from './runner/safe-ds-runner.js';

// Services
export type { SafeDsServices } from './safe-ds-module.js';
Expand All @@ -25,6 +31,7 @@ export const rpc = {
runnerInstall: RPC_RUNNER_INSTALL,
runnerStart: RPC_RUNNER_START,
runnerStarted: RPC_RUNNER_STARTED,
runnerUpdate: RPC_RUNNER_UPDATE,
};

// Dependencies
Expand Down
7 changes: 6 additions & 1 deletion packages/safe-ds-lang/src/language/runner/safe-ds-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { SafeDsMessagingProvider } from '../../communication/safe-ds-messaging-p
export const RPC_RUNNER_INSTALL = 'runner/install';
export const RPC_RUNNER_START = 'runner/start';
export const RPC_RUNNER_STARTED = 'runner/started';
export const RPC_RUNNER_UPDATE = 'runner/update';

const LOWEST_SUPPORTED_RUNNER_VERSION = '0.10.0';
const LOWEST_UNSUPPORTED_RUNNER_VERSION = '0.11.0';
Expand Down Expand Up @@ -174,9 +175,13 @@ export class SafeDsRunner {
const versionString = await this.getPythonServerVersion(pythonServerTest);
if (!semver.satisfies(versionString, npmVersionRange)) {
this.error(`Installed runner version ${versionString} does not meet requirements: ${pipVersionRange}`);
this.messaging.showErrorMessage(
const action = await this.messaging.showErrorMessage(
`The installed runner version ${versionString} is not compatible with this version of the extension. The installed version should match these requirements: ${pipVersionRange}. Please update to a matching version.`,
{ title: 'Update runner' },
);
if (action?.title === 'Update runner') {
await this.messaging.sendNotification(RPC_RUNNER_UPDATE);
}
return;
} else {
this.info(`Using safe-ds-runner version: ${versionString}`);
Expand Down
5 changes: 5 additions & 0 deletions packages/safe-ds-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@
"title": "Run Pipeline",
"category": "Safe-DS",
"icon": "$(play)"
},
{
"command": "safe-ds.updateRunner",
"title": "Update the Safe-DS Runner",
"category": "Safe-DS"
}
],
"snippets": [
Expand Down
15 changes: 6 additions & 9 deletions packages/safe-ds-vscode/src/extension/commands/installRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import child_process from 'node:child_process';
import semver from 'semver';
import { dependencies, rpc, SafeDsServices } from '@safe-ds/lang';
import { logError, printOutputMessage } from '../output.js';
import fs from 'node:fs';
import { LanguageClient } from 'vscode-languageclient/node.js';

const pythonCommandCandidates = ['python3', 'python', 'py'];
Expand All @@ -21,11 +20,9 @@ export const installRunner = (context: ExtensionContext, client: LanguageClient,
}

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

// Set the runner command in the configuration
Expand Down Expand Up @@ -82,7 +79,7 @@ const doInstallRunner = async (context: ExtensionContext): Promise<boolean> => {
},
async () => {
try {
await installRunnerInVirtualEnvironment(context);
await installRunnerInVirtualEnvironment(getPipCommand(context));
return true;
} catch (error) {
vscode.window.showErrorMessage('Failed to install the runner.');
Expand Down Expand Up @@ -131,9 +128,9 @@ const createRunnerVirtualEnvironment = async (context: ExtensionContext, pythonC
});
};

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

process.stdout.on('data', (data: Buffer) => {
Expand Down
82 changes: 82 additions & 0 deletions packages/safe-ds-vscode/src/extension/commands/updateRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import vscode, { ExtensionContext } from 'vscode';
import { LanguageClient } from 'vscode-languageclient/node.js';
import { rpc, SafeDsServices } from '@safe-ds/lang';
import fs from 'node:fs';
import path from 'node:path';
import { installRunner, installRunnerInVirtualEnvironment } from './installRunner.js';
import { platform } from 'node:os';
import { logError } from '../output.js';

export const updateRunner = (context: ExtensionContext, client: LanguageClient, services: SafeDsServices) => {
return async () => {
// If the runner is already started, do nothing
if (services.runtime.Runner.isPythonServerAvailable()) {
vscode.window.showInformationMessage('The runner is already installed and running.');
return;
}

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

// Update the runner if it is already installed
const success = await doUpdateRunner();
if (!success) {
return;
}

// Start the runner (needed if the configuration did not change, so no event is fired)
await client.sendNotification(rpc.runnerStart);

// Inform the user
vscode.window.showInformationMessage('The runner has been updated successfully.');
};
};

const doUpdateRunner = async (): Promise<boolean> => {
// Check if pip is available
const pipCommand = await getPipCommand();
if (!pipCommand) {
vscode.window.showErrorMessage('Failed to find pip.');
logError('Failed to find pip.');
return false;
}

// Install the runner in the virtual environment
return vscode.window.withProgress(
{
location: vscode.ProgressLocation.Window,
title: 'Installing the runner (this may take a few minutes)...',
},
async () => {
try {
await installRunnerInVirtualEnvironment(pipCommand);
return true;
} catch (error) {
vscode.window.showErrorMessage('Failed to install the runner.');
logError(String(error));
return false;
}
},
);
};

const getRunnerCommand = async (): Promise<string> => {
return vscode.workspace.getConfiguration('safe-ds.runner').get('command') ?? '';
};

const getPipCommand = async (): Promise<string | undefined> => {
const runnerCommand = await getRunnerCommand();
if (!runnerCommand) {
return;
}

const runnerDir = path.dirname(runnerCommand);
if (platform() === 'win32') {
return path.join(runnerDir, 'pip.exe');
} else {
return path.join(runnerDir, 'pip');
}
};
7 changes: 7 additions & 0 deletions packages/safe-ds-vscode/src/extension/mainClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { dumpDiagnostics } from './commands/dumpDiagnostics.js';
import { openDiagnosticsDumps } from './commands/openDiagnosticsDumps.js';
import { isSdsPlaceholder, SdsPipeline } from '../../../safe-ds-lang/src/language/generated/ast.js';
import { installRunner } from './commands/installRunner.js';
import { updateRunner } from './commands/updateRunner.js';

let client: LanguageClient;
let services: SafeDsServices;
Expand Down Expand Up @@ -51,6 +52,9 @@ const registerNotificationListeners = function (context: vscode.ExtensionContext
client.onNotification(rpc.runnerStarted, async (port: number) => {
await services.runtime.Runner.connectToPort(port);
});
client.onNotification(rpc.runnerUpdate, async () => {
await updateRunner(context, client, services)();
});
};

// This function is called when the extension is deactivated.
Expand Down Expand Up @@ -106,6 +110,9 @@ const registerVSCodeCommands = function (context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('safe-ds.openDiagnosticsDumps', openDiagnosticsDumps(context)),
);
context.subscriptions.push(
vscode.commands.registerCommand('safe-ds.updateRunner', updateRunner(context, client, services)),
);

context.subscriptions.push(vscode.commands.registerCommand('safe-ds.runPipelineFile', commandRunPipelineFile));
context.subscriptions.push(
Expand Down

0 comments on commit 610d45b

Please sign in to comment.