Skip to content

Commit fb5e1f7

Browse files
committed
feat(build): Android support
This commit introduces build instructions for building the kernel targetting android. The instructions follows a design that is minimal, counting it will be executed in the nix environment which will be implemented in the next commits. We consciously made this decision; Nix runs on most systems where specific implementations would otherwise be necessary for each one. Nix helps keep these instructions minimal and reduces the chance of introducing bugs.
1 parent b8e8fa5 commit fb5e1f7

2 files changed

Lines changed: 88 additions & 5 deletions

File tree

libbitcoinkernel-sys/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
9+
- Added build support for Android
910

1011
## [0.3.0] - 2026-05-20
1112

libbitcoinkernel-sys/build.rs

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ fn main() {
1515

1616
let build_config = "RelWithDebInfo";
1717

18-
Command::new("cmake")
18+
let mut cmake_configure = Command::new("cmake");
19+
cmake_configure
1920
.arg("-B")
2021
.arg(&build_dir)
2122
.arg("-S")
@@ -38,7 +39,58 @@ fn main() {
3839
.arg("-DBUILD_SHARED_LIBS=OFF")
3940
.arg("-DCMAKE_INSTALL_LIBDIR=lib")
4041
.arg("-DENABLE_IPC=OFF")
41-
.arg(format!("-DCMAKE_INSTALL_PREFIX={}", install_dir.display()))
42+
.arg(format!("-DCMAKE_INSTALL_PREFIX={}", install_dir.display()));
43+
44+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
45+
let is_android = target_os == "android";
46+
47+
let ndk = if is_android {
48+
Some(
49+
env::var("ANDROID_NDK_HOME")
50+
.expect("Android target detected but ANDROID_NDK_HOME is not set"),
51+
)
52+
} else {
53+
None
54+
};
55+
56+
if is_android {
57+
let ndk = ndk.as_deref().unwrap();
58+
let toolchain_file = format!("{ndk}/build/cmake/android.toolchain.cmake");
59+
60+
// Rust target triple -> NDK ABI name.
61+
let abi = match env::var("TARGET").unwrap() {
62+
t if t.contains("aarch64") => "arm64-v8a",
63+
t if t.contains("armv7") => "armeabi-v7a",
64+
t if t.contains("x86_64") => "x86_64",
65+
target => panic!("Unsupported Android ABI: {target}"),
66+
};
67+
68+
// API level 24+ is required because Bitcoin Core uses getifaddrs
69+
// which was introduced in Android API 24 (Nougat).
70+
//
71+
// This can be overridden to a higher level by setting ANDROID_API_LEVEL.
72+
let api_level = match env::var("ANDROID_API_LEVEL") {
73+
Ok(level) => {
74+
let n: u32 = level.parse().expect("ANDROID_API_LEVEL must be a number");
75+
assert!(n >= 24, "ANDROID_API_LEVEL must be 24+");
76+
level
77+
}
78+
_ => "24".to_string(),
79+
};
80+
81+
cmake_configure
82+
.arg(format!("-DCMAKE_TOOLCHAIN_FILE={toolchain_file}"))
83+
.arg(format!("-DANDROID_ABI={abi}"))
84+
.arg(format!("-DANDROID_PLATFORM=android-{api_level}"))
85+
.arg("-DCMAKE_SYSTEM_NAME=Android")
86+
.arg(format!("-DCMAKE_ANDROID_NDK={ndk}"))
87+
// The Android NDK toolchain sets CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
88+
// to ONLY, which prevents cmake from finding host packages via
89+
// CMAKE_PREFIX_PATH. Override it so Boost headers can be located.
90+
.arg("-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH");
91+
}
92+
93+
cmake_configure
4294
.status()
4395
.expect("cmake should be installed and available in PATH");
4496

@@ -70,19 +122,49 @@ fn main() {
70122
} else {
71123
install_dir.join("lib")
72124
};
125+
73126
println!("cargo:rustc-link-search=native={}", lib_dir.display());
74127

75128
println!("cargo:rustc-link-lib=static=bitcoinkernel");
76129

77130
let compiler = cc::Build::new().get_compiler();
78-
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
79131

80132
if target_os == "windows" {
81133
println!("cargo:rustc-link-lib=bcrypt");
82134
println!("cargo:rustc-link-lib=shell32");
83-
}
135+
} else if is_android {
136+
// Without these, changing either variable between builds won't trigger a rebuild,
137+
// so we could silently end up with a stale binary built against the wrong NDK or API level.
138+
println!("cargo:rerun-if-env-changed=ANDROID_NDK_HOME");
139+
println!("cargo:rerun-if-env-changed=ANDROID_API_LEVEL");
140+
// Android NDK ships libc++_static.a and libc++abi.a in the
141+
// per-architecture sysroot directory (not the API-level subdirectory).
142+
let ndk = ndk.as_deref().unwrap();
143+
144+
// Rust target triple -> NDK sysroot lib directory triple.
145+
// armv7 differs: Rust says `armv7-linux-androideabi`, NDK says `arm-linux-androideabi`.
146+
let ndk_triple = env::var("TARGET")
147+
.map(|target| {
148+
if target.starts_with("armv7") {
149+
"arm-linux-androideabi".into()
150+
} else {
151+
target
152+
}
153+
})
154+
.unwrap();
155+
156+
let host_tag = match std::env::consts::OS {
157+
"macos" => "darwin-x86_64",
158+
"linux" => "linux-x86_64",
159+
os => panic!("unsupported build host for Android cross-compilation: {os}"),
160+
};
84161

85-
if compiler.is_like_clang() {
162+
let ndk_lib_dir =
163+
format!("{ndk}/toolchains/llvm/prebuilt/{host_tag}/sysroot/usr/lib/{ndk_triple}");
164+
println!("cargo:rustc-link-search=native={ndk_lib_dir}");
165+
println!("cargo:rustc-link-lib=static=c++_static");
166+
println!("cargo:rustc-link-lib=static=c++abi");
167+
} else if compiler.is_like_clang() {
86168
if target_os == "macos" {
87169
println!("cargo:rustc-link-lib=dylib=c++");
88170
} else {

0 commit comments

Comments
 (0)