mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-01 12:37:37 +00:00

That way, we clearly see which things are not change, and we also clearly see which things are persistent.
147 lines
6.0 KiB
TypeScript
147 lines
6.0 KiB
TypeScript
import * as vscode from "vscode";
|
|
import * as path from "path";
|
|
import { promises as fs } from 'fs';
|
|
|
|
import { vscodeReinstallExtension, vscodeReloadWindow, log, vscodeInstallExtensionFromVsix, assert, notReentrant } from "../util";
|
|
import { Config, UpdatesChannel } from "../config";
|
|
import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces";
|
|
import { downloadArtifactWithProgressUi } from "./downloads";
|
|
import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
|
|
import { PersistentState } from "../persistent_state";
|
|
|
|
const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25;
|
|
|
|
/**
|
|
* Installs `stable` or latest `nightly` version or does nothing if the current
|
|
* extension version is what's needed according to `desiredUpdateChannel`.
|
|
*/
|
|
export async function ensureProperExtensionVersion(config: Config, state: PersistentState): Promise<never | void> {
|
|
// User has built lsp server from sources, she should manage updates manually
|
|
if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return;
|
|
|
|
const currentUpdChannel = config.installedExtensionUpdateChannel;
|
|
const desiredUpdChannel = config.updatesChannel;
|
|
|
|
if (currentUpdChannel === UpdatesChannel.Stable) {
|
|
// Release date is present only when we are on nightly
|
|
await state.installedNightlyExtensionReleaseDate.set(null);
|
|
}
|
|
|
|
if (desiredUpdChannel === UpdatesChannel.Stable) {
|
|
// VSCode should handle updates for stable channel
|
|
if (currentUpdChannel === UpdatesChannel.Stable) return;
|
|
|
|
if (!await askToDownloadProperExtensionVersion(config)) return;
|
|
|
|
await vscodeReinstallExtension(config.extensionId);
|
|
await vscodeReloadWindow(); // never returns
|
|
}
|
|
|
|
if (currentUpdChannel === UpdatesChannel.Stable) {
|
|
if (!await askToDownloadProperExtensionVersion(config)) return;
|
|
|
|
return await tryDownloadNightlyExtension(config, state);
|
|
}
|
|
|
|
const currentExtReleaseDate = state.installedNightlyExtensionReleaseDate.get();
|
|
|
|
if (currentExtReleaseDate === null) {
|
|
void vscode.window.showErrorMessage(
|
|
"Nightly release date must've been set during the installation. " +
|
|
"Did you download and install the nightly .vsix package manually?"
|
|
);
|
|
throw new Error("Nightly release date was not set in globalStorage");
|
|
}
|
|
|
|
const dateNow = new Date;
|
|
const hoursSinceLastUpdate = diffInHours(currentExtReleaseDate, dateNow);
|
|
log.debug(
|
|
"Current rust-analyzer nightly was downloaded", hoursSinceLastUpdate,
|
|
"hours ago, namely:", currentExtReleaseDate, "and now is", dateNow
|
|
);
|
|
|
|
if (hoursSinceLastUpdate < HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS) {
|
|
return;
|
|
}
|
|
if (!await askToDownloadProperExtensionVersion(config, "The installed nightly version is most likely outdated. ")) {
|
|
return;
|
|
}
|
|
|
|
await tryDownloadNightlyExtension(config, state, releaseInfo => {
|
|
assert(
|
|
currentExtReleaseDate.getTime() === state.installedNightlyExtensionReleaseDate.get()?.getTime(),
|
|
"Other active VSCode instance has reinstalled the extension"
|
|
);
|
|
|
|
if (releaseInfo.releaseDate.getTime() === currentExtReleaseDate.getTime()) {
|
|
vscode.window.showInformationMessage(
|
|
"Whoops, it appears that your nightly version is up-to-date. " +
|
|
"There might be some problems with the upcomming nightly release " +
|
|
"or you traveled too far into the future. Sorry for that 😅! "
|
|
);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
async function askToDownloadProperExtensionVersion(config: Config, reason = "") {
|
|
if (!config.askBeforeDownload) return true;
|
|
|
|
const stableOrNightly = config.updatesChannel === UpdatesChannel.Stable ? "stable" : "latest nightly";
|
|
|
|
// In case of reentering this function and showing the same info message
|
|
// (e.g. after we had shown this message, the user changed the config)
|
|
// vscode will dismiss the already shown one (i.e. return undefined).
|
|
// This behaviour is what we want, but likely it is not documented
|
|
|
|
const userResponse = await vscode.window.showInformationMessage(
|
|
reason + `Do you want to download the ${stableOrNightly} rust-analyzer extension ` +
|
|
`version and reload the window now?`,
|
|
"Download now", "Cancel"
|
|
);
|
|
return userResponse === "Download now";
|
|
}
|
|
|
|
/**
|
|
* Shutdowns the process in case of success (i.e. reloads the window) or throws an error.
|
|
*
|
|
* ACHTUNG!: this function has a crazy amount of state transitions, handling errors during
|
|
* each of them would result in a ton of code (especially accounting for cross-process
|
|
* shared mutable `globalState` access). Enforcing no reentrancy for this is best-effort.
|
|
*/
|
|
const tryDownloadNightlyExtension = notReentrant(async (
|
|
config: Config,
|
|
state: PersistentState,
|
|
shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true
|
|
): Promise<never | void> => {
|
|
const vsixSource = config.nightlyVsixSource;
|
|
try {
|
|
const releaseInfo = await fetchArtifactReleaseInfo(vsixSource.repo, vsixSource.file, vsixSource.tag);
|
|
|
|
if (!shouldDownload(releaseInfo)) return;
|
|
|
|
await downloadArtifactWithProgressUi(releaseInfo, vsixSource.file, vsixSource.dir, "nightly extension");
|
|
|
|
const vsixPath = path.join(vsixSource.dir, vsixSource.file);
|
|
|
|
await vscodeInstallExtensionFromVsix(vsixPath);
|
|
await state.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate);
|
|
await fs.unlink(vsixPath);
|
|
|
|
await vscodeReloadWindow(); // never returns
|
|
} catch (err) {
|
|
log.downloadError(err, "nightly extension", vsixSource.repo.name);
|
|
}
|
|
});
|
|
|
|
function diffInHours(a: Date, b: Date): number {
|
|
// Discard the time and time-zone information (to abstract from daylight saving time bugs)
|
|
// https://stackoverflow.com/a/15289883/9259330
|
|
|
|
const utcA = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
|
|
const utcB = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
|
|
|
|
return (utcA - utcB) / (1000 * 60 * 60);
|
|
}
|