crossbundle_tools/commands/android/native/apk/
add_libs_into_apk.rs

1use crate::{
2    error::*,
3    types::{AndroidNdk, AndroidSdk, AndroidTarget, IntoRustTriple, Profile},
4};
5use std::{
6    fs::File,
7    io::{BufRead, BufReader},
8    path::{Path, PathBuf},
9};
10
11/// Adds given lib and all reletad libs into APK.
12/// Uses `readelf`, `aapt` tools
13pub fn add_libs_into_apk(
14    sdk: &AndroidSdk,
15    ndk: &AndroidNdk,
16    apk_path: &Path,
17    lib_path: &Path,
18    build_target: AndroidTarget,
19    profile: Profile,
20    min_sdk_version: u32,
21    build_dir: &Path,
22    target_dir: &Path,
23) -> Result<PathBuf> {
24    // Get list of android system libs (https://developer.android.com/ndk/guides/stable_apis)
25    let mut system_libs = Vec::new();
26    let sysroot_platform_lib_dir = ndk.sysroot_platform_lib_dir(build_target, min_sdk_version)?;
27    for lib in get_libs_in_dir(&sysroot_platform_lib_dir)? {
28        system_libs.push(lib);
29    }
30    // Get list of dylibs_paths
31    let build_path = target_dir
32        .join(build_target.rust_triple())
33        .join(profile.as_ref());
34    let mut dylibs_paths = search_dylibs(&build_path.join("build"))?;
35    dylibs_paths.push(build_path.join("tools"));
36    // Get list of libs that main lib need for work
37    let lib_name = lib_path.file_name().unwrap().to_str().unwrap().to_owned();
38    let mut needed_libs = vec![];
39    recursively_define_needed_libs(
40        (lib_name, lib_path.to_owned()),
41        &ndk.toolchain_bin("readelf", build_target)?,
42        &ndk.sysroot_lib_dir(&build_target)?.join("libc++_shared.so"),
43        &system_libs,
44        &dylibs_paths,
45        &mut needed_libs,
46    )?;
47    let abi = build_target.android_abi();
48    let out_dir = build_dir.join("libs").join(profile).join(abi);
49    for (_lib_name, lib_path) in needed_libs {
50        aapt_add_lib(sdk, apk_path, &lib_path, &out_dir, abi)?;
51    }
52    Ok(out_dir)
53}
54
55/// Copy lib into `out_dir` then add this lib into apk file
56fn aapt_add_lib(
57    sdk: &AndroidSdk,
58    apk_path: &Path,
59    lib_path: &Path,
60    out_dir: &Path,
61    abi: &str,
62) -> Result<()> {
63    if !lib_path.exists() {
64        return Err(Error::PathNotFound(lib_path.to_owned()));
65    }
66    std::fs::create_dir_all(out_dir)?;
67    let file_name = lib_path.file_name().unwrap();
68    std::fs::copy(lib_path, &out_dir.join(file_name))?;
69    let native_lib_path = apk_path.parent().unwrap().join("lib").join(abi);
70    std::fs::create_dir_all(&native_lib_path)?;
71    std::fs::copy(lib_path, &native_lib_path.join(file_name))?;
72    // `aapt a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]`
73    // Add specified files to Zip-compatible archive
74    let mut aapt = sdk.build_tool(bin!("aapt"), Some(apk_path.parent().unwrap()))?;
75    aapt.arg("add")
76        .arg(apk_path)
77        .arg(format!("lib/{}/{}", abi, file_name.to_str().unwrap()));
78    aapt.output_err(true)?;
79    Ok(())
80}
81
82/// Search dylibs in given `deps_dir`
83pub fn search_dylibs(deps_dir: &Path) -> Result<Vec<PathBuf>> {
84    let mut paths = Vec::new();
85    for dep_dir in deps_dir.read_dir()? {
86        let output_file = dep_dir?.path().join("output");
87        if output_file.is_file() {
88            for line in BufReader::new(File::open(output_file)?).lines() {
89                let line = line?;
90                if let Some(link_search) = line.strip_prefix("cargo:rustc-link-search=") {
91                    let mut pie = link_search.split('=');
92                    let (kind, path) = match (pie.next(), pie.next()) {
93                        (Some(kind), Some(path)) => (kind, path),
94                        (Some(path), None) => ("all", path),
95                        _ => unreachable!(),
96                    };
97                    match kind {
98                        // FIXME: which kinds of search path we interested in
99                        "dependency" | "native" | "all" => paths.push(path.into()),
100                        _ => (),
101                    };
102                }
103            }
104        }
105    }
106    Ok(paths)
107}
108
109/// Update `needed_libs` hashset with given lib and all related libs.
110/// Note: libc++ is not a system lib. If you use libc++_shared.so, it must be included in
111/// your APK. https://developer.android.com/ndk/guides/cpp-support
112pub fn recursively_define_needed_libs(
113    (lib_name, lib_path): (String, PathBuf),
114    readelf_path: &Path,
115    libcpp_shared_path: &Path,
116    system_libs: &[String],
117    dylibs_paths: &[PathBuf],
118    needed_libs: &mut Vec<(String, PathBuf)>,
119) -> Result<()> {
120    let shared_libs = readelf_list_shared_libs(readelf_path, &lib_path)?;
121    needed_libs.push((lib_name, lib_path));
122    for lib_name in shared_libs {
123        if lib_name == "libc++_shared.so" {
124            needed_libs.push((lib_name, libcpp_shared_path.to_owned()));
125        } else if system_libs.contains(&lib_name) {
126            continue;
127        } else if !needed_libs.iter().any(|(name, _)| name == &lib_name) {
128            if let Some(lib_path) = find_library_path(dylibs_paths, &lib_name)? {
129                recursively_define_needed_libs(
130                    (lib_name, lib_path),
131                    readelf_path,
132                    libcpp_shared_path,
133                    system_libs,
134                    dylibs_paths,
135                    needed_libs,
136                )?;
137            } else {
138                eprintln!("Shared library \"{}\" not found.", lib_name);
139            }
140        };
141    }
142    Ok(())
143}
144
145/// List all linked shared libraries
146pub fn readelf_list_shared_libs(readelf_path: &Path, lib_path: &Path) -> Result<Vec<String>> {
147    let mut readelf = std::process::Command::new(readelf_path);
148    readelf.arg("-d").arg(lib_path);
149    let output = readelf.output_err(false)?;
150    let mut needed = Vec::new();
151    for line in output.stdout.lines() {
152        let line = line?;
153        if line.contains("(NEEDED)") {
154            let lib = line
155                .split("Shared library: [")
156                .last()
157                .and_then(|line| line.split(']').next());
158            if let Some(lib) = lib {
159                needed.push(lib.to_owned());
160            }
161        }
162    }
163    Ok(needed)
164}
165
166/// Resolves native library using search paths
167pub fn find_library_path<S: AsRef<Path>>(
168    paths: &[PathBuf],
169    lib_name: S,
170) -> Result<Option<PathBuf>> {
171    for path in paths {
172        let lib_path = path.join(&lib_name);
173        if lib_path.exists() {
174            return Ok(Some(dunce::canonicalize(lib_path)?));
175        }
176    }
177    Ok(None)
178}
179
180/// Return all files in directory with `.so` ending
181pub fn get_libs_in_dir(dir: &Path) -> std::io::Result<Vec<String>> {
182    let mut libs = Vec::new();
183    if dir.is_dir() {
184        for entry in std::fs::read_dir(dir)? {
185            let entry = entry?;
186            if !entry.path().is_dir() {
187                if let Some(file_name) = entry.file_name().to_str() {
188                    if file_name.ends_with(".so") {
189                        libs.push(file_name.to_owned());
190                    }
191                }
192            }
193        }
194    };
195    Ok(libs)
196}