From 29bd0a4ed842057e90ad2ef93b8127bc76f9699c Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 30 Oct 2023 18:10:17 +0100 Subject: [PATCH] Fix musl compilation (#234) musl (which we already use in ruff) allows statically linked binaries on linux. This PR switches to rustls and vendors and fixes the glibc detection. Using static musl builds makes it easier to avoid glibc errors in docker and we'll need it later for alpine users anyway. An alternative is using vendored openssl. --- Cargo.lock | 214 +++++++++++------------------ Cargo.toml | 2 +- crates/gourgeist/Cargo.toml | 2 +- crates/platform-host/Cargo.toml | 3 +- crates/platform-host/src/lib.rs | 137 +----------------- crates/platform-host/src/linux.rs | 149 ++++++++++++++++++++ crates/platform-host/src/mac_os.rs | 36 +++++ 7 files changed, 279 insertions(+), 264 deletions(-) create mode 100644 crates/platform-host/src/linux.rs create mode 100644 crates/platform-host/src/mac_os.rs diff --git a/Cargo.lock b/Cargo.lock index 2cbb83215..98cd1166c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,21 +784,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.0" @@ -1198,16 +1183,17 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ - "bytes", + "futures-util", + "http", "hyper", - "native-tls", + "rustls", "tokio", - "tokio-native-tls", + "tokio-rustls", ] [[package]] @@ -1594,24 +1580,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1668,50 +1636,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "openssl" -version = "0.10.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" -dependencies = [ - "bitflags 2.4.1", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -1851,12 +1775,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "plain" version = "0.2.3" @@ -1868,7 +1786,6 @@ name = "platform-host" version = "0.0.1" dependencies = [ "fs-err", - "glibc_version", "goblin", "platform-info", "plist", @@ -2562,22 +2479,23 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", + "hyper-rustls", "ipnet", "js-sys", "log", "mime", "mime_guess", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", - "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower-service", "url", @@ -2585,6 +2503,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", "winreg", ] @@ -2651,6 +2570,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2676,6 +2609,37 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.21.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.4", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.15" @@ -2697,15 +2661,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -2733,26 +2688,13 @@ dependencies = [ ] [[package]] -name = "security-framework" -version = "2.9.2" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", + "ring", + "untrusted", ] [[package]] @@ -2896,6 +2838,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "ssri" version = "9.2.0" @@ -3225,12 +3173,12 @@ dependencies = [ ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "native-tls", + "rustls", "tokio", ] @@ -3477,6 +3425,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -3501,12 +3455,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -3694,6 +3642,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + [[package]] name = "which" version = "4.4.2" diff --git a/Cargo.toml b/Cargo.toml index c5c010211..5a3ba3557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ pyproject-toml = { version = "0.7.0" } rayon = { version = "1.8.0" } reflink-copy = { version = "0.1.10" } regex = { version = "1.9.6" } -reqwest = { version = "0.11.22", features = ["json", "gzip", "brotli", "stream"] } +reqwest = { version = "0.11.22", default-features = false, features = ["json", "gzip", "brotli", "stream", "rustls-tls"] } reqwest-middleware = { version = "0.2.3" } reqwest-retry = { version = "0.3.0" } rfc2047-decoder = { version = "1.0.1" } diff --git a/crates/gourgeist/Cargo.toml b/crates/gourgeist/Cargo.toml index d2b5e1eb5..280b3c47e 100644 --- a/crates/gourgeist/Cargo.toml +++ b/crates/gourgeist/Cargo.toml @@ -24,7 +24,7 @@ clap = { workspace = true } configparser = { workspace = true } dirs = { workspace = true } fs-err = { workspace = true } -reqwest = { workspace = true, optional = true, features = ["blocking"] } +reqwest = { workspace = true, optional = true, default-features = false, features = ["blocking"] } rayon = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/platform-host/Cargo.toml b/crates/platform-host/Cargo.toml index affa6dc42..45a16aa53 100644 --- a/crates/platform-host/Cargo.toml +++ b/crates/platform-host/Cargo.toml @@ -11,12 +11,11 @@ license = { workspace = true } [dependencies] fs-err = { workspace = true } -glibc_version = { workspace = true } goblin = { workspace = true } platform-info = { workspace = true } plist = { workspace = true } regex = { workspace = true } -serde = { workspace = true } +serde = { workspace = true, features = ["derive"] } target-lexicon = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } diff --git a/crates/platform-host/src/lib.rs b/crates/platform-host/src/lib.rs index 57bc080f4..ebbc61741 100644 --- a/crates/platform-host/src/lib.rs +++ b/crates/platform-host/src/lib.rs @@ -1,16 +1,15 @@ //! Abstractions for understanding the current platform (operating system and architecture). -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; use std::{fmt, io}; -use fs_err as fs; -use goblin::elf::Elf; use platform_info::{PlatformInfo, PlatformInfoAPI, UNameAPI}; -use regex::Regex; -use serde::Deserialize; use thiserror::Error; -use tracing::trace; + +use crate::linux::detect_linux_libc; +use crate::mac_os::get_mac_os_version; + +mod linux; +mod mac_os; #[derive(Error, Debug)] pub enum PlatformError { @@ -70,7 +69,7 @@ impl Os { let target_triple = target_lexicon::HOST; let os = match target_triple.operating_system { - target_lexicon::OperatingSystem::Linux => Self::detect_linux_libc()?, + target_lexicon::OperatingSystem::Linux => detect_linux_libc()?, target_lexicon::OperatingSystem::Windows => Os::Windows, target_lexicon::OperatingSystem::MacOSX { major, minor, .. } => { Os::Macos { major, minor } @@ -113,55 +112,6 @@ impl Os { fn platform_info() -> Result { PlatformInfo::new().map_err(|err| PlatformError::OsVersionDetectionError(err.to_string())) } - - fn detect_linux_libc() -> Result { - let libc = find_libc()?; - let linux = if let Ok(Some((major, minor))) = get_musl_version(&libc) { - Os::Musllinux { major, minor } - } else if let Ok(glibc_ld) = fs::read_link(&libc) { - // Try reading the link first as it's faster - let filename = glibc_ld - .file_name() - .ok_or_else(|| { - PlatformError::OsVersionDetectionError( - "Expected the glibc ld to be a file".to_string(), - ) - })? - .to_string_lossy(); - let expr = Regex::new(r"ld-(\d{1,3})\.(\d{1,3})\.so").unwrap(); - - if let Some(capture) = expr.captures(&filename) { - let major = capture.get(1).unwrap().as_str().parse::().unwrap(); - let minor = capture.get(2).unwrap().as_str().parse::().unwrap(); - Os::Manylinux { major, minor } - } else { - trace!("Couldn't use ld filename, using `ldd --version`"); - // runs `ldd --version` - let version = glibc_version::get_version().map_err(|err| { - PlatformError::OsVersionDetectionError(format!( - "Failed to determine glibc version with `ldd --version`: {err}" - )) - })?; - Os::Manylinux { - major: u16::try_from(version.major).map_err(|_| { - PlatformError::OsVersionDetectionError(format!( - "Invalid glibc major version {}", - version.major - )) - })?, - minor: u16::try_from(version.minor).map_err(|_| { - PlatformError::OsVersionDetectionError(format!( - "Invalid glibc minor version {}", - version.minor - )) - })?, - } - } - } else { - return Err(PlatformError::OsVersionDetectionError("Couldn't detect neither glibc version nor musl libc version, at least one of which is required".to_string())); - }; - Ok(linux) - } } impl fmt::Display for Os { @@ -237,76 +187,3 @@ impl Arch { } } } - -/// Get the macOS version from the SystemVersion.plist file. -fn get_mac_os_version() -> Result<(u16, u16), PlatformError> { - // This is actually what python does - // https://github.com/python/cpython/blob/cb2b3c8d3566ae46b3b8d0718019e1c98484589e/Lib/platform.py#L409-L428 - #[derive(Deserialize)] - #[serde(rename_all = "PascalCase")] - struct SystemVersion { - product_version: String, - } - let system_version: SystemVersion = - plist::from_file("/System/Library/CoreServices/SystemVersion.plist") - .map_err(|err| PlatformError::OsVersionDetectionError(err.to_string()))?; - - let invalid_mac_os_version = || { - PlatformError::OsVersionDetectionError(format!( - "Invalid macOS version {}", - system_version.product_version - )) - }; - match system_version - .product_version - .split('.') - .collect::>() - .as_slice() - { - [major, minor] | [major, minor, _] => { - let major = major.parse::().map_err(|_| invalid_mac_os_version())?; - let minor = minor.parse::().map_err(|_| invalid_mac_os_version())?; - Ok((major, minor)) - } - _ => Err(invalid_mac_os_version()), - } -} - -/// Find musl libc path from executable's ELF header. -fn find_libc() -> Result { - let buffer = fs::read("/bin/ls")?; - let error_str = "Couldn't parse /bin/ls for detecting the ld version"; - let elf = Elf::parse(&buffer) - .map_err(|err| PlatformError::OsVersionDetectionError(format!("{error_str}: {err}")))?; - if let Some(elf_interpreter) = elf.interpreter { - Ok(PathBuf::from(elf_interpreter)) - } else { - Err(PlatformError::OsVersionDetectionError( - error_str.to_string(), - )) - } -} - -/// Read the musl version from libc library's output. Taken from maturin. -/// -/// The libc library should output something like this to `stderr`: -/// -/// ```text -/// musl libc (`x86_64`) -/// Version 1.2.2 -/// Dynamic Program Loader -/// ``` -fn get_musl_version(ld_path: impl AsRef) -> std::io::Result> { - let output = Command::new(ld_path.as_ref()) - .stdout(Stdio::null()) - .stderr(Stdio::piped()) - .output()?; - let stderr = String::from_utf8_lossy(&output.stderr); - let expr = Regex::new(r"Version (\d{2,4})\.(\d{2,4})").unwrap(); - if let Some(capture) = expr.captures(&stderr) { - let major = capture.get(1).unwrap().as_str().parse::().unwrap(); - let minor = capture.get(2).unwrap().as_str().parse::().unwrap(); - return Ok(Some((major, minor))); - } - Ok(None) -} diff --git a/crates/platform-host/src/linux.rs b/crates/platform-host/src/linux.rs new file mode 100644 index 000000000..397d556d2 --- /dev/null +++ b/crates/platform-host/src/linux.rs @@ -0,0 +1,149 @@ +//! Taken from `glibc_version` (), +//! which used the Apache 2.0 license (but not the MIT license) + +use crate::{Os, PlatformError}; +use fs_err as fs; +use goblin::elf::Elf; +use regex::Regex; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use tracing::trace; + +// glibc version is taken from std/sys/unix/os.rs +fn get_version() -> Result { + let output = Command::new("ldd") + .args(["--version"]) + .output() + .expect("failed to execute ldd"); + let output_str = std::str::from_utf8(&output.stdout).unwrap(); + let version_str = ldd_output_to_version_str(output_str)?; + + parse_glibc_version(version_str).ok_or_else(|| { + PlatformError::OsVersionDetectionError(format!( + "Invalid version string from ldd output: {version_str}" + )) + }) +} + +fn ldd_output_to_version_str(output_str: &str) -> Result<&str, PlatformError> { + let version_reg = Regex::new(r"ldd \(.+\) ([0-9]+\.[0-9]+)").unwrap(); + if let Some(captures) = version_reg.captures(output_str) { + Ok(captures.get(1).unwrap().as_str()) + } else { + Err(PlatformError::OsVersionDetectionError(format!( + "ERROR: failed to detect glibc version. ldd output: {output_str}", + ))) + } +} + +// Returns Some((major, minor)) if the string is a valid "x.y" version, +// ignoring any extra dot-separated parts. Otherwise return None. +fn parse_glibc_version(version: &str) -> Option { + let mut parsed_ints = version.split('.').map(str::parse).fuse(); + match (parsed_ints.next(), parsed_ints.next()) { + (Some(Ok(major)), Some(Ok(minor))) => Some(Os::Manylinux { major, minor }), + _ => None, + } +} + +pub(crate) fn detect_linux_libc() -> Result { + let libc = find_libc()?; + let linux = if let Ok(Some((major, minor))) = get_musl_version(&libc) { + Os::Musllinux { major, minor } + } else if let Ok(glibc_ld) = fs::read_link(&libc) { + // Try reading the link first as it's faster + let filename = glibc_ld + .file_name() + .ok_or_else(|| { + PlatformError::OsVersionDetectionError( + "Expected the glibc ld to be a file".to_string(), + ) + })? + .to_string_lossy(); + let expr = Regex::new(r"ld-(\d{1,3})\.(\d{1,3})\.so").unwrap(); + + if let Some(capture) = expr.captures(&filename) { + let major = capture.get(1).unwrap().as_str().parse::().unwrap(); + let minor = capture.get(2).unwrap().as_str().parse::().unwrap(); + Os::Manylinux { major, minor } + } else { + trace!("Couldn't use ld filename, using `ldd --version`"); + // runs `ldd --version` + get_version().map_err(|err| { + PlatformError::OsVersionDetectionError(format!( + "Failed to determine glibc version with `ldd --version`: {err}" + )) + })? + } + } else { + return Err(PlatformError::OsVersionDetectionError("Couldn't detect neither glibc version nor musl libc version, at least one of which is required".to_string())); + }; + Ok(linux) +} + +/// Read the musl version from libc library's output. Taken from maturin. +/// +/// The libc library should output something like this to `stderr`: +/// +/// ```text +/// musl libc (`x86_64`) +/// Version 1.2.2 +/// Dynamic Program Loader +/// ``` +fn get_musl_version(ld_path: impl AsRef) -> std::io::Result> { + let output = Command::new(ld_path.as_ref()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .output()?; + let stderr = String::from_utf8_lossy(&output.stderr); + let expr = Regex::new(r"Version (\d{2,4})\.(\d{2,4})").unwrap(); + if let Some(capture) = expr.captures(&stderr) { + let major = capture.get(1).unwrap().as_str().parse::().unwrap(); + let minor = capture.get(2).unwrap().as_str().parse::().unwrap(); + return Ok(Some((major, minor))); + } + Ok(None) +} + +/// Find musl libc path from executable's ELF header. +fn find_libc() -> Result { + let buffer = fs::read("/bin/ls")?; + let error_str = "Couldn't parse /bin/ls for detecting the ld version"; + let elf = Elf::parse(&buffer) + .map_err(|err| PlatformError::OsVersionDetectionError(format!("{error_str}: {err}")))?; + if let Some(elf_interpreter) = elf.interpreter { + Ok(PathBuf::from(elf_interpreter)) + } else { + Err(PlatformError::OsVersionDetectionError( + error_str.to_string(), + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_ldd_output() { + let ver_str = ldd_output_to_version_str( + r#"ldd (GNU libc) 2.12 +Copyright (C) 2010 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +Written by Roland McGrath and Ulrich Drepper."#, + ) + .unwrap(); + assert_eq!(ver_str, "2.12"); + + let ver_str = ldd_output_to_version_str( + r#"ldd (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31 + Copyright (C) 2020 Free Software Foundation, Inc. + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + Written by Roland McGrath and Ulrich Drepper."#, + ) + .unwrap(); + assert_eq!(ver_str, "2.31"); + } +} diff --git a/crates/platform-host/src/mac_os.rs b/crates/platform-host/src/mac_os.rs new file mode 100644 index 000000000..a8f5a21ca --- /dev/null +++ b/crates/platform-host/src/mac_os.rs @@ -0,0 +1,36 @@ +use crate::PlatformError; +use serde::Deserialize; + +/// Get the macOS version from the SystemVersion.plist file. +pub(crate) fn get_mac_os_version() -> Result<(u16, u16), PlatformError> { + // This is actually what python does + // https://github.com/python/cpython/blob/cb2b3c8d3566ae46b3b8d0718019e1c98484589e/Lib/platform.py#L409-L428 + #[derive(Deserialize)] + #[serde(rename_all = "PascalCase")] + struct SystemVersion { + product_version: String, + } + let system_version: SystemVersion = + plist::from_file("/System/Library/CoreServices/SystemVersion.plist") + .map_err(|err| PlatformError::OsVersionDetectionError(err.to_string()))?; + + let invalid_mac_os_version = || { + PlatformError::OsVersionDetectionError(format!( + "Invalid macOS version {}", + system_version.product_version + )) + }; + match system_version + .product_version + .split('.') + .collect::>() + .as_slice() + { + [major, minor] | [major, minor, _] => { + let major = major.parse::().map_err(|_| invalid_mac_os_version())?; + let minor = minor.parse::().map_err(|_| invalid_mac_os_version())?; + Ok((major, minor)) + } + _ => Err(invalid_mac_os_version()), + } +}