Skip to content

Commit 28549a7

Browse files
committed
Use llvm-readelf to detect and bundle needed dynamic libs for Android
Dynamic libraries that a Rust library links against while not being present on the target platform (`libc++_shared.so` and other custom libraries) need to be bundled in the APK specifically. This approach is adopted frm `android-ndk-rs` while switching from `readelf` to `llvm-readelf`.
1 parent 121f241 commit 28549a7

File tree

6 files changed

+140
-1
lines changed

6 files changed

+140
-1
lines changed

xbuild/src/command/build.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,34 @@ pub fn build(env: &BuildEnv) -> Result<()> {
264264
let lib =
265265
env.cargo_artefact(&arch_dir.join("cargo"), target, CrateType::Cdylib)?;
266266
apk.add_lib(target.android_abi(), &lib)?;
267+
268+
let ndk = env.android_ndk();
269+
270+
let search_paths = []; // TODO: deps dir and everything from `cargo:rustc-link-search=`
271+
let ndk_sysroot_libs = ndk.join("usr/lib").join(target.ndk_triple());
272+
let provided_libs_paths = [
273+
ndk_sysroot_libs.as_path(),
274+
&*ndk_sysroot_libs.join(
275+
// Use libraries (symbols) from the lowest NDK that is supported by the application,
276+
// to prevent inadvertently making newer APIs available:
277+
// https://developer.android.com/ndk/guides/sdk-versions
278+
env.manifest()
279+
.android()
280+
.sdk
281+
.min_sdk_version
282+
.unwrap()
283+
.to_string(),
284+
),
285+
];
286+
287+
let extra_libs = xcommon::llvm::list_needed_libs_recursively(
288+
&lib,
289+
&search_paths,
290+
&provided_libs_paths,
291+
)?;
292+
for lib in &extra_libs {
293+
apk.add_lib(target.android_abi(), lib)?;
294+
}
267295
}
268296
}
269297

xbuild/src/command/doctor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ impl Default for Doctor {
1818
Check::new("clang++", Some(VersionCheck::new("--version", 0, 2))),
1919
Check::new("llvm-ar", None),
2020
Check::new("llvm-lib", None),
21+
Check::new("llvm-readelf", Some(VersionCheck::new("--version", 1, 4))),
2122
Check::new("lld", Some(VersionCheck::new("-flavor ld --version", 0, 1))),
2223
Check::new("lld-link", Some(VersionCheck::new("--version", 0, 1))),
2324
Check::new("lldb", Some(VersionCheck::new("--version", 0, 2))),

xbuild/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ impl CompileTarget {
296296

297297
pub fn rust_triple(self) -> Result<&'static str> {
298298
Ok(match (self.arch, self.platform) {
299-
(Arch::Arm64, Platform::Android) => "aarch64-linux-android",
299+
(_, Platform::Android) => self.ndk_triple(),
300300
(Arch::Arm64, Platform::Ios) => "aarch64-apple-ios",
301301
(Arch::Arm64, Platform::Linux) => "aarch64-unknown-linux-gnu",
302302
(Arch::Arm64, Platform::Macos) => "aarch64-apple-darwin",

xcommon/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ license = "Apache-2.0 OR MIT"
99
[dependencies]
1010
anyhow = "1.0.53"
1111
byteorder = "1.4.3"
12+
dunce = "1"
1213
image = { version = "0.24.0", default-features = false, features = ["png"] }
1314
pem = "1.0.2"
1415
rasn = "0.5.0"

xcommon/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
pub mod llvm;
2+
13
use anyhow::Result;
24
use byteorder::{LittleEndian, ReadBytesExt};
35
use image::imageops::FilterType;

xcommon/src/llvm.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//! LLVM utilities
2+
3+
use anyhow::{bail, ensure, Result};
4+
use std::collections::HashSet;
5+
use std::path::{Path, PathBuf};
6+
use std::process::Command;
7+
8+
/// Returns the set of additional libraries that need to be bundled with
9+
/// the given library, scanned recursively.
10+
///
11+
/// Any libraries in `provided_libs_paths` will be treated as available, without
12+
/// being emitted. Any other library not in `search_paths` or `provided_libs_paths`
13+
/// will result in an error.
14+
pub fn list_needed_libs_recursively(
15+
lib: &Path,
16+
search_paths: &[&Path],
17+
provided_libs_paths: &[&Path],
18+
) -> Result<HashSet<PathBuf>> {
19+
// Create a view of all libraries that are available on Android
20+
let mut provided = HashSet::new();
21+
for path in provided_libs_paths {
22+
for lib in find_libs_in_dir(path)? {
23+
// libc++_shared is bundled with the NDK but not available on-device
24+
if lib != "libc++_shared.so" {
25+
provided.insert(lib);
26+
}
27+
}
28+
}
29+
30+
let mut to_copy = HashSet::new();
31+
32+
let mut artifacts = vec![lib.to_path_buf()];
33+
while let Some(artifact) = artifacts.pop() {
34+
for need in list_needed_libs(&artifact)? {
35+
// c++_shared is available in the NDK but not on-device.
36+
// Must be bundled with the apk if used:
37+
// https://developer.android.com/ndk/guides/cpp-support#libc
38+
let search_paths = if need == "libc++_shared.so" {
39+
provided_libs_paths
40+
} else {
41+
search_paths
42+
};
43+
44+
if provided.insert(need.clone()) {
45+
if let Some(path) = find_library_path(search_paths, &need)? {
46+
to_copy.insert(path.clone());
47+
artifacts.push(path);
48+
} else {
49+
bail!("Shared library `{}` not found", need);
50+
}
51+
}
52+
}
53+
}
54+
55+
Ok(to_copy)
56+
}
57+
58+
/// List all required shared libraries as per the dynamic section
59+
fn list_needed_libs(library_path: &Path) -> Result<HashSet<String>> {
60+
let mut readelf = Command::new("llvm-readelf");
61+
let output = readelf.arg("--needed-libs").arg(library_path).output()?;
62+
ensure!(
63+
output.status.success(),
64+
"Failed to run `{:?}`: {}",
65+
readelf,
66+
output.status
67+
);
68+
let output = std::str::from_utf8(&output.stdout).unwrap();
69+
// TODO: Switch to `trim` because of CRLF on Windows?
70+
let output = output.strip_prefix("NeededLibraries [\n").unwrap();
71+
let output = output.strip_suffix("]\n").unwrap();
72+
let mut needed = HashSet::new();
73+
74+
for line in output.lines() {
75+
let lib = line.trim_start();
76+
needed.insert(lib.to_string());
77+
}
78+
Ok(needed)
79+
}
80+
81+
/// List names of shared libraries inside directory
82+
fn find_libs_in_dir(path: &Path) -> Result<HashSet<String>> {
83+
let mut libs = HashSet::new();
84+
let entries = std::fs::read_dir(path)?;
85+
for entry in entries {
86+
let entry = entry?;
87+
if !entry.path().is_dir() {
88+
if let Some(file_name) = entry.file_name().to_str() {
89+
if file_name.ends_with(".so") {
90+
libs.insert(file_name.to_string());
91+
}
92+
}
93+
}
94+
}
95+
Ok(libs)
96+
}
97+
98+
/// Resolves native library using search paths
99+
fn find_library_path(paths: &[&Path], library: &str) -> Result<Option<PathBuf>> {
100+
for path in paths {
101+
let lib_path = path.join(library);
102+
if lib_path.exists() {
103+
return Ok(Some(dunce::canonicalize(lib_path)?));
104+
}
105+
}
106+
Ok(None)
107+
}

0 commit comments

Comments
 (0)