Skip to content

Commit 875f933

Browse files
authored
Append runtime_libs specified in manifest to APK (#106)
Certain apps and crates `dlopen` libraries at runtime (which are not listed in the shared library table by design, hence not found by `llvm-readobj`). Facilitate this by allowing the user to specify a list of folders that need to be scanned for libraries to include in the final package at while building. This folder structure includes the platform ABI to select the desired shared libraries.
1 parent c8efbd8 commit 875f933

File tree

3 files changed

+114
-60
lines changed

3 files changed

+114
-60
lines changed

xbuild/src/command/build.rs

+62-15
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use apk::Apk;
77
use appbundle::AppBundle;
88
use appimage::AppImage;
99
use msix::Msix;
10+
use std::collections::HashSet;
11+
use std::ffi::OsStr;
1012
use std::path::Path;
1113
use xcommon::{Zip, ZipFileOptions};
1214

@@ -86,7 +88,6 @@ pub fn build(env: &BuildEnv) -> Result<()> {
8688
let arch_dir = platform_dir.join(target.arch().to_string());
8789
let cargo_dir = arch_dir.join("cargo");
8890
let lib = env.cargo_artefact(&cargo_dir, target, CrateType::Cdylib)?;
89-
apk.add_lib(target.android_abi(), &lib)?;
9091

9192
let ndk = env.android_ndk();
9293

@@ -131,21 +132,67 @@ pub fn build(env: &BuildEnv) -> Result<()> {
131132
),
132133
];
133134

134-
let extra_libs = xcommon::llvm::list_needed_libs_recursively(
135-
&lib,
136-
&search_paths,
137-
&provided_libs_paths,
138-
)
139-
.with_context(|| {
140-
format!(
141-
"Failed to collect all required libraries for `{}` with `{:?}` available libraries and `{:?}` shippable libraries",
142-
lib.display(),
143-
provided_libs_paths,
144-
search_paths
135+
let mut explicit_libs = vec![lib];
136+
137+
// Collect the libraries the user wants to include
138+
for runtime_lib_path in env.config().runtime_libs(env.target().platform()) {
139+
let abi_dir = env
140+
.cargo()
141+
.package_root()
142+
.join(runtime_lib_path)
143+
.join(target.android_abi().android_abi());
144+
let entries = std::fs::read_dir(abi_dir)?;
145+
for entry in entries {
146+
let entry = entry?;
147+
let path = entry.path();
148+
if !path.is_dir() && path.extension() == Some(OsStr::new("so")) {
149+
explicit_libs.push(path);
150+
}
151+
}
152+
}
153+
154+
// Collect the names of libraries provided by the user, and assume these
155+
// are available for other dependencies to link to, too.
156+
let mut included_libs = explicit_libs
157+
.iter()
158+
.map(|p| p.file_name().unwrap().to_owned())
159+
.collect::<HashSet<_>>();
160+
161+
// Collect the names of all libraries that are available on Android
162+
for provided_libs_path in provided_libs_paths {
163+
included_libs
164+
.extend(xcommon::llvm::find_libs_in_dir(provided_libs_path)?);
165+
}
166+
167+
// libc++_shared is bundled with the NDK but not available on-device
168+
included_libs.remove(OsStr::new("libc++_shared.so"));
169+
170+
let mut needs_cpp_shared = false;
171+
172+
for lib in explicit_libs {
173+
apk.add_lib(target.android_abi(), &lib)?;
174+
175+
let (extra_libs, cpp_shared) = xcommon::llvm::list_needed_libs_recursively(
176+
&lib,
177+
&search_paths,
178+
&included_libs,
145179
)
146-
})?;
147-
for lib in &extra_libs {
148-
apk.add_lib(target.android_abi(), lib)?;
180+
.with_context(|| {
181+
format!(
182+
"Failed to collect all required libraries for `{}` with `{:?}` available libraries and `{:?}` shippable libraries",
183+
lib.display(),
184+
provided_libs_paths,
185+
search_paths
186+
)
187+
})?;
188+
needs_cpp_shared |= cpp_shared;
189+
for lib in &extra_libs {
190+
apk.add_lib(target.android_abi(), lib)?;
191+
}
192+
}
193+
if needs_cpp_shared {
194+
let cpp_shared = ndk_sysroot_libs.join("libc++_shared.so");
195+
apk.add_lib(target.android_abi(), &cpp_shared)?;
149196
}
150197
}
151198
}

xbuild/src/config.rs

+36-10
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,42 @@ impl Config {
3535
})
3636
}
3737

38+
/// Selects a generic config value from [`GenericConfig`], platform-specific
39+
/// overrides first and otherwise falls back to a shared option in the root.
40+
pub fn select_generic<T: ?Sized>(
41+
&self,
42+
platform: Platform,
43+
select: impl Fn(&GenericConfig) -> Option<&T>,
44+
) -> Option<&T> {
45+
let generic = match platform {
46+
Platform::Android => &self.android.generic,
47+
Platform::Ios => &self.ios.generic,
48+
Platform::Macos => &self.macos.generic,
49+
Platform::Linux => &self.linux.generic,
50+
Platform::Windows => &self.windows.generic,
51+
};
52+
select(generic).or_else(|| select(&self.generic))
53+
}
54+
3855
pub fn icon(&self, platform: Platform) -> Option<&Path> {
39-
let icon = match platform {
40-
Platform::Android => self.android.generic.icon.as_deref(),
41-
Platform::Ios => self.ios.generic.icon.as_deref(),
42-
Platform::Macos => self.macos.generic.icon.as_deref(),
43-
Platform::Linux => self.linux.generic.icon.as_deref(),
44-
Platform::Windows => self.windows.generic.icon.as_deref(),
56+
self.select_generic(platform, |g| g.icon.as_deref())
57+
}
58+
59+
pub fn runtime_libs(&self, platform: Platform) -> Vec<PathBuf> {
60+
let generic = match platform {
61+
Platform::Android => &self.android.generic,
62+
Platform::Ios => &self.ios.generic,
63+
Platform::Macos => &self.macos.generic,
64+
Platform::Linux => &self.linux.generic,
65+
Platform::Windows => &self.windows.generic,
4566
};
46-
if let Some(icon) = icon {
47-
return Some(icon);
48-
}
49-
self.generic.icon.as_deref()
67+
68+
generic
69+
.runtime_libs
70+
.iter()
71+
.chain(&self.generic.runtime_libs)
72+
.cloned()
73+
.collect()
5074
}
5175

5276
pub fn apply_rust_package(
@@ -302,6 +326,8 @@ struct RawConfig {
302326
#[derive(Clone, Debug, Default, Deserialize)]
303327
pub struct GenericConfig {
304328
icon: Option<PathBuf>,
329+
#[serde(default)]
330+
runtime_libs: Vec<PathBuf>,
305331
}
306332

307333
#[derive(Clone, Debug, Default, Deserialize)]

xcommon/src/llvm.rs

+16-35
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,23 @@
22
33
use anyhow::{bail, ensure, Context, Result};
44
use std::collections::HashSet;
5+
use std::ffi::{OsStr, OsString};
56
use std::path::{Path, PathBuf};
67
use std::process::Command;
78

89
/// Returns the set of additional libraries that need to be bundled with
910
/// the given library, scanned recursively.
1011
///
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`
12+
/// Any libraries in `provided_libs` will be treated as available, without
13+
/// being emitted. Any other library not in `search_paths` or `provided_libs`
1314
/// will result in an error.
1415
pub fn list_needed_libs_recursively(
1516
lib: &Path,
1617
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).with_context(|| {
23-
format!("Unable to list available libraries in `{}`", path.display())
24-
})? {
25-
// libc++_shared is bundled with the NDK but not available on-device
26-
if lib != "libc++_shared.so" {
27-
provided.insert(lib);
28-
}
29-
}
30-
}
31-
18+
provided_libs: &HashSet<OsString>,
19+
) -> Result<(HashSet<PathBuf>, bool)> {
3220
let mut to_copy = HashSet::new();
21+
let mut needs_cpp_shared = false;
3322

3423
let mut artifacts = vec![lib.to_path_buf()];
3524
while let Some(artifact) = artifacts.pop() {
@@ -39,16 +28,11 @@ pub fn list_needed_libs_recursively(
3928
artifact.display()
4029
)
4130
})? {
42-
// c++_shared is available in the NDK but not on-device.
43-
// Must be bundled with the apk if used:
44-
// https://developer.android.com/ndk/guides/cpp-support#libc
45-
let search_paths = if need == "libc++_shared.so" {
46-
provided_libs_paths
47-
} else {
48-
search_paths
49-
};
50-
51-
if provided.insert(need.clone()) {
31+
if need == "libc++_shared.so" {
32+
// c++_shared is available in the NDK but not on-device. Communicate that
33+
// we need to copy it, once
34+
needs_cpp_shared = true;
35+
} else if !provided_libs.contains(OsStr::new(&need)) {
5236
if let Some(path) = find_library_path(search_paths, &need).with_context(|| {
5337
format!(
5438
"Could not iterate one or more search directories in `{:?}` while searching for library `{}`",
@@ -64,7 +48,7 @@ pub fn list_needed_libs_recursively(
6448
}
6549
}
6650

67-
Ok(to_copy)
51+
Ok((to_copy, needs_cpp_shared))
6852
}
6953

7054
/// List all required shared libraries as per the dynamic section
@@ -93,17 +77,14 @@ fn list_needed_libs(library_path: &Path) -> Result<HashSet<String>> {
9377
}
9478

9579
/// List names of shared libraries inside directory
96-
fn find_libs_in_dir(path: &Path) -> Result<HashSet<String>> {
80+
pub fn find_libs_in_dir(path: &Path) -> Result<HashSet<OsString>> {
9781
let mut libs = HashSet::new();
9882
let entries = std::fs::read_dir(path)?;
9983
for entry in entries {
10084
let entry = entry?;
101-
if !entry.path().is_dir() {
102-
if let Some(file_name) = entry.file_name().to_str() {
103-
if file_name.ends_with(".so") {
104-
libs.insert(file_name.to_string());
105-
}
106-
}
85+
let path = entry.path();
86+
if !path.is_dir() && path.extension() == Some(OsStr::new("so")) {
87+
libs.insert(entry.file_name().to_owned());
10788
}
10889
}
10990
Ok(libs)

0 commit comments

Comments
 (0)