mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 05:15:04 +00:00
Rewrite auto-update
Everything now happens in main.ts, in the bootstrap family of functions. The current flow is: * check everything only on extension installation. * if the user is on nightly channel, try to download the nightly extension and reload. * when we install nightly extension, we persist its release id, so that we can check if the current release is different. * if server binary was not downloaded by the current version of the extension, redownload it (we persist the version of ext that downloaded the server).
This commit is contained in:
parent
f0a1b64d7e
commit
fb6e655de8
13 changed files with 270 additions and 696 deletions
|
@ -1,15 +1,18 @@
|
|||
import * as vscode from 'vscode';
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
import { promises as fs } from "fs";
|
||||
|
||||
import * as commands from './commands';
|
||||
import { activateInlayHints } from './inlay_hints';
|
||||
import { activateStatusDisplay } from './status_display';
|
||||
import { Ctx } from './ctx';
|
||||
import { activateHighlighting } from './highlighting';
|
||||
import { ensureServerBinary } from './installation/server';
|
||||
import { Config } from './config';
|
||||
import { log } from './util';
|
||||
import { ensureProperExtensionVersion } from './installation/extension';
|
||||
import { Config, NIGHTLY_TAG } from './config';
|
||||
import { log, assert } from './util';
|
||||
import { PersistentState } from './persistent_state';
|
||||
import { fetchRelease, download } from './net';
|
||||
import { spawnSync } from 'child_process';
|
||||
|
||||
let ctx: Ctx | undefined;
|
||||
|
||||
|
@ -35,27 +38,14 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
context.subscriptions.push(defaultOnEnter);
|
||||
|
||||
const config = new Config(context);
|
||||
const state = new PersistentState(context);
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config, state).catch(log.error));
|
||||
|
||||
// Don't await the user response here, otherwise we will block the lsp server bootstrap
|
||||
void ensureProperExtensionVersion(config, state).catch(log.error);
|
||||
|
||||
const serverPath = await ensureServerBinary(config, state);
|
||||
|
||||
if (serverPath == null) {
|
||||
throw new Error(
|
||||
"Rust Analyzer Language Server is not available. " +
|
||||
"Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)."
|
||||
);
|
||||
}
|
||||
const state = new PersistentState(context.globalState);
|
||||
const serverPath = await bootstrap(config, state);
|
||||
|
||||
// Note: we try to start the server before we activate type hints so that it
|
||||
// registers its `onDidChangeDocument` handler before us.
|
||||
//
|
||||
// This a horribly, horribly wrong way to deal with this problem.
|
||||
ctx = await Ctx.create(config, state, context, serverPath);
|
||||
ctx = await Ctx.create(config, context, serverPath);
|
||||
|
||||
// Commands which invokes manually via command palette, shortcut, etc.
|
||||
ctx.registerCommand('reload', (ctx) => {
|
||||
|
@ -109,3 +99,131 @@ export async function deactivate() {
|
|||
await ctx?.client?.stop();
|
||||
ctx = undefined;
|
||||
}
|
||||
|
||||
async function bootstrap(config: Config, state: PersistentState): Promise<string> {
|
||||
await fs.mkdir(config.globalStoragePath, { recursive: true });
|
||||
|
||||
await bootstrapExtension(config, state);
|
||||
const path = await bootstrapServer(config, state);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> {
|
||||
if (config.channel === "stable") {
|
||||
if (config.extensionReleaseTag === NIGHTLY_TAG) {
|
||||
vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension.
|
||||
To switch to stable, uninstall the extension and re-install it from the marketplace`);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const lastCheck = state.lastCheck;
|
||||
const now = Date.now();
|
||||
|
||||
const anHour = 60 * 60 * 1000;
|
||||
const shouldDownloadNightly = state.releaseId === undefined || (now - (lastCheck ?? 0)) > anHour;
|
||||
|
||||
if (!shouldDownloadNightly) return;
|
||||
|
||||
const release = await fetchRelease("nightly").catch((e) => {
|
||||
log.error(e);
|
||||
if (state.releaseId === undefined) { // Show error only for the initial download
|
||||
vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
if (release === undefined || release.id === state.releaseId) return;
|
||||
|
||||
const userResponse = await vscode.window.showInformationMessage(
|
||||
"New version of rust-analyzer (nightly) is available (requires reload).",
|
||||
"Update"
|
||||
);
|
||||
if (userResponse !== "Update") return;
|
||||
|
||||
const artifact = release.assets.find(artifact => artifact.name === "rust-analyzer.vsix");
|
||||
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
|
||||
|
||||
const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix");
|
||||
await download(artifact.browser_download_url, dest, "Downloading rust-analyzer extension");
|
||||
|
||||
await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest));
|
||||
await fs.unlink(dest);
|
||||
|
||||
await state.updateReleaseId(release.id);
|
||||
await state.updateLastCheck(now);
|
||||
await vscode.commands.executeCommand("workbench.action.reloadWindow");
|
||||
}
|
||||
|
||||
async function bootstrapServer(config: Config, state: PersistentState): Promise<string> {
|
||||
const path = await getServer(config, state);
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
"Rust Analyzer Language Server is not available. " +
|
||||
"Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)."
|
||||
);
|
||||
}
|
||||
|
||||
const res = spawnSync(path, ["--version"], { encoding: 'utf8' });
|
||||
log.debug("Checked binary availability via --version", res);
|
||||
log.debug(res, "--version output:", res.output);
|
||||
if (res.status !== 0) {
|
||||
throw new Error(
|
||||
`Failed to execute ${path} --version`
|
||||
);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
async function getServer(config: Config, state: PersistentState): Promise<string | undefined> {
|
||||
const explicitPath = process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath;
|
||||
if (explicitPath) {
|
||||
if (explicitPath.startsWith("~/")) {
|
||||
return os.homedir() + explicitPath.slice("~".length);
|
||||
}
|
||||
return explicitPath;
|
||||
};
|
||||
|
||||
let binaryName: string | undefined = undefined;
|
||||
if (process.arch === "x64" || process.arch === "x32") {
|
||||
if (process.platform === "linux") binaryName = "rust-analyzer-linux";
|
||||
if (process.platform === "darwin") binaryName = "rust-analyzer-mac";
|
||||
if (process.platform === "win32") binaryName = "rust-analyzer-windows.exe";
|
||||
}
|
||||
if (binaryName === undefined) {
|
||||
vscode.window.showErrorMessage(
|
||||
"Unfortunately we don't ship binaries for your platform yet. " +
|
||||
"You need to manually clone rust-analyzer repository and " +
|
||||
"run `cargo xtask install --server` to build the language server from sources. " +
|
||||
"If you feel that your platform should be supported, please create an issue " +
|
||||
"about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " +
|
||||
"will consider it."
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const dest = path.join(config.globalStoragePath, binaryName);
|
||||
const exists = await fs.stat(dest).then(() => true, () => false);
|
||||
if (!exists) {
|
||||
await state.updateServerVersion(undefined);
|
||||
}
|
||||
|
||||
if (state.serverVersion === config.packageJsonVersion) return dest;
|
||||
|
||||
if (config.askBeforeDownload) {
|
||||
const userResponse = await vscode.window.showInformationMessage(
|
||||
`Language server version ${config.packageJsonVersion} for rust-analyzer is not installed.`,
|
||||
"Download now"
|
||||
);
|
||||
if (userResponse !== "Download now") return dest;
|
||||
}
|
||||
|
||||
const release = await fetchRelease(config.extensionReleaseTag);
|
||||
const artifact = release.assets.find(artifact => artifact.name === binaryName);
|
||||
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
|
||||
|
||||
await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
|
||||
await state.updateServerVersion(config.packageJsonVersion);
|
||||
return dest;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue