diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3a0a3ce90..aa62cdbe9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,11 +42,27 @@ jobs: runner: ubuntu-latest - target: aarch64-unknown-linux-gnu runner: ubuntu-22.04-arm + - target: x86_64-unknown-linux-musl + runner: ubuntu-24.04 + musl: true + - target: aarch64-unknown-linux-musl + runner: ubuntu-24.04-arm + musl: true + env: + CC_x86_64_unknown_linux_musl: musl-gcc + CC_aarch64_unknown_linux_musl: musl-gcc + CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc + LIBZ_SYS_STATIC: "1" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + - name: Install musl toolchain + if: matrix.musl + run: sudo apt-get update && sudo apt-get install -y musl-tools + - name: Set up Rust run: | rustup toolchain install stable --profile minimal --no-self-update diff --git a/Cargo.toml b/Cargo.toml index f01684acf..d15e1a846 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ tar = "0.4" cli-table = { version = "0.5", default-features = false } console = "0.15" dialoguer = "0.12" -git2 = { version = "0.20", default-features = false } +git2 = { version = "0.20", default-features = false, features = ["vendored-libgit2"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } tracing-appender = "0.2" diff --git a/apps/marketing/public/install.sh b/apps/marketing/public/install.sh index d16457fa9..21f07572d 100755 --- a/apps/marketing/public/install.sh +++ b/apps/marketing/public/install.sh @@ -57,6 +57,9 @@ case "$OS" in aarch64) TARGET="aarch64-unknown-linux-gnu" ;; *) error "Unsupported Linux architecture: $ARCH. Supported: x86_64, aarch64" ;; esac + if ldd --version 2>&1 | grep -qi musl; then + TARGET="${TARGET%-gnu}-musl" + fi ;; *) error "Unsupported OS: $OS. Supported platforms: macOS (Apple Silicon), Linux (x86_64, aarch64)" diff --git a/lib/crates/fabro-cli/src/commands/upgrade.rs b/lib/crates/fabro-cli/src/commands/upgrade.rs index 6594fa7d5..af09a1a37 100644 --- a/lib/crates/fabro-cli/src/commands/upgrade.rs +++ b/lib/crates/fabro-cli/src/commands/upgrade.rs @@ -277,12 +277,54 @@ async fn select_backend() -> Backend { fn detect_target() -> Result<&'static str> { match (std::env::consts::OS, std::env::consts::ARCH) { ("macos", "aarch64") => Ok("aarch64-apple-darwin"), - ("linux", "x86_64") => Ok("x86_64-unknown-linux-gnu"), - ("linux", "aarch64") => Ok("aarch64-unknown-linux-gnu"), + ("linux", "x86_64") => Ok(match detect_linux_libc() { + Libc::Musl => "x86_64-unknown-linux-musl", + Libc::Gnu => "x86_64-unknown-linux-gnu", + }), + ("linux", "aarch64") => Ok(match detect_linux_libc() { + Libc::Musl => "aarch64-unknown-linux-musl", + Libc::Gnu => "aarch64-unknown-linux-gnu", + }), (os, arch) => bail!("unsupported platform: {os}/{arch}"), } } +#[derive(Debug, PartialEq, Eq)] +enum Libc { + Gnu, + Musl, +} + +/// Parse `ldd --version` output to determine the host libc flavor. +/// +/// glibc's ldd writes to stdout ("ldd (Ubuntu GLIBC 2.35...)"); +/// musl's ldd writes to stderr ("musl libc (x86_64)\nVersion 1.2.4"). +/// Callers concatenate both streams before passing in. +fn parse_ldd_libc(output: &str) -> Libc { + if output.to_ascii_lowercase().contains("musl") { + Libc::Musl + } else { + Libc::Gnu + } +} + +fn detect_linux_libc() -> Libc { + #[expect( + clippy::disallowed_methods, + reason = "one-shot libc detection at upgrade startup; async overhead is unwarranted" + )] + let result = std::process::Command::new("ldd").arg("--version").output(); + let Ok(output) = result else { + return Libc::Gnu; + }; + let combined = format!( + "{}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + parse_ldd_libc(&combined) +} + // ── Version helpers ──────────────────────────────────────────────────────── fn parse_version_from_tag(tag: &str) -> Result { @@ -690,15 +732,49 @@ mod tests { let result = detect_target(); // We can only assert it succeeds on known CI platforms if cfg!(target_os = "linux") && cfg!(target_arch = "x86_64") { - assert_eq!(result.unwrap(), "x86_64-unknown-linux-gnu"); + let got = result.unwrap(); + assert!( + got == "x86_64-unknown-linux-gnu" || got == "x86_64-unknown-linux-musl", + "got {got}" + ); } else if cfg!(target_os = "macos") && cfg!(target_arch = "aarch64") { assert_eq!(result.unwrap(), "aarch64-apple-darwin"); } else if cfg!(target_os = "linux") && cfg!(target_arch = "aarch64") { - assert_eq!(result.unwrap(), "aarch64-unknown-linux-gnu"); + let got = result.unwrap(); + assert!( + got == "aarch64-unknown-linux-gnu" || got == "aarch64-unknown-linux-musl", + "got {got}" + ); } // On other platforms it would return an error, which is fine } + // -- Libc parsing -- + + #[test] + fn parse_ldd_libc_detects_glibc() { + assert_eq!( + parse_ldd_libc( + "ldd (Ubuntu GLIBC 2.35-0ubuntu3.4) 2.35\nCopyright (C) 2022 Free Software Foundation, Inc." + ), + Libc::Gnu, + ); + } + + #[test] + fn parse_ldd_libc_detects_musl() { + assert_eq!( + parse_ldd_libc("musl libc (x86_64)\nVersion 1.2.4\nDynamic Program Loader"), + Libc::Musl, + ); + } + + #[test] + fn parse_ldd_libc_defaults_to_gnu_on_unknown_output() { + assert_eq!(parse_ldd_libc(""), Libc::Gnu); + assert_eq!(parse_ldd_libc("some unrelated output"), Libc::Gnu); + } + // -- Version parsing -- #[test] diff --git a/lib/crates/fabro-cli/tests/it/cmd/upgrade.rs b/lib/crates/fabro-cli/tests/it/cmd/upgrade.rs index 62cdc4727..64f44409e 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/upgrade.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/upgrade.rs @@ -168,7 +168,7 @@ esac "[VERSION]".to_string(), )); filters.push(( - "(aarch64-apple-darwin|x86_64-unknown-linux-gnu|aarch64-unknown-linux-gnu)".to_string(), + "(aarch64-apple-darwin|x86_64-unknown-linux-gnu|aarch64-unknown-linux-gnu|x86_64-unknown-linux-musl|aarch64-unknown-linux-musl)".to_string(), "[TARGET]".to_string(), ));