aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/installation/server.ts
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-02-16 11:54:38 +0000
committerGitHub <[email protected]>2020-02-16 11:54:38 +0000
commita15c8739b9a6da223e1f3a6ff6aa868913c0dbf4 (patch)
tree5694d395b08c416bf60d7206e0380a3859cfe1e2 /editors/code/src/installation/server.ts
parent617b5b3b31cf0b461829810640e28a9090a5b957 (diff)
parent325eba58a286c147f19dada5f205aa9e2ec6f391 (diff)
Merge #3162
3162: Feature: vscode always downloads only the matching ra_lsp_server version r=matklad a=Veetaha I tried to separate logically connected changes into separate commits, so enjoy! Now TypeScript extension saves installed binary version in global state and always checks that the installed binary version equals the version of the TypeScript extension itself (to prevent version drifts). Also, changed `fetchLatestArtifactReleaseInfo()` to `fetchArtifactReleaseInfo()` that takes an optional release tag (when not specified fetches the latest release). The version without a release tag will be useful in the future when adding auto-checking for updates. I decided not to do `Download latest language server` command (I have stated the rationale for this in #3073) and let the extension itself decide which version of the binary it wants. This way the users will be able to get the latest `ra_lsp_server` binary after the approaching 2020-02-17 release, without having to manually delete the outdated one from `~/.config/Code/User/globalStorage/matklad.rust-analyzer`! Closes #3073 Co-authored-by: Veetaha <[email protected]>
Diffstat (limited to 'editors/code/src/installation/server.ts')
-rw-r--r--editors/code/src/installation/server.ts168
1 files changed, 72 insertions, 96 deletions
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts
index 406e2299c..80cb719e3 100644
--- a/editors/code/src/installation/server.ts
+++ b/editors/code/src/installation/server.ts
@@ -1,63 +1,15 @@
1import * as vscode from "vscode"; 1import * as vscode from "vscode";
2import * as path from "path"; 2import * as path from "path";
3import { strict as assert } from "assert"; 3import { strict as assert } from "assert";
4import { promises as fs } from "fs";
5import { promises as dns } from "dns"; 4import { promises as dns } from "dns";
6import { spawnSync } from "child_process"; 5import { spawnSync } from "child_process";
7import { throttle } from "throttle-debounce";
8 6
9import { BinarySource } from "./interfaces"; 7import { BinarySource } from "./interfaces";
10import { fetchLatestArtifactReleaseInfo } from "./fetch_latest_artifact_release_info"; 8import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
11import { downloadFile } from "./download_file"; 9import { downloadArtifact } from "./download_artifact";
12
13export async function downloadLatestServer(
14 {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease
15) {
16 const { releaseName, downloadUrl } = (await fetchLatestArtifactReleaseInfo(
17 repo, artifactFileName
18 ))!;
19
20 await fs.mkdir(installationDir).catch(err => assert.strictEqual(
21 err?.code,
22 "EEXIST",
23 `Couldn't create directory "${installationDir}" to download `+
24 `language server binary: ${err.message}`
25 ));
26
27 const installationPath = path.join(installationDir, artifactFileName);
28
29 console.time("Downloading ra_lsp_server");
30 await vscode.window.withProgress(
31 {
32 location: vscode.ProgressLocation.Notification,
33 cancellable: false, // FIXME: add support for canceling download?
34 title: `Downloading language server (${releaseName})`
35 },
36 async (progress, _cancellationToken) => {
37 let lastPrecentage = 0;
38 const filePermissions = 0o755; // (rwx, r_x, r_x)
39 await downloadFile(downloadUrl, installationPath, filePermissions, throttle(
40 200,
41 /* noTrailing: */ true,
42 (readBytes, totalBytes) => {
43 const newPercentage = (readBytes / totalBytes) * 100;
44 progress.report({
45 message: newPercentage.toFixed(0) + "%",
46 increment: newPercentage - lastPrecentage
47 });
48
49 lastPrecentage = newPercentage;
50 })
51 );
52 }
53 );
54 console.timeEnd("Downloading ra_lsp_server");
55}
56export async function ensureServerBinary(
57 serverSource: null | BinarySource
58): Promise<null | string> {
59 10
60 if (!serverSource) { 11export async function ensureServerBinary(source: null | BinarySource): Promise<null | string> {
12 if (!source) {
61 vscode.window.showErrorMessage( 13 vscode.window.showErrorMessage(
62 "Unfortunately we don't ship binaries for your platform yet. " + 14 "Unfortunately we don't ship binaries for your platform yet. " +
63 "You need to manually clone rust-analyzer repository and " + 15 "You need to manually clone rust-analyzer repository and " +
@@ -69,80 +21,104 @@ export async function ensureServerBinary(
69 return null; 21 return null;
70 } 22 }
71 23
72 switch (serverSource.type) { 24 switch (source.type) {
73 case BinarySource.Type.ExplicitPath: { 25 case BinarySource.Type.ExplicitPath: {
74 if (isBinaryAvailable(serverSource.path)) { 26 if (isBinaryAvailable(source.path)) {
75 return serverSource.path; 27 return source.path;
76 } 28 }
77 29
78 vscode.window.showErrorMessage( 30 vscode.window.showErrorMessage(
79 `Unable to run ${serverSource.path} binary. ` + 31 `Unable to run ${source.path} binary. ` +
80 `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + 32 `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` +
81 "value to `null` or remove it from the settings to use it by default." 33 "value to `null` or remove it from the settings to use it by default."
82 ); 34 );
83 return null; 35 return null;
84 } 36 }
85 case BinarySource.Type.GithubRelease: { 37 case BinarySource.Type.GithubRelease: {
86 const prebuiltBinaryPath = path.join(serverSource.dir, serverSource.file); 38 const prebuiltBinaryPath = path.join(source.dir, source.file);
39
40 const installedVersion: null | string = getServerVersion(source.storage);
41 const requiredVersion: string = source.version;
87 42
88 if (isBinaryAvailable(prebuiltBinaryPath)) { 43 console.log("Installed version:", installedVersion, "required:", requiredVersion);
44
45 if (isBinaryAvailable(prebuiltBinaryPath) && installedVersion == requiredVersion) {
46 // FIXME: check for new releases and notify the user to update if possible
89 return prebuiltBinaryPath; 47 return prebuiltBinaryPath;
90 } 48 }
91 49
92 const userResponse = await vscode.window.showInformationMessage( 50 const userResponse = await vscode.window.showInformationMessage(
93 "Language server binary for rust-analyzer was not found. " + 51 `Language server version ${source.version} for rust-analyzer is not installed. ` +
94 "Do you want to download it now?", 52 "Do you want to download it now?",
95 "Download now", "Cancel" 53 "Download now", "Cancel"
96 ); 54 );
97 if (userResponse !== "Download now") return null; 55 if (userResponse !== "Download now") return null;
98 56
99 try { 57 if (!await downloadServer(source)) return null;
100 await downloadLatestServer(serverSource);
101 } catch (err) {
102 vscode.window.showErrorMessage(
103 `Failed to download language server from ${serverSource.repo.name} ` +
104 `GitHub repository: ${err.message}`
105 );
106 58
107 console.error(err); 59 return prebuiltBinaryPath;
60 }
61 }
62}
108 63
109 dns.resolve('example.com').then( 64async function downloadServer(source: BinarySource.GithubRelease): Promise<boolean> {
110 addrs => console.log("DNS resolution for example.com was successful", addrs), 65 try {
111 err => { 66 const releaseInfo = (await fetchArtifactReleaseInfo(source.repo, source.file, source.version))!;
112 console.error( 67
113 "DNS resolution for example.com failed, " + 68 await downloadArtifact(releaseInfo, source.file, source.dir, "language server");
114 "there might be an issue with Internet availability" 69 await setServerVersion(source.storage, releaseInfo.releaseName);
115 ); 70 } catch (err) {
116 console.error(err); 71 vscode.window.showErrorMessage(
117 } 72 `Failed to download language server from ${source.repo.name} ` +
118 ); 73 `GitHub repository: ${err.message}`
74 );
75
76 console.error(err);
119 77
120 return null; 78 dns.resolve('example.com').then(
79 addrs => console.log("DNS resolution for example.com was successful", addrs),
80 err => {
81 console.error(
82 "DNS resolution for example.com failed, " +
83 "there might be an issue with Internet availability"
84 );
85 console.error(err);
121 } 86 }
87 );
88 return false;
89 }
122 90
123 if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, 91 if (!isBinaryAvailable(path.join(source.dir, source.file))) assert(false,
124 `Downloaded language server binary is not functional.` + 92 `Downloaded language server binary is not functional.` +
125 `Downloaded from: ${JSON.stringify(serverSource)}` 93 `Downloaded from: ${JSON.stringify(source, null, 4)}`
126 ); 94 );
127 95
96 vscode.window.showInformationMessage(
97 "Rust analyzer language server was successfully installed 🦀"
98 );
128 99
129 vscode.window.showInformationMessage( 100 return true;
130 "Rust analyzer language server was successfully installed 🦀" 101}
131 );
132 102
133 return prebuiltBinaryPath; 103function isBinaryAvailable(binaryPath: string): boolean {
134 } 104 const res = spawnSync(binaryPath, ["--version"]);
135 }
136 105
137 function isBinaryAvailable(binaryPath: string) { 106 // ACHTUNG! `res` type declaration is inherently wrong, see
138 const res = spawnSync(binaryPath, ["--version"]); 107 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221
139 108
140 // ACHTUNG! `res` type declaration is inherently wrong, see 109 console.log("Checked binary availablity via --version", res);
141 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 110 console.log(binaryPath, "--version output:", res.output?.map(String));
142 111
143 console.log("Checked binary availablity via --version", res); 112 return res.status === 0;
144 console.log(binaryPath, "--version output:", res.output?.map(String)); 113}
145 114
146 return res.status === 0; 115function getServerVersion(storage: vscode.Memento): null | string {
147 } 116 const version = storage.get<null | string>("server-version", null);
117 console.log("Get server-version:", version);
118 return version;
119}
120
121async function setServerVersion(storage: vscode.Memento, version: string): Promise<void> {
122 console.log("Set server-version:", version);
123 await storage.update("server-version", version.toString());
148} 124}