1use crate::error::NdkError;
2use crate::manifest::AndroidManifest;
3use crate::ndk::{Key, Ndk};
4use crate::target::Target;
5use std::collections::HashMap;
6use std::collections::HashSet;
7use std::ffi::OsStr;
8use std::fs;
9use std::path::{Path, PathBuf};
10use std::process::Command;
11
12#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum StripConfig {
22 Default,
24 Strip,
26 Split,
29}
30
31impl Default for StripConfig {
32 fn default() -> Self {
33 Self::Default
34 }
35}
36
37pub struct ApkConfig {
38 pub ndk: Ndk,
39 pub build_dir: PathBuf,
40 pub apk_name: String,
41 pub assets: Option<PathBuf>,
42 pub resources: Option<PathBuf>,
43 pub manifest: AndroidManifest,
44 pub disable_aapt_compression: bool,
45 pub strip: StripConfig,
46 pub reverse_port_forward: HashMap<String, String>,
47}
48
49impl ApkConfig {
50 fn build_tool(&self, tool: &'static str) -> Result<Command, NdkError> {
51 let mut cmd = self.ndk.build_tool(tool)?;
52 cmd.current_dir(&self.build_dir);
53 Ok(cmd)
54 }
55
56 fn unaligned_apk(&self) -> PathBuf {
57 self.build_dir
58 .join(format!("{}-unaligned.apk", self.apk_name))
59 }
60
61 #[inline]
64 pub fn apk(&self) -> PathBuf {
65 self.build_dir.join(format!("{}.apk", self.apk_name))
66 }
67
68 pub fn create_apk(&self) -> Result<UnalignedApk, NdkError> {
69 std::fs::create_dir_all(&self.build_dir)?;
70 self.manifest.write_to(&self.build_dir)?;
71
72 let target_sdk_version = self
73 .manifest
74 .sdk
75 .target_sdk_version
76 .unwrap_or_else(|| self.ndk.default_target_platform());
77 let mut aapt = self.build_tool(bin!("aapt"))?;
78 aapt.arg("package")
79 .arg("-f")
80 .arg("-F")
81 .arg(self.unaligned_apk())
82 .arg("-M")
83 .arg("AndroidManifest.xml")
84 .arg("-I")
85 .arg(self.ndk.android_jar(target_sdk_version)?);
86
87 if self.disable_aapt_compression {
88 aapt.arg("-0").arg("");
89 }
90
91 if let Some(res) = &self.resources {
92 aapt.arg("-S").arg(res);
93 }
94
95 if let Some(assets) = &self.assets {
96 aapt.arg("-A").arg(assets);
97 }
98
99 if !aapt.status()?.success() {
100 return Err(NdkError::CmdFailed(aapt));
101 }
102
103 Ok(UnalignedApk {
104 config: self,
105 pending_libs: HashSet::default(),
106 })
107 }
108}
109
110pub struct UnalignedApk<'a> {
111 config: &'a ApkConfig,
112 pending_libs: HashSet<String>,
113}
114
115impl<'a> UnalignedApk<'a> {
116 pub fn config(&self) -> &ApkConfig {
117 self.config
118 }
119
120 pub fn add_lib(&mut self, path: &Path, target: Target) -> Result<(), NdkError> {
121 if !path.exists() {
122 return Err(NdkError::PathNotFound(path.into()));
123 }
124 let abi = target.android_abi();
125 let lib_path = Path::new("lib").join(abi).join(path.file_name().unwrap());
126 let out = self.config.build_dir.join(&lib_path);
127 std::fs::create_dir_all(out.parent().unwrap())?;
128
129 match self.config.strip {
130 StripConfig::Default => {
131 std::fs::copy(path, out)?;
132 }
133 StripConfig::Strip | StripConfig::Split => {
134 let obj_copy = self.config.ndk.toolchain_bin("objcopy", target)?;
135
136 {
137 let mut cmd = Command::new(&obj_copy);
138 cmd.arg("--strip-debug");
139 cmd.arg(path);
140 cmd.arg(&out);
141
142 if !cmd.status()?.success() {
143 return Err(NdkError::CmdFailed(cmd));
144 }
145 }
146
147 if self.config.strip == StripConfig::Split {
148 let dwarf_path = out.with_extension("dwarf");
149
150 {
151 let mut cmd = Command::new(&obj_copy);
152 cmd.arg("--only-keep-debug");
153 cmd.arg(path);
154 cmd.arg(&dwarf_path);
155
156 if !cmd.status()?.success() {
157 return Err(NdkError::CmdFailed(cmd));
158 }
159 }
160
161 let mut cmd = Command::new(obj_copy);
162 cmd.arg(format!("--add-gnu-debuglink={}", dwarf_path.display()));
163 cmd.arg(out);
164
165 if !cmd.status()?.success() {
166 return Err(NdkError::CmdFailed(cmd));
167 }
168 }
169 }
170 }
171
172 let lib_path_unix = lib_path.to_str().unwrap().replace('\\', "/");
176
177 self.pending_libs.insert(lib_path_unix);
178
179 Ok(())
180 }
181
182 pub fn add_runtime_libs(
183 &mut self,
184 path: &Path,
185 target: Target,
186 search_paths: &[&Path],
187 ) -> Result<(), NdkError> {
188 let abi_dir = path.join(target.android_abi());
189 for entry in fs::read_dir(&abi_dir).map_err(|e| NdkError::IoPathError(abi_dir, e))? {
190 let entry = entry?;
191 let path = entry.path();
192 if path.extension() == Some(OsStr::new("so")) {
193 self.add_lib_recursively(&path, target, search_paths)?;
194 }
195 }
196 Ok(())
197 }
198
199 pub fn add_pending_libs_and_align(self) -> Result<UnsignedApk<'a>, NdkError> {
200 let mut aapt = self.config.build_tool(bin!("aapt"))?;
201 aapt.arg("add");
202
203 if self.config.disable_aapt_compression {
204 aapt.arg("-0").arg("");
205 }
206
207 aapt.arg(self.config.unaligned_apk());
208
209 for lib_path_unix in self.pending_libs {
210 aapt.arg(lib_path_unix);
211 }
212
213 if !aapt.status()?.success() {
214 return Err(NdkError::CmdFailed(aapt));
215 }
216
217 let mut zipalign = self.config.build_tool(bin!("zipalign"))?;
218 zipalign
219 .arg("-f")
220 .arg("-v")
221 .arg("4")
222 .arg(self.config.unaligned_apk())
223 .arg(self.config.apk());
224
225 if !zipalign.status()?.success() {
226 return Err(NdkError::CmdFailed(zipalign));
227 }
228
229 Ok(UnsignedApk(self.config))
230 }
231}
232
233pub struct UnsignedApk<'a>(&'a ApkConfig);
234
235impl<'a> UnsignedApk<'a> {
236 pub fn sign(self, key: Key) -> Result<Apk, NdkError> {
237 let mut apksigner = self.0.build_tool(bat!("apksigner"))?;
238 apksigner
239 .arg("sign")
240 .arg("--ks")
241 .arg(&key.path)
242 .arg("--ks-pass")
243 .arg(format!("pass:{}", &key.password))
244 .arg(self.0.apk());
245 if !apksigner.status()?.success() {
246 return Err(NdkError::CmdFailed(apksigner));
247 }
248 Ok(Apk::from_config(self.0))
249 }
250}
251
252pub struct Apk {
253 path: PathBuf,
254 package_name: String,
255 ndk: Ndk,
256 reverse_port_forward: HashMap<String, String>,
257}
258
259impl Apk {
260 pub fn from_config(config: &ApkConfig) -> Self {
261 let ndk = config.ndk.clone();
262 Self {
263 path: config.apk(),
264 package_name: config.manifest.package.clone(),
265 ndk,
266 reverse_port_forward: config.reverse_port_forward.clone(),
267 }
268 }
269
270 pub fn reverse_port_forwarding(&self, device_serial: Option<&str>) -> Result<(), NdkError> {
271 for (from, to) in &self.reverse_port_forward {
272 println!("Reverse port forwarding from {} to {}", from, to);
273 let mut adb = self.ndk.adb(device_serial)?;
274
275 adb.arg("reverse").arg(from).arg(to);
276
277 if !adb.status()?.success() {
278 return Err(NdkError::CmdFailed(adb));
279 }
280 }
281
282 Ok(())
283 }
284
285 pub fn install(&self, device_serial: Option<&str>) -> Result<(), NdkError> {
286 let mut adb = self.ndk.adb(device_serial)?;
287
288 adb.arg("install").arg("-r").arg(&self.path);
289 if !adb.status()?.success() {
290 return Err(NdkError::CmdFailed(adb));
291 }
292 Ok(())
293 }
294
295 pub fn start(&self, device_serial: Option<&str>) -> Result<(), NdkError> {
296 let mut adb = self.ndk.adb(device_serial)?;
297 adb.arg("shell")
298 .arg("am")
299 .arg("start")
300 .arg("-a")
301 .arg("android.intent.action.MAIN")
302 .arg("-n")
303 .arg(format!("{}/android.app.NativeActivity", self.package_name));
304
305 if !adb.status()?.success() {
306 return Err(NdkError::CmdFailed(adb));
307 }
308
309 Ok(())
310 }
311
312 pub fn uidof(&self, device_serial: Option<&str>) -> Result<u32, NdkError> {
313 let mut adb = self.ndk.adb(device_serial)?;
314 adb.arg("shell")
315 .arg("pm")
316 .arg("list")
317 .arg("package")
318 .arg("-U")
319 .arg(&self.package_name);
320 let output = adb.output()?;
321
322 if !output.status.success() {
323 return Err(NdkError::CmdFailed(adb));
324 }
325
326 let output = std::str::from_utf8(&output.stdout).unwrap();
327 let (_package, uid) = output
328 .lines()
329 .filter_map(|line| line.split_once(' '))
330 .find(|(package, _uid)| package.strip_prefix("package:") == Some(&self.package_name))
333 .ok_or(NdkError::PackageNotInOutput {
334 package: self.package_name.clone(),
335 output: output.to_owned(),
336 })?;
337 let uid = uid
338 .strip_prefix("uid:")
339 .ok_or(NdkError::UidNotInOutput(output.to_owned()))?;
340 uid.parse()
341 .map_err(|e| NdkError::NotAUid(e, uid.to_owned()))
342 }
343}