import * as vscode from "vscode"; import * as path from "path"; import { spawnSync } from "child_process"; import { ArtifactSource } from "./interfaces"; import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; import { downloadArtifactWithProgressUi } from "./downloads"; import { log, assert, notReentrant } from "../util"; import { Config, NIGHTLY_TAG } from "../config"; import { PersistentState } from "../persistent_state"; export async function ensureServerBinary(config: Config, state: PersistentState): Promise { const source = config.serverSource; if (!source) { 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 null; } switch (source.type) { case ArtifactSource.Type.ExplicitPath: { if (isBinaryAvailable(source.path)) { return source.path; } vscode.window.showErrorMessage( `Unable to run ${source.path} binary. ` + `To use the pre-built language server, set "rust-analyzer.serverPath" ` + "value to `null` or remove it from the settings to use it by default." ); return null; } case ArtifactSource.Type.GithubRelease: { if (!shouldDownloadServer(state, source)) { return path.join(source.dir, source.file); } 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?", "Download now", "Cancel" ); if (userResponse !== "Download now") return null; } return await downloadServer(state, source); } } } function shouldDownloadServer( state: PersistentState, source: ArtifactSource.GithubRelease, ): boolean { if (!isBinaryAvailable(path.join(source.dir, source.file))) return true; const installed = { tag: state.serverReleaseTag.get(), date: state.serverReleaseDate.get() }; const required = { tag: source.tag, date: state.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(); } /** * Enforcing no reentrancy for this is best-effort. */ const downloadServer = notReentrant(async ( state: PersistentState, source: ArtifactSource.GithubRelease, ): Promise => { try { const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); await Promise.all([ state.serverReleaseTag.set(releaseInfo.releaseName), state.serverReleaseDate.set(releaseInfo.releaseDate) ]); } catch (err) { log.downloadError(err, "language server", source.repo.name); return null; } const binaryPath = path.join(source.dir, source.file); assert(isBinaryAvailable(binaryPath), `Downloaded language server binary is not functional.` + `Downloaded from GitHub repo ${source.repo.owner}/${source.repo.name} ` + `to ${binaryPath}` ); vscode.window.showInformationMessage( "Rust analyzer language server was successfully installed 🦀" ); return binaryPath; }); function isBinaryAvailable(binaryPath: string): boolean { const res = spawnSync(binaryPath, ["--version"]); // ACHTUNG! `res` type declaration is inherently wrong, see // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 log.debug("Checked binary availablity via --version", res); log.debug(binaryPath, "--version output:", res.output?.map(String)); return res.status === 0; }