Skip to content

Commit 6e4d238

Browse files
committed
feat: minimal android support
This commit presents the minimal work needed for this crate to be able to be compiled for android, this is minimal to avoid bloating too much the build process, which would make it hard to review and maintain. The rest of the work will be done on nix side, which provides better ergonomics for such job.
1 parent 1b52c2f commit 6e4d238

2 files changed

Lines changed: 121 additions & 10 deletions

File tree

libbitcoinkernel-sys/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- Added build support for android
1112
- New `btck_ConsensusParams` opaque type for holding consensus parameters
1213
- New `btck_chain_parameters_get_consensus_params` for extracting consensus params from `btck_ChainParameters` (lifetime-bound to the chain parameters object)
1314
- New `btck_block_check` for context-free block validation (size limits, coinbase structure, sigop limits, with optional POW and merkle-root checks via `btck_BlockCheckFlags`)

libbitcoinkernel-sys/build.rs

Lines changed: 120 additions & 10 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+
/// Rust target triple -> NDK ABI name (`arm64-v8a`, `armeabi-v7a`, …).
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+
/// Rust target triple -> NDK sysroot lib directory triple.
19+
/// armv7 differs: Rust says `armv7-linux-androideabi`, NDK says `arm-linux-androideabi`.
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+
/// NDK root from `ANDROID_NDK_HOME`, `ANDROID_NDK_ROOT`, or `NDK_HOME`.
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,8 @@ fn main() {
1746

1847
let build_config = "RelWithDebInfo";
1948

20-
Command::new("cmake")
49+
let mut cmake_configure = Command::new("cmake");
50+
cmake_configure
2151
.arg("-B")
2252
.arg(&build_dir)
2353
.arg("-S")
@@ -40,7 +70,42 @@ fn main() {
4070
.arg("-DBUILD_SHARED_LIBS=OFF")
4171
.arg("-DCMAKE_INSTALL_LIBDIR=lib")
4272
.arg("-DENABLE_IPC=OFF")
43-
.arg(format!("-DCMAKE_INSTALL_PREFIX={}", install_dir.display()))
73+
.arg(format!("-DCMAKE_INSTALL_PREFIX={}", install_dir.display()));
74+
75+
let target = env::var("TARGET").unwrap();
76+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
77+
78+
if target_os == "android" {
79+
let ndk =
80+
android_ndk_home().expect("Android target detected but ANDROID_NDK_HOME is not set");
81+
let toolchain_file = format!("{ndk}/build/cmake/android.toolchain.cmake");
82+
assert!(
83+
Path::new(&toolchain_file).exists(),
84+
"Android NDK toolchain file not found at {toolchain_file}. \
85+
Check that ANDROID_NDK_HOME points to a valid NDK installation"
86+
);
87+
let abi =
88+
android_abi(&target).unwrap_or_else(|| panic!("unsupported Android target: {target}"));
89+
90+
// API level 24+ is required because Bitcoin Core uses getifaddrs
91+
// which was introduced in Android API 24 (Nougat).
92+
let api_level = env::var("ANDROID_API_LEVEL").unwrap_or_else(|_| "24".to_string());
93+
94+
cmake_configure
95+
.arg(format!("-DCMAKE_TOOLCHAIN_FILE={toolchain_file}"))
96+
.arg(format!("-DANDROID_ABI={abi}"))
97+
.arg(format!("-DANDROID_PLATFORM=android-{api_level}"))
98+
.arg("-DCMAKE_SYSTEM_NAME=Android")
99+
.arg(format!("-DCMAKE_ANDROID_ARCH_ABI={abi}"))
100+
.arg(format!("-DCMAKE_SYSTEM_VERSION={api_level}"))
101+
.arg(format!("-DCMAKE_ANDROID_NDK={ndk}"))
102+
// The Android NDK toolchain sets CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
103+
// to ONLY, which prevents cmake from finding host packages via
104+
// CMAKE_PREFIX_PATH. Override it so Boost headers can be located.
105+
.arg("-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH");
106+
}
107+
108+
cmake_configure
44109
.status()
45110
.expect("cmake should be installed and available in PATH");
46111

@@ -81,13 +146,45 @@ fn main() {
81146
let header = include_path.join("bitcoinkernel.h");
82147

83148
#[allow(deprecated)]
84-
let bindings = bindgen::Builder::default()
149+
let mut builder = bindgen::Builder::default()
85150
.header(header.to_str().unwrap())
86151
.clang_arg("-DBITCOINKERNEL_STATIC")
87152
.rust_target(bindgen::RustTarget::Stable_1_71)
88-
.rust_edition(RustEdition::Edition2021)
89-
.generate()
90-
.expect("Unable to generate bindings");
153+
.rust_edition(RustEdition::Edition2021);
154+
155+
// When cross-compiling for Android, bindgen's host libclang does not
156+
// know the NDK sysroot or target triple. Pass them explicitly so
157+
// system headers like stddef.h are found.
158+
if target_os == "android" {
159+
if let Some(ndk) = android_ndk_home() {
160+
let host_tag = if cfg!(target_os = "macos") {
161+
"darwin-x86_64"
162+
} else {
163+
"linux-x86_64"
164+
};
165+
let prebuilt = format!("{ndk}/toolchains/llvm/prebuilt/{host_tag}");
166+
let sysroot = format!("{prebuilt}/sysroot");
167+
builder = builder
168+
.clang_arg(format!("--target={target}"))
169+
.clang_arg(format!("--sysroot={sysroot}"));
170+
171+
// The NDK's clang resource directory contains compiler builtins
172+
// (stddef.h, stdarg.h, etc.) that the host libclang may lack for
173+
// this target. Add it as a system include path.
174+
let clang_include = Path::new(&prebuilt).join("lib").join("clang");
175+
if let Ok(entries) = std::fs::read_dir(&clang_include) {
176+
for entry in entries.flatten() {
177+
let include = entry.path().join("include");
178+
if include.is_dir() {
179+
builder = builder.clang_arg(format!("-isystem{}", include.display()));
180+
break;
181+
}
182+
}
183+
}
184+
}
185+
}
186+
187+
let bindings = builder.generate().expect("Unable to generate bindings");
91188

92189
let out_path = PathBuf::from(
93190
env::var("OUT_DIR").expect("OUT_DIR was not defined by the cargo environment!"),
@@ -97,14 +194,27 @@ fn main() {
97194
.expect("Couldn't write bindings!");
98195

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

102198
if target_os == "windows" {
103199
println!("cargo:rustc-link-lib=bcrypt");
104200
println!("cargo:rustc-link-lib=shell32");
105-
}
106-
107-
if compiler.is_like_clang() {
201+
} else if target_os == "android" {
202+
// Android NDK ships libc++_static.a and libc++abi.a in the
203+
// per-architecture sysroot directory (not the API-level subdirectory).
204+
if let Some(ndk) = android_ndk_home() {
205+
let ndk_triple = android_sysroot_triple(&target);
206+
let host_tag = if cfg!(target_os = "macos") {
207+
"darwin-x86_64"
208+
} else {
209+
"linux-x86_64"
210+
};
211+
let ndk_lib_dir =
212+
format!("{ndk}/toolchains/llvm/prebuilt/{host_tag}/sysroot/usr/lib/{ndk_triple}");
213+
println!("cargo:rustc-link-search=native={ndk_lib_dir}");
214+
}
215+
println!("cargo:rustc-link-lib=static=c++_static");
216+
println!("cargo:rustc-link-lib=static=c++abi");
217+
} else if compiler.is_like_clang() {
108218
if target_os == "macos" {
109219
println!("cargo:rustc-link-lib=dylib=c++");
110220
} else {

0 commit comments

Comments
 (0)