From 9d661b5fe70db7ae2fc6a2b194b165f7408cc9e8 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Wed, 24 Apr 2024 15:48:27 +0200 Subject: [PATCH 1/2] feat: required safe-ds-runner 0.13.0 --- .../src/language/runtime/safe-ds-python-server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/safe-ds-lang/src/language/runtime/safe-ds-python-server.ts b/packages/safe-ds-lang/src/language/runtime/safe-ds-python-server.ts index 57bb0ba6a..6f4af2e29 100644 --- a/packages/safe-ds-lang/src/language/runtime/safe-ds-python-server.ts +++ b/packages/safe-ds-lang/src/language/runtime/safe-ds-python-server.ts @@ -16,8 +16,8 @@ import { UpdateRunnerNotification, } from '../communication/rpc.js'; -const LOWEST_SUPPORTED_RUNNER_VERSION = '0.12.0'; -const LOWEST_UNSUPPORTED_RUNNER_VERSION = '0.13.0'; +const LOWEST_SUPPORTED_RUNNER_VERSION = '0.13.0'; +const LOWEST_UNSUPPORTED_RUNNER_VERSION = '0.14.0'; const npmVersionRange = `>=${LOWEST_SUPPORTED_RUNNER_VERSION} <${LOWEST_UNSUPPORTED_RUNNER_VERSION}`; export const pipVersionRange = `>=${LOWEST_SUPPORTED_RUNNER_VERSION},<${LOWEST_UNSUPPORTED_RUNNER_VERSION}`; From 520f563359e6a0fb07fb850bf264251a25099b88 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Wed, 24 Apr 2024 17:01:40 +0200 Subject: [PATCH 2/2] feat: check if latest runner is installed --- .../language/runtime/safe-ds-python-server.ts | 75 +++++++++++++++++-- .../src/extension/actions/installRunner.ts | 2 +- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/packages/safe-ds-lang/src/language/runtime/safe-ds-python-server.ts b/packages/safe-ds-lang/src/language/runtime/safe-ds-python-server.ts index 6f4af2e29..c276076d2 100644 --- a/packages/safe-ds-lang/src/language/runtime/safe-ds-python-server.ts +++ b/packages/safe-ds-lang/src/language/runtime/safe-ds-python-server.ts @@ -152,21 +152,30 @@ export class SafeDsPythonServer { this.logger.debug(`Using runner command "${command}".`); // Check whether the runner command is set properly and get the runner version - let version: string; + let installedVersion: string; try { - version = await this.getRunnerVersion(command); - this.logger.debug(`Found safe-ds-runner with version "${version}".`); + installedVersion = await this.getInstalledRunnerVersion(command); + this.logger.debug(`Found safe-ds-runner with version "${installedVersion}".`); } catch (error) { await this.reportBadRunnerCommand(command, error); return undefined; } // Check whether the runner version is supported - if (!this.isValidVersion(version)) { - await this.reportOutdatedRunner(version); + if (!this.isValidVersion(installedVersion)) { + await this.reportInvalidRunnerVersion(installedVersion); return undefined; } + // Check whether a new version of the runner is available + const latestVersion = await this.getLatestMatchingRunnerVersion(); + if (latestVersion && semver.gt(latestVersion, installedVersion)) { + if (await this.reportOutdatedRunner(installedVersion, latestVersion)) { + // Abort the start process if the user wants to update the runner + return undefined; + } + } + return command; } @@ -176,7 +185,7 @@ export class SafeDsPythonServer { * @returns A promise that resolves to the version of the runner if it could be determined, otherwise the promise is * rejected. */ - private async getRunnerVersion(command: string): Promise { + private async getInstalledRunnerVersion(command: string): Promise { const versionProcess = child_process.spawn(command, ['-V']); return new Promise((resolve, reject) => { @@ -195,6 +204,30 @@ export class SafeDsPythonServer { }); } + /** + * Get the latest version of the runner in the required version range. + */ + private async getLatestMatchingRunnerVersion(): Promise { + // Get information about `safe-ds-runner` from Pypi + const response = await fetch('https://pypi.org/pypi/safe-ds-runner/json', { + signal: AbortSignal.timeout(2000), + }); + if (!response.ok) { + this.logger.error(`Could not fetch the latest version of safe-ds-runner: ${response.statusText}`); + return undefined; + } + + // Parse the response + try { + const jsonData = await response.json(); + const allReleases = Object.keys(jsonData.releases); + return semver.maxSatisfying(allReleases, `>=0.13.0 <0.14.0`) ?? undefined; + } catch (error) { + this.logger.error(`Could not parse the response from PyPI: ${error}`); + return undefined; + } + } + /** * Check whether the available runner is supported. */ @@ -401,6 +434,9 @@ export class SafeDsPythonServer { // User interaction ------------------------------------------------------------------------------------------------ + /** + * Report to the user that the runner cannot be started with the configured command. + */ private async reportBadRunnerCommand(command: string, error: unknown): Promise { const message = error instanceof Error ? error.message : String(error); this.logger.error(`Could not start runner with command "${command}": ${message}`); @@ -414,7 +450,10 @@ export class SafeDsPythonServer { } } - private async reportOutdatedRunner(version: string): Promise { + /** + * Report to the user that the runner version does not match the required version range. + */ + private async reportInvalidRunnerVersion(version: string): Promise { this.logger.error(`Installed runner version ${version} is not in range "${pipVersionRange}".`); // Show an error message to the user and offer to update the runner @@ -427,6 +466,28 @@ export class SafeDsPythonServer { } } + /** + * Report to the user that the installed runner is outdated. + * + * @returns Whether the user decided to update the runner. Returning `true` aborts the start process. + */ + private async reportOutdatedRunner(installedVersion: string, availableVersion: string): Promise { + this.logger.info( + `Installed runner version ${installedVersion} is outdated. Latest version is ${availableVersion}.`, + ); + + // Show an error message to the user and offer to update the runner + const action = await this.messaging.showInformationMessage(`A new version of the runner is available.`, { + title: 'Update runner', + }); + if (action?.title === 'Update runner') { + await this.messaging.sendNotification(UpdateRunnerNotification.type); + return true; + } else { + return false; + } + } + // TODO ------------------------------------------------------------------------------------------------------------ /** diff --git a/packages/safe-ds-vscode/src/extension/actions/installRunner.ts b/packages/safe-ds-vscode/src/extension/actions/installRunner.ts index 99f6b4885..224b5be0a 100644 --- a/packages/safe-ds-vscode/src/extension/actions/installRunner.ts +++ b/packages/safe-ds-vscode/src/extension/actions/installRunner.ts @@ -130,7 +130,7 @@ const createRunnerVirtualEnvironment = async (context: ExtensionContext, pythonC export const installRunnerInVirtualEnvironment = async (pipCommand: string): Promise => { return new Promise((resolve, reject) => { - const installCommand = `${pipCommand} install "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) => {