diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-03-19 08:06:48 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-03-19 08:06:48 +0000 |
commit | aca3c3086ee99f5770a60970e20af640c895d42a (patch) | |
tree | 2f0b3233cc4728436ba5e47a6e91e7df33585d43 /editors/code/src/installation/server.ts | |
parent | 55336722b3662cbdcc9e1b92a3e27ed0442d2452 (diff) | |
parent | fb6e655de8a44c65275ad45a27bf5bd684670ba0 (diff) |
Merge #3629
3629: Alternative aproach to plugin auto update r=matklad a=matklad
This is very much WIP (as in, I haven't run this once), but I like the result so far.
cc @Veetaha
The primary focus here on simplification:
* local simplification of data structures and control-flow: using union of strings instead of an enum, using unwrapped GitHub API responses
* global simplification of control flow: all logic is now in `main.ts`, implemented as linear functions without abstractions. This is stateful side-effective code, so arguments from [Carmack](http://number-none.com/blow/john_carmack_on_inlined_code.html) very much apply. We need all user interractions, all mutations, and all network requests to happen in a single file.
* as a side-effect of condensing everything to functions, we can get rid of various enums. The enums were basically a reified control flow:
```
enum E { A, B }
fn foo() -> E {
if cond { E::A } else { E::B }
}
fn bar(e: E) {
match e {
E::A => do_a(),
E::B => do_b(),
}
}
==>>
fn all() {
if cond { do_a() } else { do_b() }
}
```
* simplification of model: we don't need to reinstall on settings update, we can just ask the user to reload, we don't need to handle nightly=>stable fallback, we can ask the user to reinstall extension, (todo) we don't need to parse out the date from the version, we can use build id for nightly and for stable we can write the info directly into package.json.
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'editors/code/src/installation/server.ts')
-rw-r--r-- | editors/code/src/installation/server.ts | 131 |
1 files changed, 0 insertions, 131 deletions
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts deleted file mode 100644 index 05d326131..000000000 --- a/editors/code/src/installation/server.ts +++ /dev/null | |||
@@ -1,131 +0,0 @@ | |||
1 | import * as vscode from "vscode"; | ||
2 | import * as path from "path"; | ||
3 | import { spawnSync } from "child_process"; | ||
4 | |||
5 | import { ArtifactSource } from "./interfaces"; | ||
6 | import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; | ||
7 | import { downloadArtifactWithProgressUi } from "./downloads"; | ||
8 | import { log, assert, notReentrant } from "../util"; | ||
9 | import { Config, NIGHTLY_TAG } from "../config"; | ||
10 | import { PersistentState } from "../persistent_state"; | ||
11 | |||
12 | export async function ensureServerBinary(config: Config, state: PersistentState): Promise<null | string> { | ||
13 | const source = config.serverSource; | ||
14 | |||
15 | if (!source) { | ||
16 | vscode.window.showErrorMessage( | ||
17 | "Unfortunately we don't ship binaries for your platform yet. " + | ||
18 | "You need to manually clone rust-analyzer repository and " + | ||
19 | "run `cargo xtask install --server` to build the language server from sources. " + | ||
20 | "If you feel that your platform should be supported, please create an issue " + | ||
21 | "about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " + | ||
22 | "will consider it." | ||
23 | ); | ||
24 | return null; | ||
25 | } | ||
26 | |||
27 | switch (source.type) { | ||
28 | case ArtifactSource.Type.ExplicitPath: { | ||
29 | if (isBinaryAvailable(source.path)) { | ||
30 | return source.path; | ||
31 | } | ||
32 | |||
33 | vscode.window.showErrorMessage( | ||
34 | `Unable to run ${source.path} binary. ` + | ||
35 | `To use the pre-built language server, set "rust-analyzer.serverPath" ` + | ||
36 | "value to `null` or remove it from the settings to use it by default." | ||
37 | ); | ||
38 | return null; | ||
39 | } | ||
40 | case ArtifactSource.Type.GithubRelease: { | ||
41 | if (!shouldDownloadServer(state, source)) { | ||
42 | return path.join(source.dir, source.file); | ||
43 | } | ||
44 | |||
45 | if (config.askBeforeDownload) { | ||
46 | const userResponse = await vscode.window.showInformationMessage( | ||
47 | `Language server version ${source.tag} for rust-analyzer is not installed. ` + | ||
48 | "Do you want to download it now?", | ||
49 | "Download now", "Cancel" | ||
50 | ); | ||
51 | if (userResponse !== "Download now") return null; | ||
52 | } | ||
53 | |||
54 | return await downloadServer(state, source); | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | function shouldDownloadServer( | ||
60 | state: PersistentState, | ||
61 | source: ArtifactSource.GithubRelease, | ||
62 | ): boolean { | ||
63 | if (!isBinaryAvailable(path.join(source.dir, source.file))) return true; | ||
64 | |||
65 | const installed = { | ||
66 | tag: state.serverReleaseTag.get(), | ||
67 | date: state.serverReleaseDate.get() | ||
68 | }; | ||
69 | const required = { | ||
70 | tag: source.tag, | ||
71 | date: state.installedNightlyExtensionReleaseDate.get() | ||
72 | }; | ||
73 | |||
74 | log.debug("Installed server:", installed, "required:", required); | ||
75 | |||
76 | if (required.tag !== NIGHTLY_TAG || installed.tag !== NIGHTLY_TAG) { | ||
77 | return required.tag !== installed.tag; | ||
78 | } | ||
79 | |||
80 | assert(required.date !== null, "Extension release date should have been saved during its installation"); | ||
81 | assert(installed.date !== null, "Server release date should have been saved during its installation"); | ||
82 | |||
83 | return installed.date.getTime() !== required.date.getTime(); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * Enforcing no reentrancy for this is best-effort. | ||
88 | */ | ||
89 | const downloadServer = notReentrant(async ( | ||
90 | state: PersistentState, | ||
91 | source: ArtifactSource.GithubRelease, | ||
92 | ): Promise<null | string> => { | ||
93 | try { | ||
94 | const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); | ||
95 | |||
96 | await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); | ||
97 | await Promise.all([ | ||
98 | state.serverReleaseTag.set(releaseInfo.releaseName), | ||
99 | state.serverReleaseDate.set(releaseInfo.releaseDate) | ||
100 | ]); | ||
101 | } catch (err) { | ||
102 | log.downloadError(err, "language server", source.repo.name); | ||
103 | return null; | ||
104 | } | ||
105 | |||
106 | const binaryPath = path.join(source.dir, source.file); | ||
107 | |||
108 | assert(isBinaryAvailable(binaryPath), | ||
109 | `Downloaded language server binary is not functional.` + | ||
110 | `Downloaded from GitHub repo ${source.repo.owner}/${source.repo.name} ` + | ||
111 | `to ${binaryPath}` | ||
112 | ); | ||
113 | |||
114 | vscode.window.showInformationMessage( | ||
115 | "Rust analyzer language server was successfully installed 🦀" | ||
116 | ); | ||
117 | |||
118 | return binaryPath; | ||
119 | }); | ||
120 | |||
121 | function isBinaryAvailable(binaryPath: string): boolean { | ||
122 | const res = spawnSync(binaryPath, ["--version"]); | ||
123 | |||
124 | // ACHTUNG! `res` type declaration is inherently wrong, see | ||
125 | // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 | ||
126 | |||
127 | log.debug("Checked binary availablity via --version", res); | ||
128 | log.debug(binaryPath, "--version output:", res.output?.map(String)); | ||
129 | |||
130 | return res.status === 0; | ||
131 | } | ||