Skip to content

Commit 601fc9d

Browse files
author
Veetaha
committed
vscode: add nightly extension installation logic
1 parent 6d2d753 commit 601fc9d

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import * as vscode from "vscode";
2+
import * as path from "path";
3+
import { promises as fs } from 'fs';
4+
5+
import { vscodeReinstallExtension, vscodeReloadWindow, log, vscodeInstallExtensionFromVsix, assert, notReentrant } from "../util";
6+
import { Config, UpdatesChannel } from "../config";
7+
import { ArtifactReleaseInfo } from "./interfaces";
8+
import { downloadArtifactWithProgressUi } from "./downloads";
9+
import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
10+
11+
const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25;
12+
13+
/**
14+
* Installs `stable` or latest `nightly` version or does nothing if the current
15+
* extension version is what's needed according to `desiredUpdateChannel`.
16+
*/
17+
export async function ensureProperExtensionVersion(config: Config): Promise<never | void> {
18+
const currentUpdChannel = config.installedExtensionUpdateChannel;
19+
const desiredUpdChannel = config.updatesChannel;
20+
21+
if (currentUpdChannel === UpdatesChannel.Stable) {
22+
// Release date is present only when we are on nightly
23+
config.installedNightlyExtensionReleaseDate.set(null);
24+
}
25+
26+
// User has built lsp server from sources, she should manage updates manually
27+
if (currentUpdChannel === null) return;
28+
29+
if (desiredUpdChannel === UpdatesChannel.Stable) {
30+
// VSCode should handle updates for stable channel
31+
if (currentUpdChannel === UpdatesChannel.Stable) return;
32+
33+
if (!await askToDownloadProperExtensionVersion(config)) return;
34+
35+
await vscodeReinstallExtension(config.extensionId);
36+
await vscodeReloadWindow(); // never returns
37+
}
38+
39+
if (currentUpdChannel === UpdatesChannel.Stable) {
40+
if (!await askToDownloadProperExtensionVersion(config)) return;
41+
42+
return await tryDownloadNightlyExtension(config);
43+
}
44+
45+
const currentExtReleaseDate = config.installedNightlyExtensionReleaseDate.get();
46+
47+
assert(currentExtReleaseDate !== null, "nightly release date must've been set during installation");
48+
49+
const hoursSinceLastUpdate = diffInHours(currentExtReleaseDate, new Date());
50+
log.debug(`Current rust-analyzer nightly was downloaded ${hoursSinceLastUpdate} hours ago`);
51+
52+
if (hoursSinceLastUpdate < HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS) {
53+
return;
54+
}
55+
if (!await askToDownloadProperExtensionVersion(config, "The installed nightly version is most likely outdated. ")) {
56+
return;
57+
}
58+
59+
await tryDownloadNightlyExtension(config, releaseInfo => {
60+
assert(
61+
currentExtReleaseDate === config.installedNightlyExtensionReleaseDate.get(),
62+
"Other active VSCode instance has reinstalled the extension"
63+
);
64+
65+
if (releaseInfo.releaseDate === currentExtReleaseDate) {
66+
vscode.window.showInformationMessage(
67+
"Whoops, it appears that your nightly version is up-to-date. " +
68+
"There might be some problems with the upcomming nightly release " +
69+
"or you traveled too far into the future. Sorry for that 😅! "
70+
);
71+
return false;
72+
}
73+
return true;
74+
});
75+
}
76+
77+
async function askToDownloadProperExtensionVersion(config: Config, reason = "") {
78+
if (!config.askBeforeDownload) return true;
79+
80+
const stableOrNightly = config.updatesChannel === UpdatesChannel.Stable ? "stable" : "latest nightly";
81+
82+
// In case of reentering this function and showing the same info message
83+
// (e.g. after we had shown this message, the user changed the config)
84+
// vscode will dismiss the already shown one (i.e. return undefined).
85+
// This behaviour is what we want, but likely it is not documented
86+
87+
const userResponse = await vscode.window.showInformationMessage(
88+
reason + `Do you want to download the ${stableOrNightly} rust-analyzer extension ` +
89+
`version and reload the window now?`,
90+
"Download now", "Cancel"
91+
);
92+
log.debug("Response: ", userResponse);
93+
return userResponse === "Download now";
94+
}
95+
96+
/**
97+
* Shutdowns the process in case of success (i.e. reloads the window) or throws an error.
98+
*/
99+
const tryDownloadNightlyExtension = notReentrant(async function tryDownloadNightlyExtension(
100+
config: Config,
101+
shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true
102+
): Promise<never | void> {
103+
const vsixSource = config.nightlyVsixSource;
104+
try {
105+
const releaseInfo = await fetchArtifactReleaseInfo(vsixSource.repo, vsixSource.file, vsixSource.tag);
106+
107+
if (!shouldDownload(releaseInfo)) return;
108+
109+
await downloadArtifactWithProgressUi(releaseInfo, vsixSource.file, vsixSource.dir, "nightly extension");
110+
111+
const vsixPath = path.join(vsixSource.dir, vsixSource.file);
112+
113+
await vscodeInstallExtensionFromVsix(vsixPath)
114+
await config.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate);
115+
await fs.unlink(vsixPath);
116+
117+
await vscodeReloadWindow(); // never returns
118+
} catch (err) {
119+
log.downloadError(err, "nightly extension", vsixSource.repo.name);
120+
}
121+
});
122+
123+
function diffInHours(a: Date, b: Date): number {
124+
// Discard the time and time-zone information (to abstract from daylight saving time bugs)
125+
// https://stackoverflow.com/a/15289883/9259330
126+
127+
const utcA = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
128+
const utcB = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
129+
130+
return (utcA - utcB) / (1000 * 60 * 60);
131+
}

0 commit comments

Comments
 (0)