From 8203828bb081faae4cd9d39c8abe6bc073138176 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 19:54:40 +0200 Subject: vscode-prerefactor: merge two files into downloads.ts --- editors/code/src/installation/download_artifact.ts | 50 ----------- editors/code/src/installation/download_file.ts | 51 ------------ editors/code/src/installation/downloads.ts | 97 ++++++++++++++++++++++ 3 files changed, 97 insertions(+), 101 deletions(-) delete mode 100644 editors/code/src/installation/download_artifact.ts delete mode 100644 editors/code/src/installation/download_file.ts create mode 100644 editors/code/src/installation/downloads.ts (limited to 'editors/code') diff --git a/editors/code/src/installation/download_artifact.ts b/editors/code/src/installation/download_artifact.ts deleted file mode 100644 index 97e4d67c2..000000000 --- a/editors/code/src/installation/download_artifact.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as vscode from "vscode"; -import * as path from "path"; -import { promises as fs } from "fs"; - -import { ArtifactReleaseInfo } from "./interfaces"; -import { downloadFile } from "./download_file"; -import { assert } from "../util"; - -/** - * Downloads artifact from given `downloadUrl`. - * Creates `installationDir` if it is not yet created and put the artifact under - * `artifactFileName`. - * Displays info about the download progress in an info message printing the name - * of the artifact as `displayName`. - */ -export async function downloadArtifact( - { downloadUrl, releaseName }: ArtifactReleaseInfo, - artifactFileName: string, - installationDir: string, - displayName: string, -) { - await fs.mkdir(installationDir).catch(err => assert( - err?.code === "EEXIST", - `Couldn't create directory "${installationDir}" to download ` + - `${artifactFileName} artifact: ${err?.message}` - )); - - const installationPath = path.join(installationDir, artifactFileName); - - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - cancellable: false, // FIXME: add support for canceling download? - title: `Downloading ${displayName} (${releaseName})` - }, - async (progress, _cancellationToken) => { - let lastPrecentage = 0; - const filePermissions = 0o755; // (rwx, r_x, r_x) - await downloadFile(downloadUrl, installationPath, filePermissions, (readBytes, totalBytes) => { - const newPercentage = (readBytes / totalBytes) * 100; - progress.report({ - message: newPercentage.toFixed(0) + "%", - increment: newPercentage - lastPrecentage - }); - - lastPrecentage = newPercentage; - }); - } - ); -} diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts deleted file mode 100644 index ee8949d61..000000000 --- a/editors/code/src/installation/download_file.ts +++ /dev/null @@ -1,51 +0,0 @@ -import fetch from "node-fetch"; -import * as fs from "fs"; -import * as stream from "stream"; -import * as util from "util"; -import { log, assert } from "../util"; - -const pipeline = util.promisify(stream.pipeline); - -/** - * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`. - * `onProgress` callback is called on recieveing each chunk of bytes - * to track the progress of downloading, it gets the already read and total - * amount of bytes to read as its parameters. - */ -export async function downloadFile( - url: string, - destFilePath: fs.PathLike, - destFilePermissions: number, - onProgress: (readBytes: number, totalBytes: number) => void -): Promise { - const res = await fetch(url); - - if (!res.ok) { - log.error("Error", res.status, "while downloading file from", url); - log.error({ body: await res.text(), headers: res.headers }); - - throw new Error(`Got response ${res.status} when trying to download a file.`); - } - - const totalBytes = Number(res.headers.get('content-length')); - assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol"); - - log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); - - let readBytes = 0; - res.body.on("data", (chunk: Buffer) => { - readBytes += chunk.length; - onProgress(readBytes, totalBytes); - }); - - const destFileStream = fs.createWriteStream(destFilePath, { mode: destFilePermissions }); - - await pipeline(res.body, destFileStream); - return new Promise(resolve => { - destFileStream.on("close", resolve); - destFileStream.destroy(); - - // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131 - // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776 - }); -} diff --git a/editors/code/src/installation/downloads.ts b/editors/code/src/installation/downloads.ts new file mode 100644 index 000000000..7ce2e2960 --- /dev/null +++ b/editors/code/src/installation/downloads.ts @@ -0,0 +1,97 @@ +import fetch from "node-fetch"; +import * as vscode from "vscode"; +import * as path from "path"; +import * as fs from "fs"; +import * as stream from "stream"; +import * as util from "util"; +import { log, assert } from "../util"; +import { ArtifactReleaseInfo } from "./interfaces"; + +const pipeline = util.promisify(stream.pipeline); + +/** + * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`. + * `onProgress` callback is called on recieveing each chunk of bytes + * to track the progress of downloading, it gets the already read and total + * amount of bytes to read as its parameters. + */ +export async function downloadFile( + url: string, + destFilePath: fs.PathLike, + destFilePermissions: number, + onProgress: (readBytes: number, totalBytes: number) => void +): Promise { + const res = await fetch(url); + + if (!res.ok) { + log.error("Error", res.status, "while downloading file from", url); + log.error({ body: await res.text(), headers: res.headers }); + + throw new Error(`Got response ${res.status} when trying to download a file.`); + } + + const totalBytes = Number(res.headers.get('content-length')); + assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol"); + + log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); + + let readBytes = 0; + res.body.on("data", (chunk: Buffer) => { + readBytes += chunk.length; + onProgress(readBytes, totalBytes); + }); + + const destFileStream = fs.createWriteStream(destFilePath, { mode: destFilePermissions }); + + await pipeline(res.body, destFileStream); + return new Promise(resolve => { + destFileStream.on("close", resolve); + destFileStream.destroy(); + + // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131 + // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776 + }); +} + +/** + * Downloads artifact from given `downloadUrl`. + * Creates `installationDir` if it is not yet created and puts the artifact under + * `artifactFileName`. + * Displays info about the download progress in an info message printing the name + * of the artifact as `displayName`. + */ +export async function downloadArtifactWithProgressUi( + { downloadUrl, releaseName }: ArtifactReleaseInfo, + artifactFileName: string, + installationDir: string, + displayName: string, +) { + await fs.promises.mkdir(installationDir).catch(err => assert( + err?.code === "EEXIST", + `Couldn't create directory "${installationDir}" to download ` + + `${artifactFileName} artifact: ${err?.message}` + )); + + const installationPath = path.join(installationDir, artifactFileName); + + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + cancellable: false, // FIXME: add support for canceling download? + title: `Downloading rust-analyzer ${displayName} (${releaseName})` + }, + async (progress, _cancellationToken) => { + let lastPrecentage = 0; + const filePermissions = 0o755; // (rwx, r_x, r_x) + await downloadFile(downloadUrl, installationPath, filePermissions, (readBytes, totalBytes) => { + const newPercentage = (readBytes / totalBytes) * 100; + progress.report({ + message: newPercentage.toFixed(0) + "%", + increment: newPercentage - lastPrecentage + }); + + lastPrecentage = newPercentage; + }); + } + ); +} -- cgit v1.2.3 From 98b2a942d1c67f80a67a5779ecaa482f84c3a30d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 19:54:54 +0200 Subject: vscode-prerefactor: add some utility functions --- editors/code/src/util.ts | 69 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 6 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index 95a5f1227..2bfc145e6 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -1,5 +1,6 @@ import * as lc from "vscode-languageclient"; import * as vscode from "vscode"; +import { promises as dns } from "dns"; import { strict as nativeAssert } from "assert"; export function assert(condition: boolean, explanation: string): asserts condition { @@ -11,21 +12,40 @@ export function assert(condition: boolean, explanation: string): asserts conditi } } -export const log = { - enabled: true, +export const log = new class { + private enabled = true; + + setEnabled(yes: boolean): void { + log.enabled = yes; + } + debug(message?: any, ...optionalParams: any[]): void { if (!log.enabled) return; // eslint-disable-next-line no-console console.log(message, ...optionalParams); - }, + } + error(message?: any, ...optionalParams: any[]): void { if (!log.enabled) return; debugger; // eslint-disable-next-line no-console console.error(message, ...optionalParams); - }, - setEnabled(yes: boolean): void { - log.enabled = yes; + } + + downloadError(err: Error, artifactName: string, repoName: string) { + vscode.window.showErrorMessage( + `Failed to download the rust-analyzer ${artifactName} from ${repoName} ` + + `GitHub repository: ${err.message}` + ); + log.error(err); + dns.resolve('example.com').then( + addrs => log.debug("DNS resolution for example.com was successful", addrs), + err => log.error( + "DNS resolution for example.com failed, " + + "there might be an issue with Internet availability", + err + ) + ); } }; @@ -66,6 +86,17 @@ function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } +export function notReentrant( + fn: (this: TThis, ...params: TParams) => Promise +): typeof fn { + let entered = false; + return function(...params) { + assert(!entered, `Reentrancy invariant for ${fn.name} is violated`); + entered = true; + return fn.apply(this, params).finally(() => entered = false); + }; +} + export type RustDocument = vscode.TextDocument & { languageId: "rust" }; export type RustEditor = vscode.TextEditor & { document: RustDocument; id: string }; @@ -79,3 +110,29 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { return isRustDocument(editor.document); } + +/** + * @param extensionId The canonical extension identifier in the form of: `publisher.name` + */ +export async function vscodeReinstallExtension(extensionId: string) { + // Unfortunately there is no straightforward way as of now, these commands + // were found in vscode source code. + + log.debug("Uninstalling extension", extensionId); + await vscode.commands.executeCommand("workbench.extensions.uninstallExtension", extensionId); + log.debug("Installing extension", extensionId); + await vscode.commands.executeCommand("workbench.extensions.installExtension", extensionId); +} + +export async function vscodeReloadWindow(): Promise { + await vscode.commands.executeCommand("workbench.action.reloadWindow"); + + assert(false, "unreachable"); +} + +export async function vscodeInstallExtensionFromVsix(vsixPath: string) { + await vscode.commands.executeCommand( + "workbench.extensions.installExtension", + vscode.Uri.file(vsixPath) + ); +} -- cgit v1.2.3 From 0f826aec8280cf1593e1b1e265cced6f7e5d84d7 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 19:55:26 +0200 Subject: vscode: get release date from release info --- .../src/installation/fetch_artifact_release_info.ts | 3 +++ editors/code/src/installation/interfaces.ts | 18 ++++-------------- 2 files changed, 7 insertions(+), 14 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/installation/fetch_artifact_release_info.ts b/editors/code/src/installation/fetch_artifact_release_info.ts index b1b5a3485..1ad3b8338 100644 --- a/editors/code/src/installation/fetch_artifact_release_info.ts +++ b/editors/code/src/installation/fetch_artifact_release_info.ts @@ -59,12 +59,15 @@ export async function fetchArtifactReleaseInfo( return { releaseName: release.name, + releaseDate: new Date(release.published_at), downloadUrl: artifact.browser_download_url }; // We omit declaration of tremendous amount of fields that we are not using here interface GithubRelease { name: string; + // eslint-disable-next-line camelcase + published_at: string; assets: Array<{ name: string; // eslint-disable-next-line camelcase diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts index 50b635921..1a8ea0884 100644 --- a/editors/code/src/installation/interfaces.ts +++ b/editors/code/src/installation/interfaces.ts @@ -1,5 +1,3 @@ -import * as vscode from "vscode"; - export interface GithubRepo { name: string; owner: string; @@ -9,6 +7,7 @@ export interface GithubRepo { * Metadata about particular artifact retrieved from GitHub releases. */ export interface ArtifactReleaseInfo { + releaseDate: Date; releaseName: string; downloadUrl: string; } @@ -42,6 +41,9 @@ export namespace ArtifactSource { */ repo: GithubRepo; + + // FIXME: add installationPath: string; + /** * Directory on the filesystem where the bundled binary is stored. */ @@ -57,17 +59,5 @@ export namespace ArtifactSource { * Tag of github release that denotes a version required by this extension. */ tag: string; - - /** - * Object that provides `get()/update()` operations to store metadata - * about the actual binary, e.g. its actual version. - */ - storage: vscode.Memento; - - /** - * Ask for the user permission before downloading the artifact. - */ - askBeforeDownload: boolean; } - } -- cgit v1.2.3 From bc98c02dd0cdc33e3b34c0054c1570702e198d9b Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 19:56:51 +0200 Subject: vscode: prepare package.json for nightlies --- editors/code/package-lock.json | 2 +- editors/code/package.json | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) (limited to 'editors/code') diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index b07964546..575dc7c4a 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -1,6 +1,6 @@ { "name": "rust-analyzer", - "version": "0.2.20200211-dev", + "version": "0.2.20200309-nightly", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/editors/code/package.json b/editors/code/package.json index 3aaae357a..faf10528d 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -6,7 +6,7 @@ "private": true, "icon": "icon.png", "//": "The real version is in release.yaml, this one just needs to be bigger", - "version": "0.2.20200211-dev", + "version": "0.2.20200309-nightly", "publisher": "matklad", "repository": { "url": "https://github.com/rust-analyzer/rust-analyzer.git", @@ -219,6 +219,19 @@ } } }, + "rust-analyzer.updates.channel": { + "type": "string", + "enum": [ + "stable", + "nightly" + ], + "default": "stable", + "markdownEnumDescriptions": [ + "`\"stable\"` updates are shipped weekly, they don't contain cutting-edge features from VSCode proposed APIs but have less bugs in general", + "`\"nightly\"` updates are shipped daily, they contain cutting-edge features and latest bug fixes. These releases help us get your feedback very quickly and speed up rust-analyzer development **drastically**" + ], + "markdownDescription": "Choose `\"nightly\"` updates to get the latest features and bug fixes every day. While `\"stable\"` releases occur weekly and don't contain cutting-edge features from VSCode proposed APIs" + }, "rust-analyzer.updates.askBeforeDownload": { "type": "boolean", "default": true, @@ -235,7 +248,7 @@ "string" ], "default": null, - "description": "Path to rust-analyzer executable (points to bundled binary by default)" + "description": "Path to rust-analyzer executable (points to bundled binary by default). If this is set, then \"rust-analyzer.updates.channel\" setting is not used" }, "rust-analyzer.excludeGlobs": { "type": "array", -- cgit v1.2.3 From 6d2d75367763686286779dd4b595a575c6ea689e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 19:57:13 +0200 Subject: vscode: prepare config for nightlies --- editors/code/src/config.ts | 111 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 15 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 6db073bec..e2b0f6f84 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -23,22 +23,36 @@ export interface CargoFeatures { allFeatures: boolean; features: string[]; } + +export const enum UpdatesChannel { + Stable = "stable", + Nightly = "nightly" +} + +export const NIGHTLY_TAG = "nightly"; export class Config { - private static readonly rootSection = "rust-analyzer"; - private static readonly requiresReloadOpts = [ + readonly extensionId = "matklad.rust-analyzer"; + + private readonly rootSection = "rust-analyzer"; + private readonly requiresReloadOpts = [ "cargoFeatures", "cargo-watch", "highlighting.semanticTokens", "inlayHints", ] - .map(opt => `${Config.rootSection}.${opt}`); + .map(opt => `${this.rootSection}.${opt}`); - private static readonly extensionVersion: string = (() => { + /** + * Either `nightly` or `YYYY-MM-DD` (i.e. `stable` release) + */ + private readonly extensionVersion: string = (() => { const packageJsonVersion = vscode .extensions - .getExtension("matklad.rust-analyzer")! + .getExtension(this.extensionId)! .packageJSON - .version as string; // n.n.YYYYMMDD + .version as string; + + if (packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG; const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/; const [, yyyy, mm, dd] = packageJsonVersion.match(realVersionRegexp)!; @@ -54,7 +68,7 @@ export class Config { } private refreshConfig() { - this.cfg = vscode.workspace.getConfiguration(Config.rootSection); + this.cfg = vscode.workspace.getConfiguration(this.rootSection); const enableLogging = this.cfg.get("trace.extension") as boolean; log.setEnabled(enableLogging); log.debug("Using configuration:", this.cfg); @@ -63,7 +77,7 @@ export class Config { private async onConfigChange(event: vscode.ConfigurationChangeEvent) { this.refreshConfig(); - const requiresReloadOpt = Config.requiresReloadOpts.find( + const requiresReloadOpt = this.requiresReloadOpts.find( opt => event.affectsConfiguration(opt) ); @@ -121,8 +135,16 @@ export class Config { } } + get installedExtensionUpdateChannel() { + if (this.serverPath !== null) return null; + + return this.extensionVersion === NIGHTLY_TAG + ? UpdatesChannel.Nightly + : UpdatesChannel.Stable; + } + get serverSource(): null | ArtifactSource { - const serverPath = RA_LSP_DEBUG ?? this.cfg.get("serverPath"); + const serverPath = RA_LSP_DEBUG ?? this.serverPath; if (serverPath) { return { @@ -135,23 +157,47 @@ export class Config { if (!prebuiltBinaryName) return null; + return this.createGithubReleaseSource( + prebuiltBinaryName, + this.extensionVersion + ); + } + + private createGithubReleaseSource(file: string, tag: string): ArtifactSource.GithubRelease { return { type: ArtifactSource.Type.GithubRelease, + file, + tag, dir: this.ctx.globalStoragePath, - file: prebuiltBinaryName, - storage: this.ctx.globalState, - tag: Config.extensionVersion, - askBeforeDownload: this.cfg.get("updates.askBeforeDownload") as boolean, repo: { name: "rust-analyzer", - owner: "rust-analyzer", + owner: "rust-analyzer" } - }; + } } + get nightlyVsixSource(): ArtifactSource.GithubRelease { + return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG); + } + + readonly installedNightlyExtensionReleaseDate = new DateStorage( + "rust-analyzer-installed-nightly-extension-release-date", + this.ctx.globalState + ); + readonly serverReleaseDate = new DateStorage( + "rust-analyzer-server-release-date", + this.ctx.globalState + ); + readonly serverReleaseTag = new StringStorage( + "rust-analyzer-release-tag", this.ctx.globalState + ); + // We don't do runtime config validation here for simplicity. More on stackoverflow: // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension + private get serverPath() { return this.cfg.get("serverPath") as null | string; } + get updatesChannel() { return this.cfg.get("updates.channel") as UpdatesChannel; } + get askBeforeDownload() { return this.cfg.get("updates.askBeforeDownload") as boolean; } get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; } get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } @@ -189,3 +235,38 @@ export class Config { // for internal use get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } } + +export class StringStorage { + constructor( + private readonly key: string, + private readonly storage: vscode.Memento + ) {} + + get(): null | string { + const tag = this.storage.get(this.key, null); + log.debug(this.key, "==", tag); + return tag; + } + async set(tag: string) { + log.debug(this.key, "=", tag); + await this.storage.update(this.key, tag); + } +} +export class DateStorage { + + constructor( + private readonly key: string, + private readonly storage: vscode.Memento + ) {} + + get(): null | Date { + const date = this.storage.get(this.key, null); + log.debug(this.key, "==", date); + return date ? new Date(date) : null; + } + + async set(date: null | Date) { + log.debug(this.key, "=", date); + await this.storage.update(this.key, date); + } +} -- cgit v1.2.3 From 601fc9d1abf52c16356d49b6c540b31718e62b88 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 19:57:34 +0200 Subject: vscode: add nightly extension installation logic --- editors/code/src/installation/extension.ts | 131 +++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 editors/code/src/installation/extension.ts (limited to 'editors/code') diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts new file mode 100644 index 000000000..7709cd3cd --- /dev/null +++ b/editors/code/src/installation/extension.ts @@ -0,0 +1,131 @@ +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 } from "./interfaces"; +import { downloadArtifactWithProgressUi } from "./downloads"; +import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; + +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): Promise { + const currentUpdChannel = config.installedExtensionUpdateChannel; + const desiredUpdChannel = config.updatesChannel; + + if (currentUpdChannel === UpdatesChannel.Stable) { + // Release date is present only when we are on nightly + config.installedNightlyExtensionReleaseDate.set(null); + } + + // User has built lsp server from sources, she should manage updates manually + if (currentUpdChannel === null) return; + + 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); + } + + const currentExtReleaseDate = config.installedNightlyExtensionReleaseDate.get(); + + assert(currentExtReleaseDate !== null, "nightly release date must've been set during installation"); + + const hoursSinceLastUpdate = diffInHours(currentExtReleaseDate, new Date()); + log.debug(`Current rust-analyzer nightly was downloaded ${hoursSinceLastUpdate} hours ago`); + + 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, releaseInfo => { + assert( + currentExtReleaseDate === config.installedNightlyExtensionReleaseDate.get(), + "Other active VSCode instance has reinstalled the extension" + ); + + if (releaseInfo.releaseDate === currentExtReleaseDate) { + 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" + ); + log.debug("Response: ", userResponse); + return userResponse === "Download now"; +} + +/** + * Shutdowns the process in case of success (i.e. reloads the window) or throws an error. + */ +const tryDownloadNightlyExtension = notReentrant(async function tryDownloadNightlyExtension( + config: Config, + shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true +): Promise { + 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 config.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); +} -- cgit v1.2.3 From 1e73811fbe634efec90a3e009a84fd8dda9f5697 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 19:57:55 +0200 Subject: vscode: amend server installation logic to account for nightlies --- editors/code/src/commands/server_version.ts | 3 +- editors/code/src/installation/server.ts | 97 ++++++++++++++--------------- editors/code/src/main.ts | 9 ++- 3 files changed, 57 insertions(+), 52 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts index 421301b42..c4d84b443 100644 --- a/editors/code/src/commands/server_version.ts +++ b/editors/code/src/commands/server_version.ts @@ -5,7 +5,7 @@ import { spawnSync } from 'child_process'; export function serverVersion(ctx: Ctx): Cmd { return async () => { - const binaryPath = await ensureServerBinary(ctx.config.serverSource); + const binaryPath = await ensureServerBinary(ctx.config); if (binaryPath == null) { throw new Error( @@ -18,4 +18,3 @@ export function serverVersion(ctx: Ctx): Cmd { vscode.window.showInformationMessage('rust-analyzer version : ' + version); }; } - diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts index ef1c45ff6..345f30d47 100644 --- a/editors/code/src/installation/server.ts +++ b/editors/code/src/installation/server.ts @@ -1,14 +1,16 @@ import * as vscode from "vscode"; import * as path from "path"; -import { promises as dns } from "dns"; import { spawnSync } from "child_process"; import { ArtifactSource } from "./interfaces"; import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; -import { downloadArtifact } from "./download_artifact"; +import { downloadArtifactWithProgressUi } from "./downloads"; import { log, assert } from "../util"; +import { Config, NIGHTLY_TAG } from "../config"; + +export async function ensureServerBinary(config: Config): Promise { + const source = config.serverSource; -export async function ensureServerBinary(source: null | ArtifactSource): Promise { if (!source) { vscode.window.showErrorMessage( "Unfortunately we don't ship binaries for your platform yet. " + @@ -35,18 +37,11 @@ export async function ensureServerBinary(source: null | ArtifactSource): Promise return null; } case ArtifactSource.Type.GithubRelease: { - const prebuiltBinaryPath = path.join(source.dir, source.file); - - const installedVersion: null | string = getServerVersion(source.storage); - const requiredVersion: string = source.tag; - - log.debug("Installed version:", installedVersion, "required:", requiredVersion); - - if (isBinaryAvailable(prebuiltBinaryPath) && installedVersion === requiredVersion) { - return prebuiltBinaryPath; + if (!shouldDownloadServer(source, config)) { + return path.join(source.dir, source.file); } - if (source.askBeforeDownload) { + if (config.askBeforeDownload) { const userResponse = await vscode.window.showInformationMessage( `Language server version ${source.tag} for rust-analyzer is not installed. ` + "Do you want to download it now?", @@ -55,38 +50,53 @@ export async function ensureServerBinary(source: null | ArtifactSource): Promise if (userResponse !== "Download now") return null; } - if (!await downloadServer(source)) return null; - - return prebuiltBinaryPath; + return await downloadServer(source, config); } } } -async function downloadServer(source: ArtifactSource.GithubRelease): Promise { +function shouldDownloadServer( + source: ArtifactSource.GithubRelease, + config: Config +): boolean { + if (!isBinaryAvailable(path.join(source.dir, source.file))) return true; + + const installed = { + tag: config.serverReleaseTag.get(), + date: config.serverReleaseDate.get() + }; + const required = { + tag: source.tag, + date: config.installedNightlyExtensionReleaseDate.get() + }; + + log.debug("Installed server:", installed, "required:", required); + + if (required.tag !== NIGHTLY_TAG || installed.tag !== NIGHTLY_TAG) { + return required.tag !== installed.tag; + } + + assert(required.date !== null, "Extension release date should have been saved during its installation"); + assert(installed.date !== null, "Server release date should have been saved during its installation"); + + return installed.date.getTime() !== required.date.getTime(); +} + +async function downloadServer( + source: ArtifactSource.GithubRelease, + config: Config, +): Promise { try { const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); - await downloadArtifact(releaseInfo, source.file, source.dir, "language server"); - await setServerVersion(source.storage, releaseInfo.releaseName); + await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); + await Promise.all([ + config.serverReleaseTag.set(releaseInfo.releaseName), + config.serverReleaseDate.set(releaseInfo.releaseDate) + ]); } catch (err) { - vscode.window.showErrorMessage( - `Failed to download language server from ${source.repo.name} ` + - `GitHub repository: ${err.message}` - ); - - log.error(err); - - dns.resolve('example.com').then( - addrs => log.debug("DNS resolution for example.com was successful", addrs), - err => { - log.error( - "DNS resolution for example.com failed, " + - "there might be an issue with Internet availability" - ); - log.error(err); - } - ); - return false; + log.downloadError(err, "language server", source.repo.name); + return null; } const binaryPath = path.join(source.dir, source.file); @@ -101,7 +111,7 @@ async function downloadServer(source: ArtifactSource.GithubRelease): Promise("server-version", null); - log.debug("Get server-version:", version); - return version; -} - -async function setServerVersion(storage: vscode.Memento, version: string): Promise { - log.debug("Set server-version:", version); - await storage.update("server-version", version.toString()); -} diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index ecf53cf77..ee67c750c 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -8,6 +8,7 @@ import { activateHighlighting } from './highlighting'; import { ensureServerBinary } from './installation/server'; import { Config } from './config'; import { log } from './util'; +import { ensureProperExtensionVersion } from './installation/extension'; let ctx: Ctx | undefined; @@ -34,7 +35,13 @@ export async function activate(context: vscode.ExtensionContext) { const config = new Config(context); - const serverPath = await ensureServerBinary(config.serverSource); + vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config)); + + // Don't await the user response here, otherwise we will block the lsp server bootstrap + void ensureProperExtensionVersion(config); + + const serverPath = await ensureServerBinary(config); + if (serverPath == null) { throw new Error( "Rust Analyzer Language Server is not available. " + -- cgit v1.2.3 From 7e6b1a60c3d7b20e1b4cee2ab210b617e359a002 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 20:04:11 +0200 Subject: vscode: npm run fix --- editors/code/src/config.ts | 6 +++--- editors/code/src/installation/extension.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index e2b0f6f84..b5c07876b 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -173,7 +173,7 @@ export class Config { name: "rust-analyzer", owner: "rust-analyzer" } - } + }; } get nightlyVsixSource(): ArtifactSource.GithubRelease { @@ -240,7 +240,7 @@ export class StringStorage { constructor( private readonly key: string, private readonly storage: vscode.Memento - ) {} + ) { } get(): null | string { const tag = this.storage.get(this.key, null); @@ -257,7 +257,7 @@ export class DateStorage { constructor( private readonly key: string, private readonly storage: vscode.Memento - ) {} + ) { } get(): null | Date { const date = this.storage.get(this.key, null); diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index 7709cd3cd..7eab68852 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -110,7 +110,7 @@ const tryDownloadNightlyExtension = notReentrant(async function tryDownloadNight const vsixPath = path.join(vsixSource.dir, vsixSource.file); - await vscodeInstallExtensionFromVsix(vsixPath) + await vscodeInstallExtensionFromVsix(vsixPath); await config.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate); await fs.unlink(vsixPath); -- cgit v1.2.3 From 4d17152b31b27a8c851b4b1abaff359044ee9d96 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 20:14:55 +0200 Subject: vscode: make bailing out on custom serverPath more evident --- editors/code/src/config.ts | 12 +++++------- editors/code/src/installation/extension.ts | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index b5c07876b..345c9e21a 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -45,7 +45,7 @@ export class Config { /** * Either `nightly` or `YYYY-MM-DD` (i.e. `stable` release) */ - private readonly extensionVersion: string = (() => { + readonly extensionReleaseTag: string = (() => { const packageJsonVersion = vscode .extensions .getExtension(this.extensionId)! @@ -135,10 +135,8 @@ export class Config { } } - get installedExtensionUpdateChannel() { - if (this.serverPath !== null) return null; - - return this.extensionVersion === NIGHTLY_TAG + get installedExtensionUpdateChannel(): UpdatesChannel { + return this.extensionReleaseTag === NIGHTLY_TAG ? UpdatesChannel.Nightly : UpdatesChannel.Stable; } @@ -159,7 +157,7 @@ export class Config { return this.createGithubReleaseSource( prebuiltBinaryName, - this.extensionVersion + this.extensionReleaseTag ); } @@ -195,7 +193,7 @@ export class Config { // We don't do runtime config validation here for simplicity. More on stackoverflow: // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension - private get serverPath() { return this.cfg.get("serverPath") as null | string; } + get serverPath() { return this.cfg.get("serverPath") as null | string; } get updatesChannel() { return this.cfg.get("updates.channel") as UpdatesChannel; } get askBeforeDownload() { return this.cfg.get("updates.askBeforeDownload") as boolean; } get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; } diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index 7eab68852..a0925acaa 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -15,6 +15,9 @@ const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; * extension version is what's needed according to `desiredUpdateChannel`. */ export async function ensureProperExtensionVersion(config: Config): Promise { + // User has built lsp server from sources, she should manage updates manually + if (config.serverPath !== null) return; + const currentUpdChannel = config.installedExtensionUpdateChannel; const desiredUpdChannel = config.updatesChannel; @@ -23,9 +26,6 @@ export async function ensureProperExtensionVersion(config: Config): Promise Date: Mon, 9 Mar 2020 20:17:50 +0200 Subject: vscode: put comma back --- editors/code/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 345c9e21a..a05d8ac06 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -169,7 +169,7 @@ export class Config { dir: this.ctx.globalStoragePath, repo: { name: "rust-analyzer", - owner: "rust-analyzer" + owner: "rust-analyzer", } }; } -- cgit v1.2.3 From bc87d6de86a2d67febe7e4e21347af9c92dc7552 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 20:28:12 +0200 Subject: vscode-postrefactor: global storages --- editors/code/src/config.ts | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index a05d8ac06..5371384ba 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -186,8 +186,8 @@ export class Config { "rust-analyzer-server-release-date", this.ctx.globalState ); - readonly serverReleaseTag = new StringStorage( - "rust-analyzer-release-tag", this.ctx.globalState + readonly serverReleaseTag = new Storage( + "rust-analyzer-release-tag", this.ctx.globalState, null ); // We don't do runtime config validation here for simplicity. More on stackoverflow: @@ -234,37 +234,36 @@ export class Config { get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } } -export class StringStorage { +export class Storage { constructor( private readonly key: string, - private readonly storage: vscode.Memento + private readonly storage: vscode.Memento, + private readonly defaultVal: T ) { } - get(): null | string { - const tag = this.storage.get(this.key, null); - log.debug(this.key, "==", tag); - return tag; + get(): T { + const val = this.storage.get(this.key, this.defaultVal); + log.debug(this.key, "==", val); + return val; } - async set(tag: string) { - log.debug(this.key, "=", tag); - await this.storage.update(this.key, tag); + async set(val: T) { + log.debug(this.key, "=", val); + await this.storage.update(this.key, val); } } export class DateStorage { + inner: Storage; - constructor( - private readonly key: string, - private readonly storage: vscode.Memento - ) { } + constructor(key: string, storage: vscode.Memento) { + this.inner = new Storage(key, storage, null); + } get(): null | Date { - const date = this.storage.get(this.key, null); - log.debug(this.key, "==", date); - return date ? new Date(date) : null; + const dateStr = this.inner.get(); + return dateStr ? new Date(dateStr) : null; } async set(date: null | Date) { - log.debug(this.key, "=", date); - await this.storage.update(this.key, date); + await this.inner.set(date ? date.toString() : null); } } -- cgit v1.2.3 From c3ee8b10b89e6e0b1d80fd419291e5d73ff49a41 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 20:35:21 +0200 Subject: vscode-postrefactor: eliminate my-mistake floating promise @matklad --- editors/code/src/installation/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index a0925acaa..d0782ad13 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -23,7 +23,7 @@ export async function ensureProperExtensionVersion(config: Config): Promise Date: Mon, 9 Mar 2020 20:41:23 +0200 Subject: vscode-postrefactor: compare dates by value, not by reference --- editors/code/src/installation/extension.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index d0782ad13..3a1481a89 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -58,11 +58,11 @@ export async function ensureProperExtensionVersion(config: Config): Promise { assert( - currentExtReleaseDate === config.installedNightlyExtensionReleaseDate.get(), + currentExtReleaseDate.getTime() === config.installedNightlyExtensionReleaseDate.get()?.getTime(), "Other active VSCode instance has reinstalled the extension" ); - if (releaseInfo.releaseDate === currentExtReleaseDate) { + 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 " + -- cgit v1.2.3 From 028a4aa99f7f6829cab2d8968ef6b5976e9b3ee9 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 20:53:01 +0200 Subject: vscode-postrefactor: unhandled promise rejections shall not pass --- editors/code/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index ee67c750c..4f46345d6 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -35,7 +35,7 @@ export async function activate(context: vscode.ExtensionContext) { const config = new Config(context); - vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config)); + vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config).catch(log.error)); // Don't await the user response here, otherwise we will block the lsp server bootstrap void ensureProperExtensionVersion(config); -- cgit v1.2.3 From abee777b6ec842eef6609475b2bf623e9490fd7e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 20:57:20 +0200 Subject: vscode-postrefactor: remove remainders of debug logging --- editors/code/src/installation/extension.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index 3a1481a89..eb6acc341 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -89,7 +89,6 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") `version and reload the window now?`, "Download now", "Cancel" ); - log.debug("Response: ", userResponse); return userResponse === "Download now"; } -- cgit v1.2.3 From c2b0fffe3d08d4819ca073c5821869b38784328d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 21:02:19 +0200 Subject: vscode-postrefactor: add achtung comment --- editors/code/src/installation/extension.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'editors/code') diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index eb6acc341..87a587403 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -94,6 +94,10 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") /** * 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 reentrancy for this is best-effort. */ const tryDownloadNightlyExtension = notReentrant(async function tryDownloadNightlyExtension( config: Config, -- cgit v1.2.3 From 607d017229ce398bd7fef43aa5f4ab35914e6f31 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 9 Mar 2020 21:15:07 +0200 Subject: vscode-postrefactor: unhandled promise rejections shall not pass 2 --- editors/code/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 4f46345d6..8a7c81727 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -38,7 +38,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config).catch(log.error)); // Don't await the user response here, otherwise we will block the lsp server bootstrap - void ensureProperExtensionVersion(config); + void ensureProperExtensionVersion(config).catch(log.error); const serverPath = await ensureServerBinary(config); -- cgit v1.2.3 From 7f02d4657b796a438e441e107d4fb1906ec1ed7b Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 14 Mar 2020 02:00:34 +0200 Subject: vscode-postrefactor: minor config refactorings --- editors/code/src/config.ts | 5 +++-- editors/code/src/installation/extension.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 5371384ba..93f72409d 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -35,6 +35,7 @@ export class Config { private readonly rootSection = "rust-analyzer"; private readonly requiresReloadOpts = [ + "serverPath", "cargoFeatures", "cargo-watch", "highlighting.semanticTokens", @@ -50,7 +51,7 @@ export class Config { .extensions .getExtension(this.extensionId)! .packageJSON - .version as string; + .version as string; // n.n.YYYYMMDD[-nightly] if (packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG; @@ -193,7 +194,7 @@ export class Config { // We don't do runtime config validation here for simplicity. More on stackoverflow: // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension - get serverPath() { return this.cfg.get("serverPath") as null | string; } + private get serverPath() { return this.cfg.get("serverPath") as null | string; } get updatesChannel() { return this.cfg.get("updates.channel") as UpdatesChannel; } get askBeforeDownload() { return this.cfg.get("updates.askBeforeDownload") as boolean; } get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; } diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index 87a587403..2022d090d 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -4,7 +4,7 @@ import { promises as fs } from 'fs'; import { vscodeReinstallExtension, vscodeReloadWindow, log, vscodeInstallExtensionFromVsix, assert, notReentrant } from "../util"; import { Config, UpdatesChannel } from "../config"; -import { ArtifactReleaseInfo } from "./interfaces"; +import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces"; import { downloadArtifactWithProgressUi } from "./downloads"; import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; @@ -16,7 +16,7 @@ const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; */ export async function ensureProperExtensionVersion(config: Config): Promise { // User has built lsp server from sources, she should manage updates manually - if (config.serverPath !== null) return; + if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return; const currentUpdChannel = config.installedExtensionUpdateChannel; const desiredUpdChannel = config.updatesChannel; -- cgit v1.2.3 From d7b46e0527cd7b52845f37cffc57cbae4ba0b945 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 14 Mar 2020 02:01:28 +0200 Subject: vscode-postrefactor: enforcing more reentrancy --- editors/code/src/installation/extension.ts | 2 +- editors/code/src/installation/server.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index 2022d090d..f6dd20d82 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -97,7 +97,7 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") * * 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 reentrancy for this is best-effort. + * shared mutable `globalState` access). Enforcing no reentrancy for this is best-effort. */ const tryDownloadNightlyExtension = notReentrant(async function tryDownloadNightlyExtension( config: Config, diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts index 345f30d47..f35958474 100644 --- a/editors/code/src/installation/server.ts +++ b/editors/code/src/installation/server.ts @@ -5,7 +5,7 @@ import { spawnSync } from "child_process"; import { ArtifactSource } from "./interfaces"; import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; import { downloadArtifactWithProgressUi } from "./downloads"; -import { log, assert } from "../util"; +import { log, assert, notReentrant } from "../util"; import { Config, NIGHTLY_TAG } from "../config"; export async function ensureServerBinary(config: Config): Promise { @@ -82,7 +82,10 @@ function shouldDownloadServer( return installed.date.getTime() !== required.date.getTime(); } -async function downloadServer( +/** + * Enforcing no reentrancy for this is best-effort. + */ +const downloadServer = notReentrant(async function downloadServer( source: ArtifactSource.GithubRelease, config: Config, ): Promise { @@ -112,7 +115,7 @@ async function downloadServer( ); return binaryPath; -} +}); function isBinaryAvailable(binaryPath: string): boolean { const res = spawnSync(binaryPath, ["--version"]); -- cgit v1.2.3 From 5e32a67c83d46862db0166c263efc65b6ecd5b52 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 14 Mar 2020 03:01:14 +0200 Subject: vscode-postrefactor: more logging and better error handling --- editors/code/package-lock.json | 2 +- editors/code/src/config.ts | 36 ++++++++++++++---------------- editors/code/src/installation/extension.ts | 16 ++++++++++--- 3 files changed, 31 insertions(+), 23 deletions(-) (limited to 'editors/code') diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 575dc7c4a..1b497edd7 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -1,6 +1,6 @@ { "name": "rust-analyzer", - "version": "0.2.20200309-nightly", + "version": "0.2.20200309", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 93f72409d..f63e1d20e 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -1,7 +1,7 @@ import * as os from "os"; import * as vscode from 'vscode'; import { ArtifactSource } from "./installation/interfaces"; -import { log } from "./util"; +import { log, vscodeReloadWindow } from "./util"; const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; @@ -43,20 +43,20 @@ export class Config { ] .map(opt => `${this.rootSection}.${opt}`); + readonly packageJsonVersion = vscode + .extensions + .getExtension(this.extensionId)! + .packageJSON + .version as string; // n.n.YYYYMMDD[-nightly] + /** * Either `nightly` or `YYYY-MM-DD` (i.e. `stable` release) */ readonly extensionReleaseTag: string = (() => { - const packageJsonVersion = vscode - .extensions - .getExtension(this.extensionId)! - .packageJSON - .version as string; // n.n.YYYYMMDD[-nightly] - - if (packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG; + if (this.packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG; const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/; - const [, yyyy, mm, dd] = packageJsonVersion.match(realVersionRegexp)!; + const [, yyyy, mm, dd] = this.packageJsonVersion.match(realVersionRegexp)!; return `${yyyy}-${mm}-${dd}`; })(); @@ -72,7 +72,10 @@ export class Config { this.cfg = vscode.workspace.getConfiguration(this.rootSection); const enableLogging = this.cfg.get("trace.extension") as boolean; log.setEnabled(enableLogging); - log.debug("Using configuration:", this.cfg); + log.debug( + "Extension version:", this.packageJsonVersion, + "using configuration:", this.cfg + ); } private async onConfigChange(event: vscode.ConfigurationChangeEvent) { @@ -90,7 +93,7 @@ export class Config { ); if (userResponse === "Reload now") { - vscode.commands.executeCommand("workbench.action.reloadWindow"); + await vscodeReloadWindow(); } } @@ -180,16 +183,11 @@ export class Config { } readonly installedNightlyExtensionReleaseDate = new DateStorage( - "rust-analyzer-installed-nightly-extension-release-date", + "installed-nightly-extension-release-date", this.ctx.globalState ); - readonly serverReleaseDate = new DateStorage( - "rust-analyzer-server-release-date", - this.ctx.globalState - ); - readonly serverReleaseTag = new Storage( - "rust-analyzer-release-tag", this.ctx.globalState, null - ); + readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState); + readonly serverReleaseTag = new Storage("server-release-tag", this.ctx.globalState, null); // We don't do runtime config validation here for simplicity. More on stackoverflow: // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index f6dd20d82..0d69b8d0c 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -44,10 +44,20 @@ export async function ensureProperExtensionVersion(config: Config): Promise Date: Sat, 14 Mar 2020 03:20:39 +0200 Subject: vscode: sync package-lock.json version with package.json --- editors/code/package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 1b497edd7..575dc7c4a 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -1,6 +1,6 @@ { "name": "rust-analyzer", - "version": "0.2.20200309", + "version": "0.2.20200309-nightly", "lockfileVersion": 1, "requires": true, "dependencies": { -- cgit v1.2.3 From d38d59fed810e702d7473cbb8485b26f921889c6 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 16 Mar 2020 12:17:36 +0200 Subject: vscode-postrefactor: prefer arrow functions --- editors/code/src/installation/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index 0d69b8d0c..e0aa5317d 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -109,7 +109,7 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") * 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 function tryDownloadNightlyExtension( +const tryDownloadNightlyExtension = notReentrant(async ( config: Config, shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true ): Promise { -- cgit v1.2.3 From fc47274541b5f4e9c8406c4dd401392129845396 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 16 Mar 2020 12:18:30 +0200 Subject: vscode-postrefactor: fix syntax error --- editors/code/src/installation/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index e0aa5317d..eea6fded2 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -112,7 +112,7 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") const tryDownloadNightlyExtension = notReentrant(async ( config: Config, shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true -): Promise { +): Promise => { const vsixSource = config.nightlyVsixSource; try { const releaseInfo = await fetchArtifactReleaseInfo(vsixSource.repo, vsixSource.file, vsixSource.tag); -- cgit v1.2.3 From 5a0041c5aaeee49be84ce771fb0360ae55cbd8b2 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 16 Mar 2020 12:19:26 +0200 Subject: vscode-postrefactor: migrate to arrow functions --- editors/code/src/installation/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts index f35958474..05730a778 100644 --- a/editors/code/src/installation/server.ts +++ b/editors/code/src/installation/server.ts @@ -85,10 +85,10 @@ function shouldDownloadServer( /** * Enforcing no reentrancy for this is best-effort. */ -const downloadServer = notReentrant(async function downloadServer( +const downloadServer = notReentrant(async ( source: ArtifactSource.GithubRelease, config: Config, -): Promise { +): Promise => { try { const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); -- cgit v1.2.3