diff --git a/Cargo.lock b/Cargo.lock index 49abd89..c72f5ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "criterion", "derive_more", "link_args", + "regex", "serde", "serde_json", "url", @@ -333,9 +334,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.4" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -345,9 +346,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -356,9 +357,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc_version" diff --git a/Cargo.toml b/Cargo.toml index 7fe4473..0ccf4c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ serde_json = "1.0" [build-dependencies] cc = { version = "1.0.83", features = ["parallel"] } link_args = "0.6" +regex = { version = "1.10.2", features = [] } [package.metadata.docs.rs] features = ["serde"] diff --git a/build.rs b/build.rs index d40b206..8c3911a 100644 --- a/build.rs +++ b/build.rs @@ -1,10 +1,115 @@ -use std::env; +use regex::Regex; +use std::fmt::{Display, Formatter}; +use std::fs::File; +use std::io::Read; +use std::path::Path; +use std::{env, fmt}; + +#[derive(Clone, Debug)] +pub struct Target { + pub architecture: String, + pub vendor: String, + pub system: Option, + pub abi: Option, +} + +impl Target { + pub fn as_strs(&self) -> (&str, &str, Option<&str>, Option<&str>) { + ( + self.architecture.as_str(), + self.vendor.as_str(), + self.system.as_deref(), + self.abi.as_deref(), + ) + } +} + +impl Display for Target { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}-{}", &self.architecture, &self.vendor)?; + + if let Some(ref system) = self.system { + write!(f, "-{}", system) + } else { + Ok(()) + }?; + + if let Some(ref abi) = self.abi { + write!(f, "-{}", abi) + } else { + Ok(()) + } + } +} + +pub fn ndk() -> String { + env::var("ANDROID_NDK").expect("ANDROID_NDK variable not set") +} + +pub fn target_arch(arch: &str) -> &str { + match arch { + "armv7" => "arm", + "aarch64" => "arm64", + "i686" => "x86", + arch => arch, + } +} + +fn host_tag() -> &'static str { + // Because this is part of build.rs, the target_os is actually the host system + if cfg!(target_os = "windows") { + "windows-x86_64" + } else if cfg!(target_os = "linux") { + "linux-x86_64" + } else if cfg!(target_os = "macos") { + "darwin-x86_64" + } else { + panic!("host os is not supported") + } +} + +/// Get NDK major version from source.properties +fn ndk_major_version(ndk_dir: &Path) -> u32 { + // Capture version from the line with Pkg.Revision + let re = Regex::new(r"Pkg.Revision = (\d+)\.(\d+)\.(\d+)").unwrap(); + // There's a source.properties file in the ndk directory, which contains + let mut source_properties = + File::open(ndk_dir.join("source.properties")).expect("Couldn't open source.properties"); + let mut buf = String::new(); + source_properties + .read_to_string(&mut buf) + .expect("Could not read source.properties"); + // Capture version info + let captures = re + .captures(&buf) + .expect("source.properties did not match the regex"); + // Capture 0 is the whole line of text + captures[1].parse().expect("could not parse major version") +} -// Taken from https://github.com/Brooooooklyn/ada-url/blob/main/ada/build.rs fn main() { - println!("cargo:rerun-if-changed=deps/ada.cpp"); - println!("cargo:rerun-if-changed=deps/ada.h"); - println!("cargo:rerun-if-changed=deps/ada_c.h"); + let target_str = env::var("TARGET").unwrap(); + let target: Vec = target_str.split('-').map(|s| s.into()).collect(); + assert!(target.len() >= 2, "Failed to parse TARGET {}", target_str); + + let abi = if target.len() > 3 { + Some(target[3].clone()) + } else { + None + }; + + let system = if target.len() > 2 { + Some(target[2].clone()) + } else { + None + }; + + let target = Target { + architecture: target[0].clone(), + vendor: target[1].clone(), + system, + abi, + }; let mut build = cc::Build::new(); build @@ -20,52 +125,76 @@ fn main() { let compile_target_feature = env::var("CARGO_CFG_TARGET_FEATURE"); // Except for Emscripten target (which emulates POSIX environment), compile to Wasm via WASI SDK // which is currently the only standalone provider of stdlib for compilation of C/C++ libraries. - if compile_target_arch.starts_with("wasm") && compile_target_os != "emscripten" { - let wasi_sdk = env::var("WASI_SDK").unwrap_or_else(|_| "/opt/wasi-sdk".to_owned()); - assert!( - std::path::Path::new(&wasi_sdk).exists(), - "WASI SDK not found at {wasi_sdk}" - ); - build.compiler(format!("{wasi_sdk}/bin/clang++")); - let wasi_sysroot_lib = match compile_target_feature { - Ok(compile_target_feature) if compile_target_feature.contains("atomics") => { - "wasm32-wasi-threads" + + match target.system.as_deref() { + Some("android" | "androideabi") => { + let ndk = ndk(); + let major = ndk_major_version(Path::new(&ndk)); + if major < 22 { + build + .flag(&format!("--sysroot={}/sysroot", ndk)) + .flag(&format!( + "-isystem{}/sources/cxx-stl/llvm-libc++/include", + ndk + )); + } else { + // NDK versions >= 22 have the sysroot in the llvm prebuilt by + let host_toolchain = format!("{}/toolchains/llvm/prebuilt/{}", ndk, host_tag()); + // sysroot is stored in the prebuilt llvm, under the host + build.flag(&format!("--sysroot={}/sysroot", host_toolchain)); } - _ => "wasm32-wasi", - }; - println!("cargo:rustc-link-search={wasi_sdk}/share/wasi-sysroot/lib/{wasi_sysroot_lib}"); - // Wasm exceptions are new and not yet supported by WASI SDK. - build.flag("-fno-exceptions"); - // WASI SDK only has libc++ available. - build.cpp_set_stdlib("c++"); - // Explicitly link C++ ABI to avoid linking errors (it takes care of C++ -> C "lowering"). - println!("cargo:rustc-link-lib=c++abi"); - // Because Ada is a pure parsing library that doesn't need any OS syscalls, - // it's also possible to compile it to wasm32-unknown-unknown. - // This still requires WASI SDK for libc & libc++, but then we need a few hacks / overrides to get a pure Wasm w/o imports instead. - if compile_target_os == "unknown" { - build.target("wasm32-wasi"); - println!("cargo:rustc-link-lib=c"); - build.file("./deps/wasi_to_unknown.cpp"); } - } else if !(compile_target_os == "windows" && compile_target_env == "msvc") { - build.compiler("clang++"); - } - - let compiler = build.get_compiler(); - // Note: it's possible to use Clang++ explicitly on Windows as well, so this check - // should be specifically for "is target compiler MSVC" and not "is target OS Windows". - if compiler.is_like_msvc() { - build.static_crt(true); - link_args::windows! { - unsafe { - no_default_lib( - "libcmt.lib", + _ => { + if compile_target_arch.starts_with("wasm") && compile_target_os != "emscripten" { + let wasi_sdk = env::var("WASI_SDK").unwrap_or_else(|_| "/opt/wasi-sdk".to_owned()); + assert!( + Path::new(&wasi_sdk).exists(), + "WASI SDK not found at {wasi_sdk}" + ); + build.compiler(format!("{wasi_sdk}/bin/clang++")); + let wasi_sysroot_lib = match compile_target_feature { + Ok(compile_target_feature) if compile_target_feature.contains("atomics") => { + "wasm32-wasi-threads" + } + _ => "wasm32-wasi", + }; + println!( + "cargo:rustc-link-search={wasi_sdk}/share/wasi-sysroot/lib/{wasi_sysroot_lib}" ); + // Wasm exceptions are new and not yet supported by WASI SDK. + build.flag("-fno-exceptions"); + // WASI SDK only has libc++ available. + build.cpp_set_stdlib("c++"); + // Explicitly link C++ ABI to avoid linking errors (it takes care of C++ -> C "lowering"). + println!("cargo:rustc-link-lib=c++abi"); + // Because Ada is a pure parsing library that doesn't need any OS syscalls, + // it's also possible to compile it to wasm32-unknown-unknown. + // This still requires WASI SDK for libc & libc++, but then we need a few hacks / overrides to get a pure Wasm w/o imports instead. + if compile_target_os == "unknown" { + build.target("wasm32-wasi"); + println!("cargo:rustc-link-lib=c"); + build.file("./deps/wasi_to_unknown.cpp"); + } + } else if !(compile_target_os == "windows" && compile_target_env == "msvc") { + build.compiler("clang++"); } - }; - } else if compiler.is_like_clang() && cfg!(feature = "libcpp") { - build.cpp_set_stdlib("c++"); + + let compiler = build.get_compiler(); + // Note: it's possible to use Clang++ explicitly on Windows as well, so this check + // should be specifically for "is target compiler MSVC" and not "is target OS Windows". + if compiler.is_like_msvc() { + build.static_crt(true); + link_args::windows! { + unsafe { + no_default_lib( + "libcmt.lib", + ); + } + } + } else if compiler.is_like_clang() && cfg!(feature = "libcpp") { + build.cpp_set_stdlib("c++"); + } + } } build.compile("ada"); diff --git a/src/lib.rs b/src/lib.rs index 3b82737..92ead82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -729,7 +729,9 @@ impl PartialEq for Url { } impl PartialOrd for Url { - fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } } impl Ord for Url {