|
| 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 | + let output = output.strip_prefix("NeededLibraries [\n").unwrap(); |
| 70 | + let output = output.strip_suffix("]\n").unwrap(); |
| 71 | + let mut needed = HashSet::new(); |
| 72 | + |
| 73 | + for line in output.lines() { |
| 74 | + let lib = line.trim_start(); |
| 75 | + needed.insert(lib.to_string()); |
| 76 | + } |
| 77 | + Ok(needed) |
| 78 | +} |
| 79 | + |
| 80 | +/// List names of shared libraries inside directory |
| 81 | +fn find_libs_in_dir(path: &Path) -> Result<HashSet<String>> { |
| 82 | + let mut libs = HashSet::new(); |
| 83 | + let entries = std::fs::read_dir(path)?; |
| 84 | + for entry in entries { |
| 85 | + let entry = entry?; |
| 86 | + if !entry.path().is_dir() { |
| 87 | + if let Some(file_name) = entry.file_name().to_str() { |
| 88 | + if file_name.ends_with(".so") { |
| 89 | + libs.insert(file_name.to_string()); |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + } |
| 94 | + Ok(libs) |
| 95 | +} |
| 96 | + |
| 97 | +/// Resolves native library using search paths |
| 98 | +fn find_library_path(paths: &[&Path], library: &str) -> Result<Option<PathBuf>> { |
| 99 | + for path in paths { |
| 100 | + let lib_path = path.join(library); |
| 101 | + if lib_path.exists() { |
| 102 | + return Ok(Some(dunce::canonicalize(lib_path)?)); |
| 103 | + } |
| 104 | + } |
| 105 | + Ok(None) |
| 106 | +} |
0 commit comments