android_tools_rs/bundletool/build_apks.rs
1use crate::error::*;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5/// ## Generate a set of APKs from your app bundle
6///
7/// When `bundletool` generates APKs from your app bundle,it includes them in a container
8/// called an APK set archive, which uses the `.apks` file extension. To generate an APK
9/// set for all device configurations your app supports from your app bundle, use the
10/// `bundletool build-apks` command, as shown below.
11///
12/// ```xml
13/// bundletool build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks
14/// ```
15///
16/// If you want to deploy the APKs to a device, you need to also include your app's
17/// signing information, as shown in the command below. If you do not specify signing
18/// information, bundletool attempts to sign your APKs with a debug key for you.
19///
20/// ```xml
21/// bundletool build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks
22/// --ks=/MyApp/keystore.jks
23/// --ks-pass=file:/MyApp/keystore.pwd
24/// --ks-key-alias=MyKeyAlias
25/// --key-pass=file:/MyApp/key.pwd
26/// ```
27///
28/// The table below describes the various flags and options you can set when using the
29/// `bundletool build-apks` command in greater detail. Only `--bundle` and `--output` are
30/// required—all other flags are optional.
31#[derive(Debug, PartialEq)]
32pub struct BuildApks {
33 bundle: PathBuf,
34 output: PathBuf,
35 overwrite: bool,
36 aapt2: Option<PathBuf>,
37 ks: Option<PathBuf>,
38 ks_pass_pass: Option<String>,
39 ks_pass_file: Option<PathBuf>,
40 ks_key_alias: Option<String>,
41 key_pass_pass: Option<String>,
42 key_pass_file: Option<PathBuf>,
43 connected_device: bool,
44 device_id: Option<String>,
45 device_spec: Option<PathBuf>,
46 mode_universal: bool,
47 local_testing: bool,
48}
49
50#[derive(Debug, PartialEq)]
51pub enum KsPass {
52 KsPassPass,
53 KsPassFile,
54}
55
56#[derive(Debug, PartialEq)]
57pub enum KeyPass {
58 KeyPassPass,
59 KeyPassFile,
60}
61
62/// (`Required`) Specifies the path to the app bundle you built using Android Studio.
63/// To learn more, read [`Build your project`].
64///
65/// [`Build your project`]::https://developer.android.com/studio/run#reference
66///
67/// (Required) Specifies the name of the output `.apks` file, which contains all the
68/// APK artifacts for your app. To test the artifacts in this file on a device, go to
69/// the section about how to [`deploy APKs to a connected device`].
70///
71/// [`deploy APKs to a connected device`]::https://developer.android.com/studio/command-line/bundletool#deploy_with_bundletool
72impl BuildApks {
73 pub fn new(bundle: &Path, output: &Path) -> Self {
74 Self {
75 bundle: bundle.to_owned(),
76 output: output.to_owned(),
77 overwrite: false,
78 aapt2: None,
79 ks: None,
80 ks_pass_pass: None,
81 ks_pass_file: None,
82 ks_key_alias: None,
83 key_pass_pass: None,
84 key_pass_file: None,
85 connected_device: false,
86 device_id: None,
87 device_spec: None,
88 mode_universal: false,
89 local_testing: false,
90 }
91 }
92
93 /// Include this flag if you want to overwrite any existing output file with the same
94 /// path you specify using the --output option. If you don't include this flag and the
95 /// output file already exists, you get a build error.
96 pub fn overwrite(&mut self, overwrite: bool) -> &mut Self {
97 self.overwrite = overwrite;
98 self
99 }
100
101 /// Specifies a custom path to AAPT2. By default, bundletool includes its own version
102 /// of AAPT2.
103 pub fn aapt2(&mut self, aapt2: &Path) -> &mut Self {
104 self.aapt2 = Some(aapt2.to_owned());
105 self
106 }
107
108 /// Specifies the path to the deployment keystore used to sign the APKs. This flag is
109 /// optional. If you don't include it, bundletool attempts to sign your APKs with a
110 /// debug signing key.
111 pub fn ks(&mut self, ks: &Path) -> &mut Self {
112 self.ks = Some(ks.to_owned());
113 self
114 }
115
116 /// Specifies your keystore's password. If you're specifying a password in plain text,
117 /// qualify it with pass:. If you're passing the path to a file that contains the
118 /// password, qualify it with file:. If you specify a keystore using the --ks flag
119 /// without specifying --ks-pass, bundletool prompts you for a password from the
120 /// command line.
121 pub fn ks_pass_pass(&mut self, ks_pass_pass: String) -> &mut Self {
122 self.ks_pass_pass = Some(ks_pass_pass);
123 self
124 }
125
126 /// Specifies your keystore's password. If you're specifying a password in plain text,
127 /// qualify it with pass:. If you're passing the path to a file that contains the
128 /// password, qualify it with file:. If you specify a keystore using the --ks flag
129 /// without specifying --ks-pass, bundletool prompts you for a password from the
130 /// command line.
131 pub fn ks_pass_file(&mut self, ks_pass_file: &Path) -> &mut Self {
132 self.ks_pass_file = Some(ks_pass_file.to_owned());
133 self
134 }
135
136 /// Specifies the alias of the signing key you want to use.
137 pub fn ks_key_alias(&mut self, ks_key_alias: String) -> &mut Self {
138 self.ks_key_alias = Some(ks_key_alias);
139 self
140 }
141
142 ///Specifies the password for the signing key. If you're specifying a password in
143 /// plain text, qualify it with pass:. If you're passing the path to a file that
144 /// contains the password, qualify it with file:.
145 ///
146 /// If this password is identical to the one for the keystore itself, you can omit
147 /// this flag.
148 pub fn key_pass_pass(&mut self, key_pass_pass: String) -> &mut Self {
149 self.key_pass_pass = Some(key_pass_pass);
150 self
151 }
152
153 ///Specifies the password for the signing key. If you're specifying a password in
154 /// plain text, qualify it with pass:. If you're passing the path to a file that
155 /// contains the password, qualify it with file:.
156 ///
157 /// If this password is identical to the one for the keystore itself, you can omit
158 /// this flag.
159 pub fn key_pass_file(&mut self, key_pass_file: &Path) -> &mut Self {
160 self.key_pass_file = Some(key_pass_file.to_owned());
161 self
162 }
163
164 /// Instructs bundletool to build APKs that target the configuration of a connected
165 /// device. If you don't include this flag, bundletool generates APKs for all device
166 /// configurations your app supports.
167 pub fn connected_device(&mut self, connected_device: bool) -> &mut Self {
168 self.connected_device = connected_device;
169 self
170 }
171
172 /// If you have more than one connected device, use this flag to specify the serial ID
173 /// of the device to which you want to deploy your app.
174 pub fn device_id(&mut self, device_id: String) -> &mut Self {
175 self.device_id = Some(device_id);
176 self
177 }
178
179 /// Use this flag to provide a path to a .json file that specifies the device
180 /// configuration you want to target. To learn more, go to the section about how to
181 /// [`Create and use device specification JSON files`].
182 ///
183 /// [`Create and use device specification JSON files`]::https://developer.android.com/studio/command-line/bundletool#create_use_json
184 pub fn device_spec(&mut self, device_spec: &Path) -> &mut Self {
185 self.device_spec = Some(device_spec.to_owned());
186 self
187 }
188
189 /// Set the mode to universal if you want bundletool to build only a single APK that
190 /// includes all of your app's code and resources such that the APK is compatible with
191 /// all device configurations your app supports.
192 ///
193 /// ## Note
194 /// `bundletool` includes only feature modules that specify `<dist:fusing
195 /// dist:include="true"/>` in their manifest in a universal APK. To learn more, read
196 /// about the [`feature module manifest`].
197 ///
198 /// Keep in mind, these APKs are larger than those optimized for a particular device
199 /// configuration. However, they're easier to share with internal testers who, for
200 /// example, want to test your app on multiple device configurations.
201 ///
202 /// [`feature module manifest`]::https://developer.android.com/guide/playcore/feature-delivery#dynamic_feature_manifest
203 pub fn mode_universal(&mut self, mode_universal: bool) -> &mut Self {
204 self.mode_universal = mode_universal;
205 self
206 }
207
208 /// Use this flag to enable your app bundle for local testing. Local testing allows
209 /// for quick, iterative testing cycles without the need to upload to Google Play
210 /// servers.
211 ///
212 /// For an example of how to test module installation using the --local-testing flag,
213 /// see [`Locally test module installs`].
214 ///
215 /// [`Locally test module installs`]::https://developer.android.com/guide/app-bundle/test/testing-fakesplitinstallmanager
216 pub fn local_testing(&mut self, local_testing: bool) -> &mut Self {
217 self.local_testing = local_testing;
218 self
219 }
220
221 pub fn run(&self) -> Result<()> {
222 let mut build_apks = Command::new("java");
223 build_apks.arg("-jar");
224 if let Ok(bundletool_path) = std::env::var("BUNDLETOOL_PATH") {
225 build_apks.arg(bundletool_path);
226 } else {
227 return Err(Error::BundletoolNotFound);
228 }
229 build_apks.arg("build-apks");
230 build_apks.arg("--bundle").arg(&self.bundle);
231 build_apks.arg("--output").arg(&self.output);
232 if self.overwrite {
233 build_apks.arg("--overwrite");
234 }
235 if let Some(aapt2) = &self.aapt2 {
236 build_apks.arg("--aapt2").arg(aapt2);
237 }
238 if let Some(ks) = &self.ks {
239 build_apks.arg("--ks").arg(ks);
240 }
241 if let Some(ks_pass_pass) = &self.ks_pass_pass {
242 build_apks.arg("--ks-pass=pass:").arg(ks_pass_pass);
243 }
244 if let Some(ks_pass_file) = &self.ks_pass_file {
245 build_apks.arg("--ks-pass=file:").arg(ks_pass_file);
246 }
247 if let Some(ks_key_alias) = &self.ks_key_alias {
248 build_apks.arg("--ks-key-alias").arg(ks_key_alias);
249 }
250 if let Some(key_pass_pass) = &self.key_pass_pass {
251 build_apks.arg("--key-pass=pass").arg(key_pass_pass);
252 }
253 if let Some(key_pass_file) = &self.key_pass_file {
254 build_apks.arg("--key-pass=file").arg(key_pass_file);
255 }
256 if self.connected_device {
257 build_apks.arg("--connected-device");
258 }
259 if let Some(device_id) = &self.device_id {
260 build_apks.arg("--device-id").arg(device_id);
261 }
262 if let Some(device_spec) = &self.device_spec {
263 build_apks.arg("--device-spec").arg(device_spec);
264 }
265 if self.mode_universal {
266 build_apks.arg("--mode=universal");
267 }
268 if self.local_testing {
269 build_apks.arg("--local-testing");
270 }
271 build_apks.output_err(true)?;
272 Ok(())
273 }
274}