-
Notifications
You must be signed in to change notification settings - Fork 12
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
Changes from 5 commits
bc35ae9
2a06aff
5b32686
1d1582e
321bded
346a538
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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 { | ||
// 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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a small style thing but I like |
||
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 | ||
|
@@ -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". | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"); | ||
|
There was a problem hiding this comment.
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 theto_string
, saves you an allocation that you aren't using anyway.