Skip to content

Commit 3d49fa0

Browse files
authored
fix: android builds (#58)
1 parent a657645 commit 3d49fa0

File tree

4 files changed

+187
-54
lines changed

4 files changed

+187
-54
lines changed

Cargo.lock

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ serde_json = "1.0"
4444
[build-dependencies]
4545
cc = { version = "1.0.83", features = ["parallel"] }
4646
link_args = "0.6"
47+
regex = { version = "1.10.2", features = [] }
4748

4849
[package.metadata.docs.rs]
4950
features = ["serde"]

build.rs

Lines changed: 176 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,115 @@
1-
use std::env;
1+
use regex::Regex;
2+
use std::fmt::{Display, Formatter};
3+
use std::fs::File;
4+
use std::io::Read;
5+
use std::path::Path;
6+
use std::{env, fmt};
7+
8+
#[derive(Clone, Debug)]
9+
pub struct Target {
10+
pub architecture: String,
11+
pub vendor: String,
12+
pub system: Option<String>,
13+
pub abi: Option<String>,
14+
}
15+
16+
impl Target {
17+
pub fn as_strs(&self) -> (&str, &str, Option<&str>, Option<&str>) {
18+
(
19+
self.architecture.as_str(),
20+
self.vendor.as_str(),
21+
self.system.as_deref(),
22+
self.abi.as_deref(),
23+
)
24+
}
25+
}
26+
27+
impl Display for Target {
28+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
29+
write!(f, "{}-{}", &self.architecture, &self.vendor)?;
30+
31+
if let Some(ref system) = self.system {
32+
write!(f, "-{}", system)
33+
} else {
34+
Ok(())
35+
}?;
36+
37+
if let Some(ref abi) = self.abi {
38+
write!(f, "-{}", abi)
39+
} else {
40+
Ok(())
41+
}
42+
}
43+
}
44+
45+
pub fn ndk() -> String {
46+
env::var("ANDROID_NDK").expect("ANDROID_NDK variable not set")
47+
}
48+
49+
pub fn target_arch(arch: &str) -> &str {
50+
match arch {
51+
"armv7" => "arm",
52+
"aarch64" => "arm64",
53+
"i686" => "x86",
54+
arch => arch,
55+
}
56+
}
57+
58+
fn host_tag() -> &'static str {
59+
// Because this is part of build.rs, the target_os is actually the host system
60+
if cfg!(target_os = "windows") {
61+
"windows-x86_64"
62+
} else if cfg!(target_os = "linux") {
63+
"linux-x86_64"
64+
} else if cfg!(target_os = "macos") {
65+
"darwin-x86_64"
66+
} else {
67+
panic!("host os is not supported")
68+
}
69+
}
70+
71+
/// Get NDK major version from source.properties
72+
fn ndk_major_version(ndk_dir: &Path) -> u32 {
73+
// Capture version from the line with Pkg.Revision
74+
let re = Regex::new(r"Pkg.Revision = (\d+)\.(\d+)\.(\d+)").unwrap();
75+
// There's a source.properties file in the ndk directory, which contains
76+
let mut source_properties =
77+
File::open(ndk_dir.join("source.properties")).expect("Couldn't open source.properties");
78+
let mut buf = String::new();
79+
source_properties
80+
.read_to_string(&mut buf)
81+
.expect("Could not read source.properties");
82+
// Capture version info
83+
let captures = re
84+
.captures(&buf)
85+
.expect("source.properties did not match the regex");
86+
// Capture 0 is the whole line of text
87+
captures[1].parse().expect("could not parse major version")
88+
}
289

3-
// Taken from https://github.com/Brooooooklyn/ada-url/blob/main/ada/build.rs
490
fn main() {
5-
println!("cargo:rerun-if-changed=deps/ada.cpp");
6-
println!("cargo:rerun-if-changed=deps/ada.h");
7-
println!("cargo:rerun-if-changed=deps/ada_c.h");
91+
let target_str = env::var("TARGET").unwrap();
92+
let target: Vec<String> = target_str.split('-').map(|s| s.into()).collect();
93+
assert!(target.len() >= 2, "Failed to parse TARGET {}", target_str);
94+
95+
let abi = if target.len() > 3 {
96+
Some(target[3].clone())
97+
} else {
98+
None
99+
};
100+
101+
let system = if target.len() > 2 {
102+
Some(target[2].clone())
103+
} else {
104+
None
105+
};
106+
107+
let target = Target {
108+
architecture: target[0].clone(),
109+
vendor: target[1].clone(),
110+
system,
111+
abi,
112+
};
8113

9114
let mut build = cc::Build::new();
10115
build
@@ -20,52 +125,76 @@ fn main() {
20125
let compile_target_feature = env::var("CARGO_CFG_TARGET_FEATURE");
21126
// Except for Emscripten target (which emulates POSIX environment), compile to Wasm via WASI SDK
22127
// which is currently the only standalone provider of stdlib for compilation of C/C++ libraries.
23-
if compile_target_arch.starts_with("wasm") && compile_target_os != "emscripten" {
24-
let wasi_sdk = env::var("WASI_SDK").unwrap_or_else(|_| "/opt/wasi-sdk".to_owned());
25-
assert!(
26-
std::path::Path::new(&wasi_sdk).exists(),
27-
"WASI SDK not found at {wasi_sdk}"
28-
);
29-
build.compiler(format!("{wasi_sdk}/bin/clang++"));
30-
let wasi_sysroot_lib = match compile_target_feature {
31-
Ok(compile_target_feature) if compile_target_feature.contains("atomics") => {
32-
"wasm32-wasi-threads"
128+
129+
match target.system.as_deref() {
130+
Some("android" | "androideabi") => {
131+
let ndk = ndk();
132+
let major = ndk_major_version(Path::new(&ndk));
133+
if major < 22 {
134+
build
135+
.flag(&format!("--sysroot={}/sysroot", ndk))
136+
.flag(&format!(
137+
"-isystem{}/sources/cxx-stl/llvm-libc++/include",
138+
ndk
139+
));
140+
} else {
141+
// NDK versions >= 22 have the sysroot in the llvm prebuilt by
142+
let host_toolchain = format!("{}/toolchains/llvm/prebuilt/{}", ndk, host_tag());
143+
// sysroot is stored in the prebuilt llvm, under the host
144+
build.flag(&format!("--sysroot={}/sysroot", host_toolchain));
33145
}
34-
_ => "wasm32-wasi",
35-
};
36-
println!("cargo:rustc-link-search={wasi_sdk}/share/wasi-sysroot/lib/{wasi_sysroot_lib}");
37-
// Wasm exceptions are new and not yet supported by WASI SDK.
38-
build.flag("-fno-exceptions");
39-
// WASI SDK only has libc++ available.
40-
build.cpp_set_stdlib("c++");
41-
// Explicitly link C++ ABI to avoid linking errors (it takes care of C++ -> C "lowering").
42-
println!("cargo:rustc-link-lib=c++abi");
43-
// Because Ada is a pure parsing library that doesn't need any OS syscalls,
44-
// it's also possible to compile it to wasm32-unknown-unknown.
45-
// 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.
46-
if compile_target_os == "unknown" {
47-
build.target("wasm32-wasi");
48-
println!("cargo:rustc-link-lib=c");
49-
build.file("./deps/wasi_to_unknown.cpp");
50146
}
51-
} else if !(compile_target_os == "windows" && compile_target_env == "msvc") {
52-
build.compiler("clang++");
53-
}
54-
55-
let compiler = build.get_compiler();
56-
// Note: it's possible to use Clang++ explicitly on Windows as well, so this check
57-
// should be specifically for "is target compiler MSVC" and not "is target OS Windows".
58-
if compiler.is_like_msvc() {
59-
build.static_crt(true);
60-
link_args::windows! {
61-
unsafe {
62-
no_default_lib(
63-
"libcmt.lib",
147+
_ => {
148+
if compile_target_arch.starts_with("wasm") && compile_target_os != "emscripten" {
149+
let wasi_sdk = env::var("WASI_SDK").unwrap_or_else(|_| "/opt/wasi-sdk".to_owned());
150+
assert!(
151+
Path::new(&wasi_sdk).exists(),
152+
"WASI SDK not found at {wasi_sdk}"
153+
);
154+
build.compiler(format!("{wasi_sdk}/bin/clang++"));
155+
let wasi_sysroot_lib = match compile_target_feature {
156+
Ok(compile_target_feature) if compile_target_feature.contains("atomics") => {
157+
"wasm32-wasi-threads"
158+
}
159+
_ => "wasm32-wasi",
160+
};
161+
println!(
162+
"cargo:rustc-link-search={wasi_sdk}/share/wasi-sysroot/lib/{wasi_sysroot_lib}"
64163
);
164+
// Wasm exceptions are new and not yet supported by WASI SDK.
165+
build.flag("-fno-exceptions");
166+
// WASI SDK only has libc++ available.
167+
build.cpp_set_stdlib("c++");
168+
// Explicitly link C++ ABI to avoid linking errors (it takes care of C++ -> C "lowering").
169+
println!("cargo:rustc-link-lib=c++abi");
170+
// Because Ada is a pure parsing library that doesn't need any OS syscalls,
171+
// it's also possible to compile it to wasm32-unknown-unknown.
172+
// 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.
173+
if compile_target_os == "unknown" {
174+
build.target("wasm32-wasi");
175+
println!("cargo:rustc-link-lib=c");
176+
build.file("./deps/wasi_to_unknown.cpp");
177+
}
178+
} else if !(compile_target_os == "windows" && compile_target_env == "msvc") {
179+
build.compiler("clang++");
65180
}
66-
};
67-
} else if compiler.is_like_clang() && cfg!(feature = "libcpp") {
68-
build.cpp_set_stdlib("c++");
181+
182+
let compiler = build.get_compiler();
183+
// Note: it's possible to use Clang++ explicitly on Windows as well, so this check
184+
// should be specifically for "is target compiler MSVC" and not "is target OS Windows".
185+
if compiler.is_like_msvc() {
186+
build.static_crt(true);
187+
link_args::windows! {
188+
unsafe {
189+
no_default_lib(
190+
"libcmt.lib",
191+
);
192+
}
193+
}
194+
} else if compiler.is_like_clang() && cfg!(feature = "libcpp") {
195+
build.cpp_set_stdlib("c++");
196+
}
197+
}
69198
}
70199

71200
build.compile("ada");

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,9 @@ impl PartialEq for Url {
729729
}
730730

731731
impl PartialOrd for Url {
732-
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
732+
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
733+
Some(self.cmp(other))
734+
}
733735
}
734736

735737
impl Ord for Url {

0 commit comments

Comments
 (0)