diff --git a/PRIVACY.md b/PRIVACY.md index 718fbed12069..89e252be731d 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1 +1 @@ -See the [Privacy](https://rust-analyzer.github.io/manual.html#security) section of the user manual. +See the [Privacy](https://rust-analyzer.github.io/manual.html#privacy) section of the user manual. diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 1335f8df7c01..13165e8d3f0f 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -653,10 +653,12 @@ Here is a **non-exhaustive** list of ways to make rust-analyzer execute arbitrar The LSP server performs no network access in itself, but runs `cargo metadata` which will update or download the crate registry and the source code of the project dependencies. If enabled (the default), build scripts and procedural macros can do anything. -The Code extension automatically connects to GitHub to download updated LSP binaries and, if the nightly channel is selected, to perform update checks using the GitHub API. For `rust-analyzer` developers, using `cargo xtask release` uses the same API to put together the release notes. +The Code extension does not access the network. Any other editor plugins are not under the control of the `rust-analyzer` developers. For any privacy concerns, you should check with their respective developers. +For `rust-analyzer` developers, `cargo xtask release` uses the GitHub API to put together the release notes. + == Features include::./generated_features.adoc[] diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 8728654662c6..4aa9ac9d4301 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -11,8 +11,6 @@ "dependencies": { "d3": "^7.2.0", "d3-graphviz": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^3.0.3", "vscode-languageclient": "8.0.0-next.2" }, "devDependencies": { @@ -345,6 +343,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "dependencies": { "debug": "4" }, @@ -1274,18 +1273,11 @@ "node": ">=12" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", - "engines": { - "node": ">= 12" - } - }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2059,27 +2051,6 @@ "pend": "~1.2.0" } }, - "node_modules/fetch-blob": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz", - "integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2123,17 +2094,6 @@ "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2403,6 +2363,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -2770,7 +2731,8 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/mute-stream": { "version": "0.0.8", @@ -2814,23 +2776,6 @@ "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, - "node_modules/node-fetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz", - "integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.2", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -3866,14 +3811,6 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.3.tgz", "integrity": "sha512-VQcXnhKYxUW6OiRMhG++SzmZYMJwusXknJGd+FfdOnS1yHAo734OHyR0e2eEHDlv0/oWc8RZPgx/VKSKyondVg==" }, - "node_modules/web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4170,6 +4107,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "requires": { "debug": "4" } @@ -4874,15 +4812,11 @@ "d3-transition": "2 - 3" } }, - "data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" - }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -5434,14 +5368,6 @@ "pend": "~1.2.0" } }, - "fetch-blob": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz", - "integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==", - "requires": { - "web-streams-polyfill": "^3.0.3" - } - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5476,14 +5402,6 @@ "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, - "formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "requires": { - "fetch-blob": "^3.1.2" - } - }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -5696,6 +5614,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -5978,7 +5897,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "mute-stream": { "version": "0.0.8", @@ -6021,16 +5941,6 @@ "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, - "node-fetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz", - "integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==", - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.2", - "formdata-polyfill": "^4.0.10" - } - }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -6827,11 +6737,6 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.3.tgz", "integrity": "sha512-VQcXnhKYxUW6OiRMhG++SzmZYMJwusXknJGd+FfdOnS1yHAo734OHyR0e2eEHDlv0/oWc8RZPgx/VKSKyondVg==" }, - "web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/editors/code/package.json b/editors/code/package.json index 9ab57d7dc5f3..550b28e1cdb2 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -36,8 +36,6 @@ "test": "node ./out/tests/runTests.js" }, "dependencies": { - "https-proxy-agent": "^5.0.0", - "node-fetch": "^3.0.3", "vscode-languageclient": "8.0.0-next.2", "d3": "^7.2.0", "d3-graphviz": "^4.0.0" @@ -305,24 +303,6 @@ "default": true, "description": "Whether inlay hints font size should be smaller than editor's font size." }, - "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 (extension updates automatically by downloading artifacts directly from GitHub), 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": false, - "description": "Whether to ask for permission before downloading any files from the Internet." - }, "rust-analyzer.server.path": { "type": [ "null", diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index cb0868db5979..cdbaa67fe22c 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -8,11 +8,6 @@ const NIGHTLY_TAG = "nightly"; export type RunnableEnvCfg = undefined | Record | { mask?: string; env: Record }[]; -export class ProxySettings { - proxy?: string = undefined; - strictSSL: boolean = true; -} - export class Config { readonly extensionId = "matklad.rust-analyzer"; @@ -24,7 +19,6 @@ export class Config { "procMacro", "files", "highlighting", - "updates.channel", "lens", // works as lens.* ] .map(opt => `${this.rootSection}.${opt}`); @@ -36,11 +30,9 @@ export class Config { } = vscode.extensions.getExtension(this.extensionId)!.packageJSON; readonly globalStorageUri: vscode.Uri; - readonly installUri: vscode.Uri; constructor(ctx: vscode.ExtensionContext) { this.globalStorageUri = ctx.globalStorageUri; - this.installUri = ctx.extensionUri; vscode.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, ctx.subscriptions); this.refreshLogging(); } @@ -103,21 +95,7 @@ export class Config { return this.get("server.path") ?? this.get("serverPath"); } get serverExtraEnv() { return this.get("server.extraEnv") ?? {}; } - get channel() { return this.get("updates.channel"); } - get askBeforeDownload() { return this.get("updates.askBeforeDownload"); } get traceExtension() { return this.get("trace.extension"); } - get proxySettings(): ProxySettings { - const proxy = vscode - .workspace - .getConfiguration('http') - .get("proxy")! || process.env["https_proxy"] || process.env["HTTPS_PROXY"]; - const strictSSL = vscode.workspace.getConfiguration("http").get("proxyStrictSSL") ?? true; - - return { - proxy: proxy, - strictSSL: strictSSL, - }; - } get inlayHints() { return { diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index dc0a9a2f872c..df7580b9bc52 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -5,12 +5,11 @@ import * as commands from './commands'; import { activateInlayHints } from './inlay_hints'; import { Ctx } from './ctx'; import { Config } from './config'; -import { log, assert, isValidExecutable, isRustDocument } from './util'; +import { log, isValidExecutable, isRustDocument } from './util'; import { PersistentState } from './persistent_state'; -import { fetchRelease, download } from './net'; import { activateTaskProvider } from './tasks'; import { setContextValue } from './util'; -import { exec, spawnSync } from 'child_process'; +import { exec } from 'child_process'; let ctx: Ctx | undefined; @@ -28,14 +27,9 @@ export async function activate(context: vscode.ExtensionContext) { async function tryActivate(context: vscode.ExtensionContext) { const config = new Config(context); const state = new PersistentState(context.globalState); - const serverPath = await bootstrap(config, state).catch(err => { + const serverPath = await bootstrap(context, config, state).catch(err => { let message = "bootstrap error. "; - if (err.code === "EBUSY" || err.code === "ETXTBSY" || err.code === "EPERM") { - message += "Other vscode windows might be using rust-analyzer, "; - message += "you should close them and reload this window to retry. "; - } - message += 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). '; message += 'To enable verbose logs use { "rust-analyzer.trace.extension": true }'; @@ -111,10 +105,6 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) { await activate(context).catch(log.error); }); - ctx.registerCommand('updateGithubToken', ctx => async () => { - await queryForGithubToken(new PersistentState(ctx.globalState)); - }); - ctx.registerCommand('analyzerStatus', commands.analyzerStatus); ctx.registerCommand('memoryUsage', commands.memoryUsage); ctx.registerCommand('shuffleCrateGraph', commands.shuffleCrateGraph); @@ -161,99 +151,8 @@ export async function deactivate() { ctx = undefined; } -async function bootstrap(config: Config, state: PersistentState): Promise { - await vscode.workspace.fs.createDirectory(config.globalStorageUri).then(); - - if (!config.currentExtensionIsNightly) { - await state.updateNightlyReleaseId(undefined); - } - await bootstrapExtension(config, state); - const path = await bootstrapServer(config, state); - return path; -} - -async function bootstrapExtension(config: Config, state: PersistentState): Promise { - if (config.package.releaseTag === null) return; - if (config.channel === "stable") { - if (config.currentExtensionIsNightly) { - void vscode.window.showWarningMessage( - `You are running a nightly version of rust-analyzer extension. ` + - `To switch to stable, uninstall the extension and re-install it from the marketplace` - ); - } - return; - }; - if (serverPath(config)) return; - - const now = Date.now(); - const isInitialNightlyDownload = state.nightlyReleaseId === undefined; - if (config.currentExtensionIsNightly) { - // Check if we should poll github api for the new nightly version - // if we haven't done it during the past hour - const lastCheck = state.lastCheck; - - const anHour = 60 * 60 * 1000; - const shouldCheckForNewNightly = isInitialNightlyDownload || (now - (lastCheck ?? 0)) > anHour; - - if (!shouldCheckForNewNightly) return; - } - - const latestNightlyRelease = await downloadWithRetryDialog(state, async () => { - return await fetchRelease("nightly", state.githubToken, config.proxySettings); - }).catch(async (e) => { - log.error(e); - if (isInitialNightlyDownload) { - await vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly: ${e}`); - } - return; - }); - if (latestNightlyRelease === undefined) { - if (isInitialNightlyDownload) { - await vscode.window.showErrorMessage("Failed to download rust-analyzer nightly: empty release contents returned"); - } - return; - } - if (config.currentExtensionIsNightly && latestNightlyRelease.id === state.nightlyReleaseId) return; - - const userResponse = await vscode.window.showInformationMessage( - "New version of rust-analyzer (nightly) is available (requires reload).", - "Update" - ); - if (userResponse !== "Update") return; - - let arch = process.arch; - if (arch === "ia32") { - arch = "x64"; - } - let platform = process.platform as string; - if (platform === "linux" && isMusl()) { - platform = "alpine"; - } - const artifactName = `rust-analyzer-${platform}-${arch}.vsix`; - - const artifact = latestNightlyRelease.assets.find(artifact => artifact.name === artifactName); - assert(!!artifact, `Bad release: ${JSON.stringify(latestNightlyRelease)}`); - const dest = vscode.Uri.joinPath(config.globalStorageUri, "rust-analyzer.vsix"); - - await downloadWithRetryDialog(state, async () => { - await download({ - url: artifact.browser_download_url, - dest, - progressTitle: "Downloading rust-analyzer extension", - proxySettings: config.proxySettings, - }); - }); - - await vscode.commands.executeCommand("workbench.extensions.installExtension", dest); - await vscode.workspace.fs.delete(dest); - - await state.updateNightlyReleaseId(latestNightlyRelease.id); - await state.updateLastCheck(now); - await vscode.commands.executeCommand("workbench.action.reloadWindow"); -} - -async function bootstrapServer(config: Config, state: PersistentState): Promise { - const path = await getServer(config, state); +async function bootstrap(context: vscode.ExtensionContext, config: Config, state: PersistentState): Promise { + const path = await getServer(context, config, state); if (!path) { throw new Error( "Rust Analyzer Language Server is not available. " + @@ -318,7 +217,7 @@ async function patchelf(dest: vscode.Uri): Promise { ); } -async function getServer(config: Config, state: PersistentState): Promise { +async function getServer(context: vscode.ExtensionContext, config: Config, state: PersistentState): Promise { const explicitPath = serverPath(config); if (explicitPath) { if (explicitPath.startsWith("~/")) { @@ -328,85 +227,39 @@ async function getServer(config: Config, state: PersistentState): Promise true, () => false); - let exists = await vscode.workspace.fs.stat(dest).then(() => true, () => false); if (bundledExists) { + let server = bundled; + if (await isNixOs()) { + await vscode.workspace.fs.createDirectory(config.globalStorageUri).then(); + const dest = vscode.Uri.joinPath(config.globalStorageUri, `rust-analyzer${ext}`); + let exists = await vscode.workspace.fs.stat(dest).then(() => true, () => false); + if (exists && config.package.version !== state.serverVersion) { + await vscode.workspace.fs.delete(dest); + exists = false; + } + if (!exists) { + await vscode.workspace.fs.copy(bundled, dest); + await patchelf(dest); + server = dest; + } + } await state.updateServerVersion(config.package.version); - if (!await isNixOs()) { - return bundled.fsPath; - } - if (!exists) { - await vscode.workspace.fs.copy(bundled, dest); - await patchelf(dest); - exists = true; - } - } - if (!exists) { - await state.updateServerVersion(undefined); + return server.fsPath; } - if (state.serverVersion === config.package.version) return dest.fsPath; - - if (config.askBeforeDownload) { - const userResponse = await vscode.window.showInformationMessage( - `Language server version ${config.package.version} for rust-analyzer is not installed.`, - "Download now" - ); - if (userResponse !== "Download now") return dest.fsPath; - } - - const releaseTag = config.package.releaseTag; - const release = await downloadWithRetryDialog(state, async () => { - return await fetchRelease(releaseTag, state.githubToken, config.proxySettings); - }); - const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`); - assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); - - await downloadWithRetryDialog(state, async () => { - await download({ - url: artifact.browser_download_url, - dest, - progressTitle: "Downloading rust-analyzer server", - gunzip: true, - mode: 0o755, - proxySettings: config.proxySettings, - }); - }); - - // Patching executable if that's NixOS. - if (await isNixOs()) { - await patchelf(dest); - } - - await state.updateServerVersion(config.package.version); - return dest.fsPath; + await state.updateServerVersion(undefined); + await vscode.window.showErrorMessage( + "Unfortunately we don't ship binaries for your platform yet. " + + "You need to manually clone the 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 undefined; } function serverPath(config: Config): string | null { @@ -422,85 +275,14 @@ async function isNixOs(): Promise { } } -function isMusl(): boolean { - // We can detect Alpine by checking `/etc/os-release` but not Void Linux musl. - // Instead, we run `ldd` since it advertises the libc which it belongs to. - const res = spawnSync("ldd", ["--version"]); - return res.stderr != null && res.stderr.indexOf("musl libc") >= 0; -} - -async function downloadWithRetryDialog(state: PersistentState, downloadFunc: () => Promise): Promise { - while (true) { - try { - return await downloadFunc(); - } catch (e) { - const selected = await vscode.window.showErrorMessage("Failed to download: " + e.message, {}, { - title: "Update Github Auth Token", - updateToken: true, - }, { - title: "Retry download", - retry: true, - }, { - title: "Dismiss", - }); - - if (selected?.updateToken) { - await queryForGithubToken(state); - continue; - } else if (selected?.retry) { - continue; - } - throw e; - }; - } -} - -async function queryForGithubToken(state: PersistentState): Promise { - const githubTokenOptions: vscode.InputBoxOptions = { - value: state.githubToken, - password: true, - prompt: ` - This dialog allows to store a Github authorization token. - The usage of an authorization token will increase the rate - limit on the use of Github APIs and can thereby prevent getting - throttled. - Auth tokens can be created at https://github.com/settings/tokens`, - }; - - const newToken = await vscode.window.showInputBox(githubTokenOptions); - if (newToken === undefined) { - // The user aborted the dialog => Do not update the stored token - return; - } - - if (newToken === "") { - log.info("Clearing github token"); - await state.updateGithubToken(undefined); - } else { - log.info("Storing new github token"); - await state.updateGithubToken(newToken); - } -} - function warnAboutExtensionConflicts() { - const conflicting = [ - ["rust-analyzer", "matklad.rust-analyzer"], - ["Rust", "rust-lang.rust"], - ["Rust", "kalitaalexey.vscode-rust"], - ]; - - const found = conflicting.filter( - nameId => vscode.extensions.getExtension(nameId[1]) !== undefined); - - if (found.length > 1) { - const fst = found[0]; - const sec = found[1]; + if (vscode.extensions.getExtension("rust-lang.rust")) { vscode.window.showWarningMessage( - `You have both the ${fst[0]} (${fst[1]}) and ${sec[0]} (${sec[1]}) ` + + `You have both the rust-analyzer (matklad.rust-analyzer) and Rust (rust-lang.rust) ` + "plugins enabled. These are known to conflict and cause various functions of " + "both plugins to not work correctly. You should disable one of them.", "Got it") .then(() => { }, console.error); - }; + } } /** diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts deleted file mode 100644 index bd1bb5f8a8a8..000000000000 --- a/editors/code/src/net.ts +++ /dev/null @@ -1,228 +0,0 @@ -import fetch from "node-fetch"; -var HttpsProxyAgent = require('https-proxy-agent'); - -import * as vscode from "vscode"; -import * as stream from "stream"; -import * as crypto from "crypto"; -import * as fs from "fs"; -import * as zlib from "zlib"; -import * as util from "util"; -import * as path from "path"; -import { log, assert } from "./util"; -import * as https from "https"; -import { ProxySettings } from "./config"; - -const pipeline = util.promisify(stream.pipeline); - -const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; -const OWNER = "rust-analyzer"; -const REPO = "rust-analyzer"; - -function makeHttpAgent(proxy: string | null | undefined, options?: https.AgentOptions) { - if (proxy) { - return new HttpsProxyAgent({ ...options, ...new URL(proxy) }); - } else { - return new https.Agent(options); - } -} - -export async function fetchRelease( - releaseTag: string, - githubToken: string | null | undefined, - proxySettings: ProxySettings, -): Promise { - - const apiEndpointPath = `/repos/${OWNER}/${REPO}/releases/tags/${releaseTag}`; - - const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; - - log.debug("Issuing request for released artifacts metadata to", requestUrl); - - const headers: Record = { Accept: "application/vnd.github.v3+json" }; - if (githubToken != null) { - headers.Authorization = "token " + githubToken; - } - - const response = await (() => { - if (proxySettings.proxy) { - log.debug(`Fetching release metadata via proxy: ${proxySettings.proxy}`); - } - const options: any = {}; - if (proxySettings.strictSSL) { - options["rejectUnauthorized"] = false; - } - const agent = makeHttpAgent(proxySettings.proxy, options); - return fetch(requestUrl, { headers: headers, agent: agent }); - })(); - - if (!response.ok) { - log.error("Error fetching artifact release info", { - requestUrl, - releaseTag, - response: { - headers: response.headers, - status: response.status, - body: await response.text(), - } - }); - - throw new Error( - `Got response ${response.status} when trying to fetch ` + - `release info for ${releaseTag} release` - ); - } - - // We skip runtime type checks for simplicity (here we cast from `unknown` to `GithubRelease`) - const release = await response.json() as GithubRelease; - return release; -} - -// We omit declaration of tremendous amount of fields that we are not using here -export interface GithubRelease { - name: string; - id: number; - // eslint-disable-next-line camelcase - published_at: string; - assets: Array<{ - name: string; - // eslint-disable-next-line camelcase - browser_download_url: vscode.Uri; - }>; -} - -interface DownloadOpts { - progressTitle: string; - url: vscode.Uri; - dest: vscode.Uri; - mode?: number; - gunzip?: boolean; - proxySettings: ProxySettings; -} - -export async function download(opts: DownloadOpts) { - // Put artifact into a temporary file (in the same dir for simplicity) - // to prevent partially downloaded files when user kills vscode - // This also avoids overwriting running executables - const randomHex = crypto.randomBytes(5).toString("hex"); - const rawDest = path.parse(opts.dest.fsPath); - const oldServerPath = vscode.Uri.joinPath(vscode.Uri.file(rawDest.dir), `${rawDest.name}-stale-${randomHex}${rawDest.ext}`); - const tempFilePath = vscode.Uri.joinPath(vscode.Uri.file(rawDest.dir), `${rawDest.name}${randomHex}`); - - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - cancellable: false, - title: opts.progressTitle - }, - async (progress, _cancellationToken) => { - let lastPercentage = 0; - await downloadFile(opts.url, tempFilePath, opts.mode, !!opts.gunzip, opts.proxySettings, (readBytes, totalBytes) => { - const newPercentage = Math.round((readBytes / totalBytes) * 100); - if (newPercentage !== lastPercentage) { - progress.report({ - message: `${newPercentage.toFixed(0)}%`, - increment: newPercentage - lastPercentage - }); - - lastPercentage = newPercentage; - } - }); - } - ); - - // Try to rename a running server to avoid EPERM on Windows - // NB: this can lead to issues if a running Code instance tries to restart the server. - try { - await vscode.workspace.fs.rename(opts.dest, oldServerPath, { overwrite: true }); - log.info(`Renamed old server binary ${opts.dest.fsPath} to ${oldServerPath.fsPath}`); - } catch (err) { - const fsErr = err as vscode.FileSystemError; - - // This is supposed to return `FileNotFound` (spelled as `EntryNotFound`) - // but instead `code` is `Unknown` and `name` is `EntryNotFound (FileSystemError) (FileSystemError)`. - // https://github.com/rust-analyzer/rust-analyzer/pull/10222 - if (!fsErr.code || fsErr.code !== "EntryNotFound" && fsErr.name.indexOf("EntryNotFound") === -1) { - log.error(`Cannot rename existing server instance: ${err}"`); - } - } - try { - await vscode.workspace.fs.rename(tempFilePath, opts.dest, { overwrite: true }); - } catch (err) { - log.error(`Cannot update server binary: ${err}`); - } - - // Now try to remove any stale server binaries - const serverDir = vscode.Uri.file(rawDest.dir); - try { - const entries = await vscode.workspace.fs.readDirectory(serverDir); - for (const [entry, _] of entries) { - try { - if (entry.includes(`${rawDest.name}-stale-`)) { - const uri = vscode.Uri.joinPath(serverDir, entry); - try { - await vscode.workspace.fs.delete(uri); - log.info(`Removed old server binary ${uri.fsPath}`); - } catch (err) { - log.error(`Unable to remove old server binary ${uri.fsPath}`); - } - } - } catch (err) { - log.error(`Unable to parse ${entry}`); - } - } - } catch (err) { - log.error(`Unable to enumerate contents of ${serverDir.fsPath}`); - } -} - -async function downloadFile( - url: vscode.Uri, - destFilePath: vscode.Uri, - mode: number | undefined, - gunzip: boolean, - proxySettings: ProxySettings, - onProgress: (readBytes: number, totalBytes: number) => void -): Promise { - const urlString = url.toString(); - - const res = await (() => { - if (proxySettings.proxy) { - log.debug(`Downloading ${urlString} via proxy: ${proxySettings.proxy}`); - } - const options: any = {}; - if (proxySettings.strictSSL) { - options["rejectUnauthorized"] = false; - } - const agent = makeHttpAgent(proxySettings.proxy, options); - return fetch(urlString, { agent: agent }); - })(); - - if (!res.ok) { - log.error("Error", res.status, "while downloading file from", urlString); - log.error({ body: await res.text(), headers: res.headers }); - - throw new Error(`Got response ${res.status} when trying to download a file.`); - } - - if (!res.body) { - log.error("Empty body while downloading file from", urlString); - log.error({ headers: res.headers }); - throw new Error(`Got empty body 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", urlString, "to", destFilePath.fsPath); - - let readBytes = 0; - res.body.on("data", (chunk: Buffer) => { - readBytes += chunk.length; - onProgress(readBytes, totalBytes); - }); - - const destFileStream = fs.createWriteStream(destFilePath.fsPath, { mode }); - const srcStream = gunzip ? res.body.pipe(zlib.createGunzip()) : res.body; - - await pipeline(srcStream, destFileStream); -} diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts index dd2aeecca354..3e86ed1e3231 100644 --- a/editors/code/src/persistent_state.ts +++ b/editors/code/src/persistent_state.ts @@ -3,34 +3,13 @@ import { log } from './util'; export class PersistentState { constructor(private readonly globalState: vscode.Memento) { - const { lastCheck, nightlyReleaseId, serverVersion } = this; - log.info("PersistentState:", { lastCheck, nightlyReleaseId, serverVersion }); - } - - /** - * Used to check for *nightly* updates once an hour. - */ - get lastCheck(): number | undefined { - return this.globalState.get("lastCheck"); - } - async updateLastCheck(value: number) { - await this.globalState.update("lastCheck", value); - } - - /** - * Release id of the *nightly* extension. - * Used to check if we should update. - */ - get nightlyReleaseId(): number | undefined { - return this.globalState.get("releaseId"); - } - async updateNightlyReleaseId(value: number | undefined) { - await this.globalState.update("releaseId", value); + const { serverVersion } = this; + log.info("PersistentState:", { serverVersion }); } /** * Version of the extension that installed the server. - * Used to check if we need to update the server. + * Used to check if we need to run patchelf again on NixOS. */ get serverVersion(): string | undefined { return this.globalState.get("serverVersion"); @@ -38,15 +17,4 @@ export class PersistentState { async updateServerVersion(value: string | undefined) { await this.globalState.update("serverVersion", value); } - - /** - * Github authorization token. - * This is used for API requests against the Github API. - */ - get githubToken(): string | undefined { - return this.globalState.get("githubToken"); - } - async updateGithubToken(value: string | undefined) { - await this.globalState.update("githubToken", value); - } }