creator_tools/commands/android/
add_libs_into_apk.rs

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