fix(tools): install rust-analyzer without rustup (#490)

* fix(tools): install rust-analyzer without rustup

* fix(tools): reinstall a managed rust-analyzer with the installer that made it

Once rustup appeared, Reinstall ran rustup while the Open VSX copy stayed in use.
This commit is contained in:
l0ng-ai
2026-10-31 10:14:55 +08:00
committed by GitHub
parent 4a620122e8
commit 4f1feebbd0
4 changed files with 243 additions and 29 deletions
+39 -26
View File
@@ -138,7 +138,7 @@ fn bin_dirs_in(root: &Path, platform: Platform) -> Vec<PathBuf> {
}
fn managed_bin_dir(root: &Path, spec: &ToolSpec, platform: Platform) -> Option<PathBuf> {
match spec.method {
match spec.fallback.unwrap_or(spec.method) {
Method::Npm { .. } => Some(root.join("node_modules").join(".bin")),
Method::Go { .. } | Method::Gem { .. } | Method::DotnetTool { .. } | Method::Release { .. } => {
Some(root.join(spec.name).join("bin"))
@@ -520,16 +520,26 @@ impl Tools {
parse_version(&out)
}
/// The installer that owns this tool: a managed copy is refreshed and removed by the
/// fallback that made it, even if the primary installer has appeared since.
fn install_method(&self, spec: &ToolSpec) -> Method {
let Some(fallback) = spec.fallback else { return spec.method };
let managed = managed_bin_dir(&self.root, spec, self.platform)
.and_then(|dir| self.exe_in(&dir, spec.commands[0]))
.is_some();
if managed || self.method_missing(spec.method).is_some() { fallback } else { spec.method }
}
/// Requirement check for installing `spec` right now.
fn unavailable_reason(&self, spec: &ToolSpec) -> Option<String> {
self.installer_missing(spec)
self.method_missing(self.install_method(spec))
.or_else(|| spec.needs.iter().find(|(cmd, _)| self.which(cmd).is_none()).map(|(_, why)| why.to_string()))
}
/// What the install method itself lacks (npm, a build for this platform, …).
fn installer_missing(&self, spec: &ToolSpec) -> Option<String> {
fn method_missing(&self, method: Method) -> Option<String> {
let need = |ok: bool, msg: &str| (!ok).then(|| msg.to_string());
match spec.method {
match method {
Method::Rustup { .. } => need(self.which("rustup").is_some(), "rustup is required (https://rustup.rs)"),
Method::Npm { .. } => need(
self.which_any(&["npm", "pnpm"]).is_some(),
@@ -602,7 +612,7 @@ impl Tools {
status,
path: path.map(Into::into),
version,
install_method: Some(spec.method.label().to_string()),
install_method: Some(self.install_method(spec).label().to_string()),
can_install: unavailable_reason.is_none(),
unavailable_reason,
can_uninstall,
@@ -643,7 +653,16 @@ impl Tools {
fn detect(&self, spec: &ToolSpec) -> (ToolStatus, Option<PathBuf>, Option<String>) {
let main = spec.commands[0];
match spec.method {
let managed = managed_bin_dir(&self.root, spec, self.platform).and_then(|d| self.exe_in(&d, main));
if let Some(path) = managed {
let version = match spec.method {
Method::Npm { packages } => self.npm_package_version(npm_name(packages[0])),
_ => self.manifest(spec.name).and_then(|m| m.version),
}
.or_else(|| self.query_version(spec, &path));
return (ToolStatus::Installed, Some(path), version);
}
match self.install_method(spec) {
Method::Rustup { .. } => {
let Some(path) = self.which(main) else { return (ToolStatus::Missing, None, None) };
// The rustup proxy exists even without the component; it only answers when installed.
@@ -678,24 +697,13 @@ impl Tools {
_ => (ToolStatus::Missing, None, None),
}
}
_ => {
let managed = managed_bin_dir(&self.root, spec, self.platform).and_then(|d| self.exe_in(&d, main));
if let Some(path) = managed {
let version = match spec.method {
Method::Npm { packages } => self.npm_package_version(npm_name(packages[0])),
_ => self.manifest(spec.name).and_then(|m| m.version),
}
.or_else(|| self.query_version(spec, &path));
return (ToolStatus::Installed, Some(path), version);
_ => match self.which(main) {
Some(path) => {
let version = self.query_version(spec, &path);
(ToolStatus::OnPath, Some(path), version)
}
match self.which(main) {
Some(path) => {
let version = self.query_version(spec, &path);
(ToolStatus::OnPath, Some(path), version)
}
None => (ToolStatus::Missing, None, None),
}
}
None => (ToolStatus::Missing, None, None),
},
}
}
@@ -769,8 +777,9 @@ impl Tools {
let emit = &fan as Emit;
let result = (|| {
std::fs::create_dir_all(&self.root)?;
emit(InstallEvent::Line { text: format!("Installing {} via {}…", spec.display, spec.method.label()) });
match spec.method {
let method = self.install_method(spec);
emit(InstallEvent::Line { text: format!("Installing {} via {}…", spec.display, method.label()) });
match method {
Method::Rustup { component } => {
let mut c = self.command(self.which("rustup").expect("checked"));
c.args(["component", "add", component]);
@@ -1072,6 +1081,10 @@ impl Tools {
}
ReleaseSource::OpenVsx { namespace, extension } => {
let json = http.get_text(&sources::openvsx_latest_url(namespace, extension), cancel)?;
let json = match sources::openvsx_platform_url(&json, assets, self.platform)? {
Some(url) => http.get_text(&url, cancel)?,
None => json,
};
sources::openvsx_pick(&json, assets, self.platform)
}
ReleaseSource::MavenCentral { group, artifact } => {
@@ -1172,7 +1185,7 @@ impl Tools {
let _guard = InstallGuard::acquire(&format!("uninstall-{name}"))?;
let cancel = CancellationToken::new();
let quiet = |_: InstallEvent| {};
match spec.method {
match self.install_method(spec) {
Method::Rustup { component } => {
let rustup = self.which("rustup").ok_or_else(|| IdeError::unavailable("rustup not found"))?;
let mut c = self.command(rustup);
+63
View File
@@ -82,6 +82,8 @@ pub struct ToolSpec {
/// is a plugin bundle loaded by another tool (copied to `bin/` under this name).
pub commands: &'static [&'static str],
pub method: Method,
/// Used when the primary installer is unavailable.
pub fallback: Option<Method>,
/// Version query: a command (looked up next to the main binary first) and its args.
pub version: Option<(&'static str, &'static [&'static str])>,
pub homepage: &'static str,
@@ -123,6 +125,18 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["rust-analyzer"],
method: Method::Rustup { component: "rust-analyzer" },
fallback: Some(Method::Release {
source: ReleaseSource::OpenVsx { namespace: "rust-lang", extension: "rust-analyzer" },
assets: &[
rule(Mac, Arm64, "rust-lang.rust-analyzer-*@darwin-arm64.vsix"),
rule(Mac, X64, "rust-lang.rust-analyzer-*@darwin-x64.vsix"),
rule(Linux, Arm64, "rust-lang.rust-analyzer-*@linux-arm64.vsix"),
rule(Linux, X64, "rust-lang.rust-analyzer-*@linux-x64.vsix"),
rule(Windows, Arm64, "rust-lang.rust-analyzer-*@win32-arm64.vsix"),
rule(Windows, X64, "rust-lang.rust-analyzer-*@win32-x64.vsix"),
],
locate: &["rust-analyzer"],
}),
version: ver("rust-analyzer"),
homepage: "https://rust-analyzer.github.io",
needs: &[],
@@ -133,6 +147,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["vtsls"],
method: Method::Npm { packages: &["@vtsls/language-server"] },
fallback: None,
version: V,
homepage: "https://github.com/yioneko/vtsls",
needs: &[],
@@ -143,6 +158,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["typescript-language-server"],
method: Method::Npm { packages: &["typescript-language-server", TYPESCRIPT] },
fallback: None,
version: ver("typescript-language-server"),
homepage: "https://github.com/typescript-language-server/typescript-language-server",
needs: &[],
@@ -153,6 +169,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["pyright-langserver", "pyright"],
method: Method::Npm { packages: &["pyright"] },
fallback: None,
version: ver("pyright"),
homepage: "https://github.com/microsoft/pyright",
needs: &[],
@@ -163,6 +180,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["basedpyright-langserver", "basedpyright"],
method: Method::PipVenv { package: "basedpyright" },
fallback: None,
version: ver("basedpyright"),
homepage: "https://docs.basedpyright.com",
needs: &[],
@@ -173,6 +191,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["ty"],
method: Method::PipVenv { package: "ty" },
fallback: None,
version: ver("ty"),
homepage: "https://docs.astral.sh/ty/",
needs: &[],
@@ -183,6 +202,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["bash-language-server"],
method: Method::Npm { packages: &["bash-language-server"] },
fallback: None,
version: ver("bash-language-server"),
homepage: "https://github.com/bash-lsp/bash-language-server",
needs: &[],
@@ -193,6 +213,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["yaml-language-server"],
method: Method::Npm { packages: &["yaml-language-server"] },
fallback: None,
version: V,
homepage: "https://github.com/redhat-developer/yaml-language-server",
needs: &[],
@@ -203,6 +224,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["vscode-json-language-server"],
method: Method::Npm { packages: &["vscode-langservers-extracted"] },
fallback: None,
version: V,
homepage: "https://github.com/hrsh7th/vscode-langservers-extracted",
needs: &[],
@@ -213,6 +235,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["vscode-css-language-server"],
method: Method::Npm { packages: &["vscode-langservers-extracted"] },
fallback: None,
version: V,
homepage: "https://github.com/hrsh7th/vscode-langservers-extracted",
needs: &[],
@@ -223,6 +246,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["vscode-html-language-server"],
method: Method::Npm { packages: &["vscode-langservers-extracted"] },
fallback: None,
version: V,
homepage: "https://github.com/hrsh7th/vscode-langservers-extracted",
needs: &[],
@@ -233,6 +257,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["docker-langserver"],
method: Method::Npm { packages: &["dockerfile-language-server-nodejs"] },
fallback: None,
version: V,
homepage: "https://github.com/rcjsuen/dockerfile-language-server",
needs: &[],
@@ -243,6 +268,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["svelteserver"],
method: Method::Npm { packages: &["svelte-language-server", TYPESCRIPT] },
fallback: None,
version: V,
homepage: "https://github.com/sveltejs/language-tools",
needs: &[],
@@ -253,6 +279,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["vue-language-server"],
method: Method::Npm { packages: &["@vue/language-server", TYPESCRIPT] },
fallback: None,
version: V,
homepage: "https://github.com/vuejs/language-tools",
needs: &[],
@@ -264,6 +291,7 @@ pub static TOOLS: &[ToolSpec] = &[
commands: &["ngserver"],
// Brings the TypeScript and `@angular/language-service` it loads.
method: Method::Npm { packages: &["@angular/language-server"] },
fallback: None,
version: V,
homepage: "https://angular.dev/tools/language-service",
needs: &[],
@@ -274,6 +302,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["graphql-lsp"],
method: Method::Npm { packages: &["graphql-language-service-cli"] },
fallback: None,
version: V,
homepage: "https://github.com/graphql/graphiql",
needs: &[],
@@ -284,6 +313,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["intelephense"],
method: Method::Npm { packages: &["intelephense"] },
fallback: None,
version: V,
homepage: "https://intelephense.com",
needs: &[],
@@ -294,6 +324,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["gopls"],
method: Method::Go { module: "golang.org/x/tools/gopls@latest" },
fallback: None,
version: Some(("gopls", &["version"])),
homepage: "https://pkg.go.dev/golang.org/x/tools/gopls",
needs: &[],
@@ -304,6 +335,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["sqls"],
method: Method::Go { module: "github.com/sqls-server/sqls@latest" },
fallback: None,
version: Some(("sqls", &["--version"])),
homepage: "https://github.com/sqls-server/sqls",
needs: &[],
@@ -314,6 +346,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: Debugger,
commands: &["dlv"],
method: Method::Go { module: "github.com/go-delve/delve/cmd/dlv@latest" },
fallback: None,
version: Some(("dlv", &["version"])),
homepage: "https://github.com/go-delve/delve",
needs: &[],
@@ -324,6 +357,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: Debugger,
commands: &["python3"],
method: Method::PipTarget { package: "debugpy", module: "debugpy" },
fallback: None,
version: V,
homepage: "https://github.com/microsoft/debugpy",
needs: &[],
@@ -342,6 +376,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["clangd"],
},
fallback: None,
version: ver("clangd"),
homepage: "https://clangd.llvm.org",
needs: &[],
@@ -362,6 +397,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["lua-language-server"],
},
fallback: None,
version: ver("lua-language-server"),
homepage: "https://luals.github.io",
needs: &[],
@@ -381,6 +417,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["marksman"],
},
fallback: None,
version: ver("marksman"),
homepage: "https://github.com/artempyanykh/marksman",
needs: &[],
@@ -401,6 +438,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["taplo"],
},
fallback: None,
version: ver("taplo"),
homepage: "https://taplo.tamasfe.dev",
needs: &[],
@@ -421,6 +459,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["zls"],
},
fallback: None,
version: ver("zls"),
homepage: "https://github.com/zigtools/zls",
needs: &[],
@@ -441,6 +480,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["terraform-ls"],
},
fallback: None,
version: Some(("terraform-ls", &["version"])),
homepage: "https://github.com/hashicorp/terraform-ls",
needs: &[],
@@ -466,6 +506,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["codelldb"],
},
fallback: None,
version: V,
homepage: "https://github.com/vadimcn/codelldb",
needs: &[],
@@ -485,6 +526,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["dapDebugServer.js"],
},
fallback: None,
version: V,
homepage: "https://github.com/microsoft/vscode-js-debug",
needs: &[],
@@ -503,6 +545,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["jdtls", "jdtls.bat"],
},
fallback: None,
version: V,
homepage: "https://github.com/eclipse-jdtls/eclipse.jdt.ls",
needs: &[],
@@ -522,6 +565,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["com.microsoft.java.debug.plugin-*.jar"],
},
fallback: None,
version: V,
homepage: "https://github.com/microsoft/java-debug",
needs: &[],
@@ -541,6 +585,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["lombok.jar"],
},
fallback: None,
version: V,
homepage: "https://projectlombok.org",
needs: &[],
@@ -565,6 +610,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["kotlin-lsp", "kotlin-lsp.sh", "kotlin-lsp.cmd"],
},
fallback: None,
version: V,
homepage: "https://github.com/Kotlin/kotlin-lsp",
needs: &[],
@@ -581,6 +627,7 @@ pub static TOOLS: &[ToolSpec] = &[
assets: ELIXIR_LS_ASSETS,
locate: &["language_server.sh", "language_server.bat"],
},
fallback: None,
version: V,
homepage: "https://github.com/elixir-lsp/elixir-ls",
needs: ELIXIR,
@@ -595,6 +642,7 @@ pub static TOOLS: &[ToolSpec] = &[
assets: ELIXIR_LS_ASSETS,
locate: &["debug_adapter.sh", "debug_adapter.bat"],
},
fallback: None,
version: V,
homepage: "https://github.com/elixir-lsp/elixir-ls",
needs: ELIXIR,
@@ -616,6 +664,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["buf"],
},
fallback: None,
version: ver("buf"),
homepage: "https://buf.build",
needs: &[],
@@ -636,6 +685,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["clojure-lsp"],
},
fallback: None,
version: V,
homepage: "https://clojure-lsp.io",
needs: &[],
@@ -657,6 +707,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["texlab"],
},
fallback: None,
version: ver("texlab"),
homepage: "https://github.com/latex-lsp/texlab",
needs: &[],
@@ -677,6 +728,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["neocmakelsp"],
},
fallback: None,
version: ver("neocmakelsp"),
homepage: "https://github.com/Decodetalkers/neocmakelsp",
needs: &[],
@@ -697,6 +749,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["lemminx-*"],
},
fallback: None,
version: V,
homepage: "https://github.com/eclipse/lemminx",
needs: &[],
@@ -715,6 +768,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["perlnavigator"],
},
fallback: None,
version: V,
homepage: "https://github.com/bscan/PerlNavigator",
needs: &[("perl", "Perl is required (https://www.perl.org/get.html)")],
@@ -729,6 +783,7 @@ pub static TOOLS: &[ToolSpec] = &[
assets: &[any_arch(Mac, "server.zip"), any_arch(Linux, "server.zip"), any_arch(Windows, "server.zip")],
locate: &["kotlin-language-server"],
},
fallback: None,
version: V,
homepage: "https://github.com/fwcd/kotlin-language-server",
needs: JAVA,
@@ -743,6 +798,7 @@ pub static TOOLS: &[ToolSpec] = &[
assets: &[any_arch(Mac, "adapter.zip"), any_arch(Linux, "adapter.zip"), any_arch(Windows, "adapter.zip")],
locate: &["kotlin-debug-adapter"],
},
fallback: None,
version: V,
homepage: "https://github.com/fwcd/kotlin-debug-adapter",
needs: JAVA,
@@ -762,6 +818,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["netcoredbg"],
},
fallback: None,
version: V,
homepage: "https://github.com/Samsung/netcoredbg",
needs: &[],
@@ -780,6 +837,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["bashDebug.js"],
},
fallback: None,
version: V,
homepage: "https://github.com/rogalmic/vscode-bash-debug",
needs: NODE,
@@ -798,6 +856,7 @@ pub static TOOLS: &[ToolSpec] = &[
],
locate: &["phpDebug.js"],
},
fallback: None,
version: V,
homepage: "https://github.com/xdebug/vscode-php-debug",
needs: NODE,
@@ -808,6 +867,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["ruby-lsp"],
method: Method::Gem { gem: "ruby-lsp" },
fallback: None,
version: V,
homepage: "https://shopify.github.io/ruby-lsp",
needs: &[],
@@ -818,6 +878,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["solargraph"],
method: Method::Gem { gem: "solargraph" },
fallback: None,
version: ver("solargraph"),
homepage: "https://solargraph.org",
needs: &[],
@@ -828,6 +889,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["csharp-ls"],
method: Method::DotnetTool { package: "csharp-ls" },
fallback: None,
version: V,
homepage: "https://github.com/razzmatazz/csharp-language-server",
needs: &[],
@@ -838,6 +900,7 @@ pub static TOOLS: &[ToolSpec] = &[
kind: LanguageServer,
commands: &["fsautocomplete"],
method: Method::DotnetTool { package: "fsautocomplete" },
fallback: None,
version: V,
homepage: "https://github.com/ionide/FsAutoComplete",
needs: &[],
+68 -1
View File
@@ -177,6 +177,8 @@ pub fn jdtls_download(version: &str, latest_txt: &str) -> IdeResult<Download> {
#[derive(Deserialize)]
struct VsxExtension {
version: String,
#[serde(default)]
downloads: std::collections::BTreeMap<String, String>,
files: VsxFiles,
}
@@ -191,7 +193,22 @@ pub fn openvsx_latest_url(namespace: &str, extension: &str) -> String {
format!("https://open-vsx.org/api/{namespace}/{extension}/latest")
}
/// The `.vsix` of an Open VSX `…/latest` response (platform-independent extensions only).
/// Select platform metadata when the universal package does not match the asset rules.
pub fn openvsx_platform_url(json: &str, rules: &[AssetRule], platform: Platform) -> IdeResult<Option<String>> {
let ext: VsxExtension =
serde_json::from_str(json).map_err(|e| proto(format!("unexpected Open VSX response: {e}")))?;
if select_asset(&[file_name(&ext.files.download)], rules, platform).is_some() {
return Ok(None);
}
let names: Vec<&str> = ext.downloads.values().map(|url| file_name(url)).collect();
let name = select_asset(&names, rules, platform)
.ok_or_else(|| IdeError::unavailable("No Open VSX package for this platform"))?;
let url = ext.downloads.values().find(|url| file_name(url) == name).expect("selected download");
let (metadata, _) = url.rsplit_once("/file/").ok_or_else(|| proto("unexpected Open VSX download URL"))?;
Ok(Some(metadata.to_string()))
}
/// The `.vsix` of an Open VSX `…/latest` response.
pub fn openvsx_pick(json: &str, rules: &[AssetRule], platform: Platform) -> IdeResult<Download> {
let ext: VsxExtension =
serde_json::from_str(json).map_err(|e| proto(format!("unexpected Open VSX response: {e}")))?;
@@ -355,6 +372,56 @@ mod tests {
assert!(openvsx_pick(&other, rules, LINUX).is_err());
}
#[test]
fn openvsx_rust_analyzer_platform_packages() {
use crate::platform::{Arch, Os};
let crate::registry::Method::Release { assets, .. } = find("rust-analyzer").unwrap().fallback.unwrap() else {
panic!("expected release fallback")
};
let targets = [
(Os::Mac, Arch::Arm64, "darwin-arm64"),
(Os::Mac, Arch::X64, "darwin-x64"),
(Os::Linux, Arch::Arm64, "linux-arm64"),
(Os::Linux, Arch::X64, "linux-x64"),
(Os::Windows, Arch::Arm64, "win32-arm64"),
(Os::Windows, Arch::X64, "win32-x64"),
];
let base = "https://open-vsx.org/api/rust-lang/rust-analyzer";
let downloads: serde_json::Map<String, serde_json::Value> = targets
.iter()
.map(|(_, _, target)| {
(
target.to_string(),
format!("{base}/{target}/1.0.0/file/rust-lang.rust-analyzer-1.0.0@{target}.vsix").into(),
)
})
.collect();
let universal = serde_json::json!({
"version": "1.0.0", "downloads": downloads,
"files": {"download": format!("{base}/1.0.0/file/rust-lang.rust-analyzer-1.0.0.vsix")}
})
.to_string();
for (os, arch, target) in targets {
let platform = Platform { os, arch };
assert_eq!(
openvsx_platform_url(&universal, assets, platform).unwrap(),
Some(format!("{base}/{target}/1.0.0"))
);
let json = serde_json::json!({
"version": "1.0.0",
"files": {
"download": downloads[target],
"sha256": format!("{base}/{target}/1.0.0/file/rust-lang.rust-analyzer-1.0.0@{target}.sha256")
}
})
.to_string();
let download = openvsx_pick(&json, assets, platform).unwrap();
assert!(download.name.ends_with(&format!("@{target}.vsix")));
assert!(download.checksum_url.unwrap().ends_with(&format!("@{target}.sha256")));
}
assert!(openvsx_platform_url(&universal.replace("downloads", "unused"), assets, MAC_ARM).is_err());
}
#[test]
fn maven_central() {
let xml =
+73 -2
View File
@@ -65,8 +65,8 @@ fn everything_missing_on_an_empty_system() {
let ra = find(&list, "rust-analyzer");
assert_eq!(ra.status, ToolStatus::Missing);
assert_eq!(ra.languages, vec!["Rust".to_string()]);
assert!(!ra.can_install, "rustup is not on the fake PATH");
assert!(ra.unavailable_reason.as_deref().unwrap().contains("rustup"));
assert!(!ra.can_install, "curl is not on the fake PATH");
assert!(ra.unavailable_reason.as_deref().unwrap().contains("curl"));
let vtsls = find(&list, "vtsls");
assert!(vtsls.languages.iter().any(|l| l == "TypeScript"));
assert!(vtsls.unavailable_reason.as_deref().unwrap().contains("npm"));
@@ -552,3 +552,74 @@ fn the_installing_request_can_still_cancel_its_shared_install() {
assert!(slot.cancel.is_cancelled());
release_install_slot("taplo", &slot);
}
#[cfg(unix)]
#[test]
fn rust_analyzer_selects_openvsx_without_rustup() {
let env = Env::new();
env.script(&env.sys, "curl", "exit 0");
let tools = env.tools();
let spec = registry::find("rust-analyzer").unwrap();
assert!(matches!(
tools.install_method(spec),
Method::Release { source: ReleaseSource::OpenVsx { namespace: "rust-lang", extension: "rust-analyzer" }, .. }
));
let info = tools.info("rust-analyzer", &langs()).unwrap();
assert_eq!(info.status, ToolStatus::Missing);
assert_eq!(info.install_method.as_deref(), Some("Open VSX"));
assert!(info.can_install);
assert!(info.unavailable_reason.is_none());
assert!(bin_dirs_in(&env.root, Platform::current()).contains(&env.root.join("rust-analyzer/bin")));
}
#[cfg(unix)]
static RUST_ANALYZER_INSTALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
#[cfg(unix)]
#[test]
fn rust_analyzer_prefers_rustup_and_installs_component() {
let _guard = RUST_ANALYZER_INSTALL.lock();
let env = Env::new();
env.script(&env.sys, "rustup", "test \"$*\" = 'component add rust-analyzer'");
env.script(&env.sys, "rust-analyzer", "echo 'rust-analyzer 1.0.0'");
let tools = env.tools();
assert!(matches!(
tools.install_method(registry::find("rust-analyzer").unwrap()),
Method::Rustup { component: "rust-analyzer" }
));
let info = tools.info("rust-analyzer", &langs()).unwrap();
assert_eq!(info.install_method.as_deref(), Some("rustup"));
assert!(info.can_install, "rustup does not require curl");
let mut lines = Vec::new();
let emit = parking_lot::Mutex::new(&mut lines);
tools
.install("rust-analyzer", &langs(), &CancellationToken::new(), &|event| {
if let InstallEvent::Line { text } = event {
emit.lock().push(text);
}
})
.unwrap();
assert!(lines.iter().any(|line| line.contains("via rustup")));
}
#[cfg(unix)]
#[test]
fn rust_analyzer_managed_copy_survives_rustup_becoming_available() {
let _guard = RUST_ANALYZER_INSTALL.lock();
let env = Env::new();
let bin = env.root.join("rust-analyzer/bin");
env.script(&bin, "rust-analyzer", "echo 'rust-analyzer 1.0.0'");
for with_rustup in [false, true] {
if with_rustup {
env.script(&env.sys, "rustup", "exit 1");
env.script(&env.sys, "rust-analyzer", "exit 1");
}
let info = env.tools().info("rust-analyzer", &langs()).unwrap();
assert_eq!(info.status, ToolStatus::Installed);
assert_eq!(info.path.as_deref(), Some(bin.join("rust-analyzer").as_path()));
assert_eq!(info.install_method.as_deref(), Some("Open VSX"), "Reinstall refreshes the copy in use");
assert!(info.can_uninstall);
}
env.tools().uninstall("rust-analyzer", &langs()).unwrap();
assert!(!env.root.join("rust-analyzer").exists());
}