Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: android builds #58

Merged
merged 6 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
224 changes: 177 additions & 47 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,116 @@
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<String>,
pub abi: Option<String>,
}

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() -> String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you only use this to interpolate into another String later; I would make this return &'static str and remove the to_string, saves you an allocation that you aren't using anyway.

// 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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know much about android so it's hard for me to know if this is right or wrong, but I do know that sometimes I am frustrated by proactive checks like this, when I'm on a system that's close enough to one of these where things will still work, but don't specifically because of the check. that's only something you can really know though, and maybe you just wouldn't want to support those cases anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly not much can be done here as they only support macOS, Linux and Windows

}
.to_string()
}

/// 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just simple enough that I wonder if you need to bring in a full regex implementation here, that being said, it is easier, but if you did the parsing yourself, i bet compile times would be faster. hard to say, to be honest, but I thought I'd mention it.

// 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 = "".to_string();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a small style thing but I like String::new() here. does the exact same thing I just think it looks way cleaner.

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<String> = 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
Expand All @@ -20,52 +126,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".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

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");
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,9 @@ impl PartialEq for Url {
}

impl PartialOrd for Url {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for Url {
Expand Down