diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-03-16 10:26:31 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-03-16 10:26:31 +0000 |
commit | 200c275c2e9955371e61f6ad7684084655df46fc (patch) | |
tree | c4b61de644cec37cffca9010d56afc4136d23ca8 /editors/code/src/installation/server.ts | |
parent | a99cac671c3e6105a0192acbb1a91cb83e453018 (diff) | |
parent | 5a0041c5aaeee49be84ce771fb0360ae55cbd8b2 (diff) |
Merge #3534
3534: Feature: vscode impl nightlies download and installation r=Veetaha a=Veetaha
I need to test things more, but the core shape of the code is quite well-formed.
The main problem is that we save the release date only for nightlies and there are no means to get the release date of the stable extension (i.e. for this we would need to consult the github releases via a network request, or we would need to somehow save this info into package.json or any other file accessible from the extension code during the deployment step, but this will be very hard I guess).
So there is an invariant that the users can install nightly only from our extension and they can't do it manually, because when installing the nightly `.vsix` we actually save its release date to `globalState`
Closes: #3402
TODO:
- [x] More manual tests and documentation
cc @matklad @lnicola
Co-authored-by: Veetaha <[email protected]>
Co-authored-by: Veetaha <[email protected]>
Diffstat (limited to 'editors/code/src/installation/server.ts')
-rw-r--r-- | editors/code/src/installation/server.ts | 104 |
1 files changed, 53 insertions, 51 deletions
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts index ef1c45ff6..05730a778 100644 --- a/editors/code/src/installation/server.ts +++ b/editors/code/src/installation/server.ts | |||
@@ -1,14 +1,16 @@ | |||
1 | import * as vscode from "vscode"; | 1 | import * as vscode from "vscode"; |
2 | import * as path from "path"; | 2 | import * as path from "path"; |
3 | import { promises as dns } from "dns"; | ||
4 | import { spawnSync } from "child_process"; | 3 | import { spawnSync } from "child_process"; |
5 | 4 | ||
6 | import { ArtifactSource } from "./interfaces"; | 5 | import { ArtifactSource } from "./interfaces"; |
7 | import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; | 6 | import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; |
8 | import { downloadArtifact } from "./download_artifact"; | 7 | import { downloadArtifactWithProgressUi } from "./downloads"; |
9 | import { log, assert } from "../util"; | 8 | import { log, assert, notReentrant } from "../util"; |
9 | import { Config, NIGHTLY_TAG } from "../config"; | ||
10 | |||
11 | export async function ensureServerBinary(config: Config): Promise<null | string> { | ||
12 | const source = config.serverSource; | ||
10 | 13 | ||
11 | export async function ensureServerBinary(source: null | ArtifactSource): Promise<null | string> { | ||
12 | if (!source) { | 14 | if (!source) { |
13 | vscode.window.showErrorMessage( | 15 | vscode.window.showErrorMessage( |
14 | "Unfortunately we don't ship binaries for your platform yet. " + | 16 | "Unfortunately we don't ship binaries for your platform yet. " + |
@@ -35,18 +37,11 @@ export async function ensureServerBinary(source: null | ArtifactSource): Promise | |||
35 | return null; | 37 | return null; |
36 | } | 38 | } |
37 | case ArtifactSource.Type.GithubRelease: { | 39 | case ArtifactSource.Type.GithubRelease: { |
38 | const prebuiltBinaryPath = path.join(source.dir, source.file); | 40 | if (!shouldDownloadServer(source, config)) { |
39 | 41 | return path.join(source.dir, source.file); | |
40 | const installedVersion: null | string = getServerVersion(source.storage); | ||
41 | const requiredVersion: string = source.tag; | ||
42 | |||
43 | log.debug("Installed version:", installedVersion, "required:", requiredVersion); | ||
44 | |||
45 | if (isBinaryAvailable(prebuiltBinaryPath) && installedVersion === requiredVersion) { | ||
46 | return prebuiltBinaryPath; | ||
47 | } | 42 | } |
48 | 43 | ||
49 | if (source.askBeforeDownload) { | 44 | if (config.askBeforeDownload) { |
50 | const userResponse = await vscode.window.showInformationMessage( | 45 | const userResponse = await vscode.window.showInformationMessage( |
51 | `Language server version ${source.tag} for rust-analyzer is not installed. ` + | 46 | `Language server version ${source.tag} for rust-analyzer is not installed. ` + |
52 | "Do you want to download it now?", | 47 | "Do you want to download it now?", |
@@ -55,38 +50,56 @@ export async function ensureServerBinary(source: null | ArtifactSource): Promise | |||
55 | if (userResponse !== "Download now") return null; | 50 | if (userResponse !== "Download now") return null; |
56 | } | 51 | } |
57 | 52 | ||
58 | if (!await downloadServer(source)) return null; | 53 | return await downloadServer(source, config); |
59 | |||
60 | return prebuiltBinaryPath; | ||
61 | } | 54 | } |
62 | } | 55 | } |
63 | } | 56 | } |
64 | 57 | ||
65 | async function downloadServer(source: ArtifactSource.GithubRelease): Promise<boolean> { | 58 | function shouldDownloadServer( |
59 | source: ArtifactSource.GithubRelease, | ||
60 | config: Config | ||
61 | ): boolean { | ||
62 | if (!isBinaryAvailable(path.join(source.dir, source.file))) return true; | ||
63 | |||
64 | const installed = { | ||
65 | tag: config.serverReleaseTag.get(), | ||
66 | date: config.serverReleaseDate.get() | ||
67 | }; | ||
68 | const required = { | ||
69 | tag: source.tag, | ||
70 | date: config.installedNightlyExtensionReleaseDate.get() | ||
71 | }; | ||
72 | |||
73 | log.debug("Installed server:", installed, "required:", required); | ||
74 | |||
75 | if (required.tag !== NIGHTLY_TAG || installed.tag !== NIGHTLY_TAG) { | ||
76 | return required.tag !== installed.tag; | ||
77 | } | ||
78 | |||
79 | assert(required.date !== null, "Extension release date should have been saved during its installation"); | ||
80 | assert(installed.date !== null, "Server release date should have been saved during its installation"); | ||
81 | |||
82 | return installed.date.getTime() !== required.date.getTime(); | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * Enforcing no reentrancy for this is best-effort. | ||
87 | */ | ||
88 | const downloadServer = notReentrant(async ( | ||
89 | source: ArtifactSource.GithubRelease, | ||
90 | config: Config, | ||
91 | ): Promise<null | string> => { | ||
66 | try { | 92 | try { |
67 | const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); | 93 | const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); |
68 | 94 | ||
69 | await downloadArtifact(releaseInfo, source.file, source.dir, "language server"); | 95 | await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); |
70 | await setServerVersion(source.storage, releaseInfo.releaseName); | 96 | await Promise.all([ |
97 | config.serverReleaseTag.set(releaseInfo.releaseName), | ||
98 | config.serverReleaseDate.set(releaseInfo.releaseDate) | ||
99 | ]); | ||
71 | } catch (err) { | 100 | } catch (err) { |
72 | vscode.window.showErrorMessage( | 101 | log.downloadError(err, "language server", source.repo.name); |
73 | `Failed to download language server from ${source.repo.name} ` + | 102 | return null; |
74 | `GitHub repository: ${err.message}` | ||
75 | ); | ||
76 | |||
77 | log.error(err); | ||
78 | |||
79 | dns.resolve('example.com').then( | ||
80 | addrs => log.debug("DNS resolution for example.com was successful", addrs), | ||
81 | err => { | ||
82 | log.error( | ||
83 | "DNS resolution for example.com failed, " + | ||
84 | "there might be an issue with Internet availability" | ||
85 | ); | ||
86 | log.error(err); | ||
87 | } | ||
88 | ); | ||
89 | return false; | ||
90 | } | 103 | } |
91 | 104 | ||
92 | const binaryPath = path.join(source.dir, source.file); | 105 | const binaryPath = path.join(source.dir, source.file); |
@@ -101,8 +114,8 @@ async function downloadServer(source: ArtifactSource.GithubRelease): Promise<boo | |||
101 | "Rust analyzer language server was successfully installed 🦀" | 114 | "Rust analyzer language server was successfully installed 🦀" |
102 | ); | 115 | ); |
103 | 116 | ||
104 | return true; | 117 | return binaryPath; |
105 | } | 118 | }); |
106 | 119 | ||
107 | function isBinaryAvailable(binaryPath: string): boolean { | 120 | function isBinaryAvailable(binaryPath: string): boolean { |
108 | const res = spawnSync(binaryPath, ["--version"]); | 121 | const res = spawnSync(binaryPath, ["--version"]); |
@@ -115,14 +128,3 @@ function isBinaryAvailable(binaryPath: string): boolean { | |||
115 | 128 | ||
116 | return res.status === 0; | 129 | return res.status === 0; |
117 | } | 130 | } |
118 | |||
119 | function getServerVersion(storage: vscode.Memento): null | string { | ||
120 | const version = storage.get<null | string>("server-version", null); | ||
121 | log.debug("Get server-version:", version); | ||
122 | return version; | ||
123 | } | ||
124 | |||
125 | async function setServerVersion(storage: vscode.Memento, version: string): Promise<void> { | ||
126 | log.debug("Set server-version:", version); | ||
127 | await storage.update("server-version", version.toString()); | ||
128 | } | ||