Skip to content
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

feat: updater for the runner #1042

Merged
merged 1 commit into from
Apr 13, 2024
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
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
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
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