diff --git a/e2e/cli/test_lock b/e2e/cli/test_lock index 7ff524d2bb..bc3d86ab02 100644 --- a/e2e/cli/test_lock +++ b/e2e/cli/test_lock @@ -16,9 +16,9 @@ EOF rm -f mise.lock -# Test that lock command generates lockfile for all 5 default platforms +# Test that lock command generates lockfile for all 7 default platforms (including musl variants) output=$(mise lock 2>&1) -assert_contains "echo '$output'" "Targeting 5 platform(s)" +assert_contains "echo '$output'" "Targeting 7 platform(s)" assert_contains "echo '$output'" "Processing 1 tool(s)" assert_contains "echo '$output'" "Updated" assert_contains "echo '$output'" "Lockfile written to" diff --git a/registry/snyk.toml b/registry/snyk.toml index 1df1806c97..7f623fe5cb 100644 --- a/registry/snyk.toml +++ b/registry/snyk.toml @@ -1,2 +1,2 @@ -backends = ["aqua:snyk/cli", "asdf:nirfuchs/asdf-snyk"] +backends = ["aqua:snyk/cli", "github:snyk/cli", "asdf:nirfuchs/asdf-snyk"] description = "Snyk CLI scans and monitors your projects for security vulnerabilities" diff --git a/src/backend/asset_matcher.rs b/src/backend/asset_matcher.rs index 38f72c70dd..abf7888a36 100644 --- a/src/backend/asset_matcher.rs +++ b/src/backend/asset_matcher.rs @@ -176,13 +176,15 @@ pub struct AssetPicker { } impl AssetPicker { - /// Create an AssetPicker with an explicit libc setting + /// Create an AssetPicker with an explicit libc setting. + /// When no explicit libc is provided, defaults to the platform's standard libc + /// (msvc for Windows, gnu for Linux/other). The caller is responsible for passing + /// the correct libc qualifier from PlatformTarget — this avoids polluting + /// cross-platform lockfile entries with the current system's libc. pub fn with_libc(target_os: String, target_arch: String, libc: Option) -> Self { let target_libc = libc.unwrap_or_else(|| { if target_os == "windows" { "msvc".to_string() - } else if cfg!(target_env = "musl") { - "musl".to_string() } else { "gnu".to_string() } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 378ae8d9fc..9f868b67d8 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -373,12 +373,10 @@ pub trait Backend: Debug + Send + Sync { fn ba(&self) -> &Arc; /// Generates a platform key for lockfile storage. - /// Default implementation uses os-arch format, but backends can override for more specific keys. + /// Default implementation uses the current platform key (os-arch or os-arch-qualifier), + /// which includes the libc qualifier on musl systems. fn get_platform_key(&self) -> String { - let settings = Settings::get(); - let os = settings.os(); - let arch = settings.arch(); - format!("{os}-{arch}") + Platform::current().to_key() } /// Resolves the lockfile options for a tool request on a target platform. diff --git a/src/platform.rs b/src/platform.rs index 5f089709ee..d2d447c139 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -38,13 +38,20 @@ impl Platform { } } - /// Get the current platform from system information + /// Get the current platform from system information. + /// On Linux, detects musl vs glibc at runtime and sets the qualifier accordingly. pub fn current() -> Self { let settings = Settings::get(); + let os = settings.os().to_string(); + let qualifier = if os == "linux" && is_musl_system() { + Some("musl".to_string()) + } else { + None + }; Platform { - os: settings.os().to_string(), + os, arch: settings.arch().to_string(), - qualifier: None, + qualifier, } } @@ -117,7 +124,9 @@ impl Platform { pub fn common_platforms() -> Vec { vec![ Platform::parse("linux-x64").unwrap(), + Platform::parse("linux-x64-musl").unwrap(), Platform::parse("linux-arm64").unwrap(), + Platform::parse("linux-arm64-musl").unwrap(), Platform::parse("macos-x64").unwrap(), Platform::parse("macos-arm64").unwrap(), Platform::parse("windows-x64").unwrap(), @@ -174,6 +183,43 @@ impl From<&str> for Platform { } } +/// Detect whether the system uses musl libc at runtime. +/// Checks for the absence of glibc's dynamic linker (`ld-linux-*`) in /lib and /lib64. +/// On glibc systems, `ld-linux-*` is always present (even if musl-tools is installed +/// for cross-compilation, which also places `ld-musl-*` in /lib). On musl-only systems +/// (Alpine, Void musl, etc.), only `ld-musl-*` exists without `ld-linux-*`. +#[cfg(target_os = "linux")] +fn is_musl_system() -> bool { + use std::sync::LazyLock; + static IS_MUSL: LazyLock = LazyLock::new(|| { + // If glibc's dynamic linker exists, this is a glibc system + for dir in ["/lib", "/lib64"] { + if has_file_prefix(dir, "ld-linux-") { + return false; + } + } + // No glibc linker found — check for musl's + has_file_prefix("/lib", "ld-musl-") + }); + *IS_MUSL +} + +#[cfg(target_os = "linux")] +fn has_file_prefix(dir: &str, prefix: &str) -> bool { + std::fs::read_dir(dir) + .map(|entries| { + entries + .flatten() + .any(|e| e.file_name().to_string_lossy().starts_with(prefix)) + }) + .unwrap_or(false) +} + +#[cfg(not(target_os = "linux"))] +fn is_musl_system() -> bool { + false +} + #[cfg(test)] mod tests { use super::*; @@ -283,11 +329,13 @@ mod tests { #[test] fn test_common_platforms() { let platforms = Platform::common_platforms(); - assert_eq!(platforms.len(), 5); + assert_eq!(platforms.len(), 7); let keys: Vec = platforms.iter().map(|p| p.to_key()).collect(); assert!(keys.contains(&"linux-x64".to_string())); + assert!(keys.contains(&"linux-x64-musl".to_string())); assert!(keys.contains(&"linux-arm64".to_string())); + assert!(keys.contains(&"linux-arm64-musl".to_string())); assert!(keys.contains(&"macos-x64".to_string())); assert!(keys.contains(&"macos-arm64".to_string())); assert!(keys.contains(&"windows-x64".to_string()));