From cf51b579241ed8aeb0c3a6dd629f04fe31ac6585 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:47:50 +0800 Subject: [PATCH] Add Windows ARM64 and Linux ARM64 desktop releases (#516) * build: verify native Windows and Linux ARM64 bundles * fix: select native ARM64 tools before Windows x64 fallback * build: register verified ARM64 updater bundles and remove verification workflow * fix(agent): prefer npx and uvx over emulated x64 binaries on Windows ARM64 --- .github/workflows/release.yml | 11 +++++++++ crates/ide-agent/src/registry.rs | 21 ++++++++++++++++ crates/ide-tools/src/jdk.rs | 6 ++--- crates/ide-tools/src/platform.rs | 41 ++++++++++++++++++++++++++++---- crates/ide-tools/src/registry.rs | 3 +++ 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acf4a20f2..1527d20fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,6 +70,12 @@ jobs: - name: Linux x64 os: ubuntu-22.04 target: x86_64-unknown-linux-gnu + - name: Linux arm64 + os: ubuntu-22.04-arm + target: aarch64-unknown-linux-gnu + - name: Windows arm64 + os: windows-11-arm + target: aarch64-pc-windows-msvc - name: Windows x64 os: windows-latest target: x86_64-pc-windows-msvc @@ -196,11 +202,16 @@ jobs: } // Bundle suffix → updater platform keys, as tauri-action names them. const kinds = [ + [/_aarch64\.AppImage$/, ["linux-aarch64", "linux-aarch64-appimage"]], + [/_arm64\.deb$/, ["linux-aarch64-deb"]], + [/\.aarch64\.rpm$/, ["linux-aarch64-rpm"]], [/_amd64\.AppImage$/, ["linux-x86_64", "linux-x86_64-appimage"]], [/_amd64\.deb$/, ["linux-x86_64-deb"]], [/\.x86_64\.rpm$/, ["linux-x86_64-rpm"]], [/_aarch64\.app\.tar\.gz$/, ["darwin-aarch64", "darwin-aarch64-app"]], [/_x64\.app\.tar\.gz$/, ["darwin-x86_64", "darwin-x86_64-app"]], + [/_arm64_en-US\.msi$/, ["windows-aarch64", "windows-aarch64-msi"]], + [/_arm64-setup\.exe$/, ["windows-aarch64-nsis"]], [/_x64_en-US\.msi$/, ["windows-x86_64", "windows-x86_64-msi"]], [/_x64-setup\.exe$/, ["windows-x86_64-nsis"]], ]; diff --git a/crates/ide-agent/src/registry.rs b/crates/ide-agent/src/registry.rs index c9bef601b..26bf31f3f 100644 --- a/crates/ide-agent/src/registry.rs +++ b/crates/ide-agent/src/registry.rs @@ -222,6 +222,13 @@ fn choose<'a>(entry: &'a Entry, platform: Platform, on_path: &dyn Fn(&str) -> bo { return Some(Choice::Uvx(p)); } + // Last resort: npx and uvx run natively, this one under OS emulation. + if platform.os == Os::Windows + && platform.arch == Arch::Arm64 + && let Some(b) = d.binary.get("windows-x86_64") + { + return Some(Choice::Binary(b)); + } None } @@ -689,6 +696,20 @@ mod tests { const LINUX: Platform = Platform { os: Os::Linux, arch: Arch::X64 }; const WINDOWS: Platform = Platform { os: Os::Windows, arch: Arch::X64 }; + #[test] + fn windows_arm_binary_prefers_native_then_x64() { + let mut entry = parse(INDEX).unwrap().remove(0); + let arm = Platform { os: Os::Windows, arch: Arch::Arm64 }; + assert!(matches!(choose(&entry, arm, &|_| false), Some(Choice::Binary(_)))); + assert!(choose(&entry, Platform { os: Os::Linux, arch: Arch::Arm64 }, &|_| false).is_none()); + entry.distribution.npx = Some(serde_json::from_str(r#"{"package":"amp-acp"}"#).unwrap()); + assert!(matches!(choose(&entry, arm, &|_| true), Some(Choice::Npx(_)))); + let mut native = entry.distribution.binary["windows-x86_64"].clone(); + native.cmd = "native.exe".into(); + entry.distribution.binary.insert("windows-aarch64".into(), native); + assert!(matches!(choose(&entry, arm, &|_| false), Some(Choice::Binary(b)) if b.cmd == "native.exe")); + } + const INDEX: &str = r#"{"version":"1.0.0","agents":[ {"id":"amp-acp","name":"Amp","version":"0.10.0","description":"Amp over ACP", "repository":"https://github.com/x/amp-acp","authors":["x"],"license":"Apache-2.0", diff --git a/crates/ide-tools/src/jdk.rs b/crates/ide-tools/src/jdk.rs index 0b85718e5..a96ff2924 100644 --- a/crates/ide-tools/src/jdk.rs +++ b/crates/ide-tools/src/jdk.rs @@ -323,9 +323,9 @@ impl Tools { picked = adoptium_pick(&http.get_text(&adoptium_assets_url(feature, self.platform), cancel)?, self.platform)?; } - if picked.is_none() && self.platform.os == Os::Mac && self.platform.arch == Arch::Arm64 { - // Java 8 has no Apple Silicon build: the Intel one runs under Rosetta. - let intel = Platform { os: Os::Mac, arch: Arch::X64 }; + if picked.is_none() && matches!(self.platform.os, Os::Mac | Os::Windows) && self.platform.arch == Arch::Arm64 { + // Older JDKs may need the OS-provided x64 emulation. + let intel = Platform { os: self.platform.os, arch: Arch::X64 }; picked = adoptium_pick(&http.get_text(&adoptium_assets_url(feature, intel), cancel)?, intel)?; } let (dl, major) = picked.ok_or_else(|| { diff --git a/crates/ide-tools/src/platform.rs b/crates/ide-tools/src/platform.rs index c5e455dfa..d32b07ac7 100644 --- a/crates/ide-tools/src/platform.rs +++ b/crates/ide-tools/src/platform.rs @@ -61,8 +61,8 @@ pub const fn any_arch(os: Os, pattern: &'static str) -> AssetRule { } /// Picks the asset for `platform`: rules are tried in order and the first -/// rule matching any asset name wins. Apple Silicon falls back to x64 assets -/// (Rosetta) when no native build exists. +/// rule matching any asset name wins. ARM64 macOS and Windows fall back to x64 assets +/// under OS emulation when no native build exists. pub fn select_asset<'a>(names: &[&'a str], rules: &[AssetRule], platform: Platform) -> Option<&'a str> { let pick = |arch: Arch| { rules @@ -70,8 +70,11 @@ pub fn select_asset<'a>(names: &[&'a str], rules: &[AssetRule], platform: Platfo .filter(|r| r.os == platform.os && r.arch.is_none_or(|a| a == arch)) .find_map(|r| names.iter().copied().find(|n| glob_match(r.pattern, n))) }; - pick(platform.arch) - .or_else(|| (platform.os == Os::Mac && platform.arch == Arch::Arm64).then(|| pick(Arch::X64)).flatten()) + pick(platform.arch).or_else(|| { + (matches!(platform.os, Os::Mac | Os::Windows) && platform.arch == Arch::Arm64) + .then(|| pick(Arch::X64)) + .flatten() + }) } /// Case-insensitive glob with `*` wildcards only. @@ -175,6 +178,36 @@ mod tests { assert_eq!(select_asset(&old, rules, LINUX_X64), Some("codelldb-x86_64-linux.vsix")); } + #[test] + fn arm64_native_then_emulated_assets() { + for os in [Os::Mac, Os::Windows, Os::Linux] { + let rules = [rule(os, Arch::X64, "tool-x64"), rule(os, Arch::Arm64, "tool-arm64")]; + let platform = Platform { os, arch: Arch::Arm64 }; + assert_eq!(select_asset(&["tool-x64", "tool-arm64"], &rules, platform), Some("tool-arm64")); + let fallback = if os == Os::Linux { None } else { Some("tool-x64") }; + assert_eq!(select_asset(&["tool-x64"], &rules, platform), fallback); + assert_eq!(select_asset(&[], &rules, platform), None); + } + } + + #[test] + fn native_windows_release_rules() { + let platform = Platform { os: Os::Windows, arch: Arch::Arm64 }; + for (tool, asset) in [ + ("zls", "zls-aarch64-windows.zip"), + ("terraform-ls", "terraform-ls_0.39.1_windows_arm64.zip"), + ("taplo", "taplo-windows-aarch64.zip"), + ("buf", "buf-Windows-arm64.zip"), + ("texlab", "texlab-aarch64-windows.zip"), + ("neocmakelsp", "neocmakelsp-aarch64-pc-windows-msvc.zip"), + ] { + assert_eq!( + select_asset(&[asset], crate::registry::find(tool).unwrap().asset_rules(), platform), + Some(asset) + ); + } + } + #[test] fn exe_suffix() { assert_eq!(WIN.exe("clangd"), "clangd.exe"); diff --git a/crates/ide-tools/src/registry.rs b/crates/ide-tools/src/registry.rs index 161d9dc86..8bac71906 100644 --- a/crates/ide-tools/src/registry.rs +++ b/crates/ide-tools/src/registry.rs @@ -435,6 +435,7 @@ pub static TOOLS: &[ToolSpec] = &[ rule(Linux, X64, "taplo-linux-x86_64.gz"), rule(Linux, Arm64, "taplo-linux-aarch64.gz"), rule(Windows, X64, "taplo-windows-x86_64.zip"), + rule(Windows, Arm64, "taplo-windows-aarch64.zip"), ], locate: &["taplo"], }, @@ -456,6 +457,7 @@ pub static TOOLS: &[ToolSpec] = &[ rule(Linux, X64, "zls-x86_64-linux.tar.xz"), rule(Linux, Arm64, "zls-aarch64-linux.tar.xz"), rule(Windows, X64, "zls-x86_64-windows.zip"), + rule(Windows, Arm64, "zls-aarch64-windows.zip"), ], locate: &["zls"], }, @@ -477,6 +479,7 @@ pub static TOOLS: &[ToolSpec] = &[ rule(Linux, X64, "terraform-ls_*_linux_amd64.zip"), rule(Linux, Arm64, "terraform-ls_*_linux_arm64.zip"), rule(Windows, X64, "terraform-ls_*_windows_amd64.zip"), + rule(Windows, Arm64, "terraform-ls_*_windows_arm64.zip"), ], locate: &["terraform-ls"], },