Skip to content

Commit 55272e4

Browse files
committed
feat: android support
1 parent 384e6d5 commit 55272e4

2 files changed

Lines changed: 129 additions & 12 deletions

File tree

flake.nix

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
]);
7676
};
7777

78-
# -- Stable Rust for checks (MSRV is too old for some vendored crate manifests) --
7978
rustStable = fenix.packages.${system}.stable;
8079
mkAndroidCheck = { name, rustTarget }:
8180
let
@@ -98,6 +97,10 @@
9897

9998
cargoLock.lockFile = self + "/Cargo-recent.lock";
10099

100+
postUnpack = ''
101+
cp ${./Cargo-recent.lock} $sourceRoot/Cargo.lock
102+
'';
103+
101104
nativeBuildInputs = [
102105
pkgs.cmake
103106
pkgs.pkg-config

libbitcoinkernel-sys/build.rs

Lines changed: 125 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,35 @@ use std::path::Path;
44
use std::path::PathBuf;
55
use std::process::Command;
66

7+
/// Maps Rust target triples to Android ABI names used by the NDK.
8+
fn android_abi(target: &str) -> Option<&'static str> {
9+
match target {
10+
t if t.contains("aarch64") => Some("arm64-v8a"),
11+
t if t.contains("armv7") => Some("armeabi-v7a"),
12+
t if t.contains("x86_64") => Some("x86_64"),
13+
t if t.contains("i686") => Some("x86"),
14+
_ => None,
15+
}
16+
}
17+
18+
/// Maps Rust target triples to the NDK sysroot directory names.
19+
/// These differ from Rust triples for armv7 targets.
20+
fn android_sysroot_triple(target: &str) -> &str {
21+
if target.starts_with("armv7") {
22+
"arm-linux-androideabi"
23+
} else {
24+
target
25+
}
26+
}
27+
28+
/// Detect whether we are cross-compiling for Android and return the NDK home path if so.
29+
fn android_ndk_home() -> Option<String> {
30+
env::var("ANDROID_NDK_HOME")
31+
.or_else(|_| env::var("ANDROID_NDK_ROOT"))
32+
.or_else(|_| env::var("NDK_HOME"))
33+
.ok()
34+
}
35+
736
fn main() {
837
let bitcoin_dir = Path::new("bitcoin");
938
let out_dir = env::var("OUT_DIR").unwrap();
@@ -17,7 +46,12 @@ fn main() {
1746

1847
let build_config = "RelWithDebInfo";
1948

20-
Command::new("cmake")
49+
let target = env::var("TARGET").unwrap();
50+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
51+
let is_android = target_os == "android";
52+
53+
let mut cmake_configure = Command::new("cmake");
54+
cmake_configure
2155
.arg("-B")
2256
.arg(&build_dir)
2357
.arg("-S")
@@ -40,9 +74,33 @@ fn main() {
4074
.arg("-DBUILD_SHARED_LIBS=OFF")
4175
.arg("-DCMAKE_INSTALL_LIBDIR=lib")
4276
.arg("-DENABLE_IPC=OFF")
43-
.arg(format!("-DCMAKE_INSTALL_PREFIX={}", install_dir.display()))
44-
.status()
45-
.unwrap();
77+
.arg(format!("-DCMAKE_INSTALL_PREFIX={}", install_dir.display()));
78+
79+
if is_android {
80+
let ndk =
81+
android_ndk_home().expect("Android target detected but ANDROID_NDK_HOME is not set");
82+
let toolchain_file = format!("{ndk}/build/cmake/android.toolchain.cmake");
83+
let abi =
84+
android_abi(&target).unwrap_or_else(|| panic!("unsupported Android target: {target}"));
85+
// API level 24+ is required because Bitcoin Core uses getifaddrs
86+
// which was introduced in Android API 24 (Nougat).
87+
let api_level = env::var("ANDROID_API_LEVEL").unwrap_or_else(|_| "24".to_string());
88+
89+
cmake_configure
90+
.arg(format!("-DCMAKE_TOOLCHAIN_FILE={toolchain_file}"))
91+
.arg(format!("-DANDROID_ABI={abi}"))
92+
.arg(format!("-DANDROID_PLATFORM=android-{api_level}"))
93+
.arg("-DCMAKE_SYSTEM_NAME=Android")
94+
.arg(format!("-DCMAKE_ANDROID_ARCH_ABI={abi}"))
95+
.arg(format!("-DCMAKE_SYSTEM_VERSION={api_level}"))
96+
.arg(format!("-DCMAKE_ANDROID_NDK={ndk}"))
97+
// The Android NDK toolchain sets CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
98+
// to ONLY, which prevents cmake from finding host packages via
99+
// CMAKE_PREFIX_PATH. Override it so Boost headers can be located.
100+
.arg("-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH");
101+
}
102+
103+
cmake_configure.status().unwrap();
46104

47105
let num_jobs = env::var("NUM_JOBS")
48106
.ok()
@@ -80,10 +138,58 @@ fn main() {
80138
let include_path = install_dir.join("include");
81139
let header = include_path.join("bitcoinkernel.h");
82140

83-
#[allow(deprecated)]
84-
let bindings = bindgen::Builder::default()
141+
let mut builder = bindgen::Builder::default()
85142
.header(header.to_str().unwrap())
86-
.clang_arg("-DBITCOINKERNEL_STATIC")
143+
.clang_arg("-DBITCOINKERNEL_STATIC");
144+
145+
if is_android {
146+
builder = builder.clang_arg(format!("--target={target}"));
147+
if let Some(ndk) = android_ndk_home() {
148+
let api_level = env::var("ANDROID_API_LEVEL").unwrap_or_else(|_| "24".to_string());
149+
let sysroot = format!("{ndk}/toolchains/llvm/prebuilt/linux-x86_64/sysroot");
150+
let ndk_triple = android_sysroot_triple(&target);
151+
152+
// Suppress host system headers so only NDK headers are used.
153+
builder = builder.clang_arg("-nostdinc");
154+
155+
// Add clang builtin headers (stddef.h, stdarg.h, etc.) from the
156+
// resource directory. Must come before NDK sysroot includes.
157+
if let Ok(libclang) = env::var("LIBCLANG_PATH") {
158+
let libclang_path = Path::new(&libclang);
159+
let candidates = [
160+
libclang_path.to_path_buf(),
161+
libclang_path
162+
.parent()
163+
.map(|p| p.to_path_buf())
164+
.unwrap_or_default(),
165+
];
166+
for base in &candidates {
167+
let clang_dir = base.join("clang");
168+
if let Ok(entries) = std::fs::read_dir(&clang_dir) {
169+
for entry in entries.flatten() {
170+
let resource_dir = entry.path();
171+
if resource_dir.join("include").join("stddef.h").exists() {
172+
builder = builder.clang_arg(format!(
173+
"-isystem{}",
174+
resource_dir.join("include").display()
175+
));
176+
break;
177+
}
178+
}
179+
}
180+
}
181+
}
182+
183+
builder = builder
184+
.clang_arg(format!("--sysroot={sysroot}"))
185+
.clang_arg(format!("-isystem{sysroot}/usr/include"))
186+
.clang_arg(format!("-isystem{sysroot}/usr/include/{ndk_triple}"))
187+
.clang_arg(format!("-D__ANDROID_API__={api_level}"));
188+
}
189+
}
190+
191+
#[allow(deprecated)]
192+
let bindings = builder
87193
.rust_target(bindgen::RustTarget::Stable_1_71)
88194
.rust_edition(RustEdition::Edition2021)
89195
.generate()
@@ -97,14 +203,22 @@ fn main() {
97203
.expect("Couldn't write bindings!");
98204

99205
let compiler = cc::Build::new().get_compiler();
100-
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
101206

102207
if target_os == "windows" {
103208
println!("cargo:rustc-link-lib=bcrypt");
104209
println!("cargo:rustc-link-lib=shell32");
105-
}
106-
107-
if compiler.is_like_clang() {
210+
} else if is_android {
211+
// Android NDK ships libc++_static.a and libc++abi.a in the
212+
// per-architecture sysroot directory (not the API-level subdirectory).
213+
if let Some(ndk) = android_ndk_home() {
214+
let ndk_triple = android_sysroot_triple(&target);
215+
let ndk_lib_dir =
216+
format!("{ndk}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/{ndk_triple}");
217+
println!("cargo:rustc-link-search=native={ndk_lib_dir}");
218+
}
219+
println!("cargo:rustc-link-lib=static=c++_static");
220+
println!("cargo:rustc-link-lib=static=c++abi");
221+
} else if compiler.is_like_clang() {
108222
if target_os == "macos" {
109223
println!("cargo:rustc-link-lib=dylib=c++");
110224
} else {

0 commit comments

Comments
 (0)