android_tools/bundletool/build_apks.rs
1use super::bundletool;
2use crate::error::*;
3use std::path::{Path, PathBuf};
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/// ```sh
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, Default)]
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)]
51pub enum KsPass {
52 KsPassPass,
53 KsPassFile,
54}
55
56#[derive(Debug)]
57pub enum KeyPass {
58 KeyPassPass,
59 KeyPassFile,
60}
61
62impl BuildApks {
63 /// (`Required`) Specifies the path to the app bundle you built using Android Studio.
64 /// To learn more, read [`Build your project`].
65 ///
66 /// (Required) Specifies the name of the output `.apks` file, which contains all the
67 /// APK artifacts for your app. To test the artifacts in this file on a device, go to
68 /// the section about how to
69 /// [`deploy APKs to a connected device`](https://developer.android.com/studio/command-line/bundletool#deploy_with_bundletool)
70 ///
71 /// [Build your project]: (https://developer.android.com/studio/run#reference)
72 pub fn new(bundle: &Path, output: &Path) -> Self {
73 Self {
74 bundle: bundle.to_owned(),
75 output: output.to_owned(),
76 ..Default::default()
77 }
78 }
79
80 /// Include this flag if you want to overwrite any existing output file with the same
81 /// path you specify using the `--output` option. If you don't include this flag and
82 /// the output file already exists, you get a build error
83 pub fn overwrite(&mut self, overwrite: bool) -> &mut Self {
84 self.overwrite = overwrite;
85 self
86 }
87
88 /// Specifies a custom path to AAPT2. By default, `bundletool` includes its own
89 /// version of AAPT2
90 pub fn aapt2(&mut self, aapt2: &Path) -> &mut Self {
91 self.aapt2 = Some(aapt2.to_owned());
92 self
93 }
94
95 /// Specifies the path to the deployment keystore used to sign the APKs. This flag is
96 /// optional. If you don't include it, `bundletool` attempts to sign your APKs with a
97 /// debug signing key
98 pub fn ks(&mut self, ks: &Path) -> &mut Self {
99 self.ks = Some(ks.to_owned());
100 self
101 }
102
103 /// Specifies your keystore's password. If you're specifying a password in plain text,
104 /// qualify it with pass:. If you're passing the path to a file that contains the
105 /// password, qualify it with file:. If you specify a keystore using the `--ks` flag
106 /// without specifying `--ks-pass`, `build_apks` prompts you for a password from the
107 /// command line
108 pub fn ks_pass_pass(&mut self, ks_pass_pass: String) -> &mut Self {
109 self.ks_pass_pass = Some(ks_pass_pass);
110 self
111 }
112
113 /// Specifies your keystore's password. If you're specifying a password in plain text,
114 /// qualify it with pass:. If you're passing the path to a file that contains the
115 /// password, qualify it with file:. If you specify a keystore using the `--ks` flag
116 /// without specifying `--ks-pass`, `build_apks` prompts you for a password from the
117 /// command line
118 pub fn ks_pass_file(&mut self, ks_pass_file: &Path) -> &mut Self {
119 self.ks_pass_file = Some(ks_pass_file.to_owned());
120 self
121 }
122
123 /// Specifies the alias of the signing key you want to use
124 pub fn ks_key_alias(&mut self, ks_key_alias: String) -> &mut Self {
125 self.ks_key_alias = Some(ks_key_alias);
126 self
127 }
128
129 /// Specifies the password for the signing key. If you're specifying a password in
130 /// plain text, qualify it with pass:. If you're passing the path to a file that
131 /// contains the password, qualify it with file:.
132 ///
133 /// If this password is identical to the one for the keystore itself, you can omit
134 /// this flag
135 pub fn key_pass_pass(&mut self, key_pass_pass: String) -> &mut Self {
136 self.key_pass_pass = Some(key_pass_pass);
137 self
138 }
139
140 /// Specifies the password for the signing key. If you're specifying a password in
141 /// plain text, qualify it with pass:. If you're passing the path to a file that
142 /// contains the password, qualify it with file:.
143 ///
144 /// If this password is identical to the one for the keystore itself, you can omit
145 /// this flag
146 pub fn key_pass_file(&mut self, key_pass_file: &Path) -> &mut Self {
147 self.key_pass_file = Some(key_pass_file.to_owned());
148 self
149 }
150
151 /// Instructs `build_apks` to build APKs that target the configuration of a connected
152 /// device. If you don't include this flag, `build_apks` generates APKs for all device
153 /// configurations your app supports
154 pub fn connected_device(&mut self, connected_device: bool) -> &mut Self {
155 self.connected_device = connected_device;
156 self
157 }
158
159 /// If you have more than one connected device, use this flag to specify the serial ID
160 /// of the device to which you want to deploy your app
161 pub fn device_id(&mut self, device_id: String) -> &mut Self {
162 self.device_id = Some(device_id);
163 self
164 }
165
166 /// Use this flag to provide a path to a `.json` file that specifies the device
167 /// configuration you want to target. To learn more, go to the section about how to
168 /// [`Create and use device specification JSON files`](https://developer.android.com/studio/command-line/bundletool#create_use_json)
169 pub fn device_spec(&mut self, device_spec: &Path) -> &mut Self {
170 self.device_spec = Some(device_spec.to_owned());
171 self
172 }
173
174 /// Set the mode to universal if you want `build_apks` to build only a single APK that
175 /// includes all of your app's code and resources such that the APK is compatible with
176 /// all device configurations your app supports.
177 ///
178 /// ## Note
179 /// `build_apks` includes only feature modules that specify `<dist:fusing
180 /// dist:include="true"/>` in their manifest in a universal APK. To learn more, read
181 /// about the [`feature module manifest`].
182 ///
183 /// Keep in mind, these APKs are larger than those optimized for a particular device
184 /// configuration. However, they're easier to share with internal testers who, for
185 /// example, want to test your app on multiple device configurations.
186 ///
187 /// [feature module manifest]: https://developer.android.com/guide/playcore/feature-delivery#dynamic_feature_manifest
188 pub fn mode_universal(&mut self, mode_universal: bool) -> &mut Self {
189 self.mode_universal = mode_universal;
190 self
191 }
192
193 /// Use this flag to enable your app bundle for local testing. Local testing allows
194 /// for quick, iterative testing cycles without the need to upload to Google Play
195 /// servers.
196 ///
197 /// For an example of how to test module installation using the `--local-testing`
198 /// flag, see
199 /// [`Locally test module installs`](https://developer.android.com/guide/app-bundle/test/testing-fakesplitinstallmanager)
200 pub fn local_testing(&mut self, local_testing: bool) -> &mut Self {
201 self.local_testing = local_testing;
202 self
203 }
204
205 /// Runs `build_apks` commands to build apks
206 pub fn run(&self) -> Result<PathBuf> {
207 let mut build_apks = bundletool()?;
208 build_apks.arg("build-apks");
209 build_apks.arg("--bundle").arg(&self.bundle);
210 build_apks.arg("--output").arg(&self.output);
211 if self.overwrite {
212 build_apks.arg("--overwrite");
213 }
214 if let Some(aapt2) = &self.aapt2 {
215 build_apks.arg("--aapt2").arg(aapt2);
216 }
217 if let Some(ks) = &self.ks {
218 build_apks.arg("--ks").arg(ks);
219 }
220 if let Some(ks_pass_pass) = &self.ks_pass_pass {
221 build_apks
222 .arg("--ks-pass")
223 .arg(format!("pass:{}", ks_pass_pass));
224 }
225 if let Some(ks_pass_file) = &self.ks_pass_file {
226 build_apks.arg("--ks-pass").arg(format!(
227 "file:{}",
228 ks_pass_file.to_str().expect("Wrong ks_pass_file provided")
229 ));
230 }
231 if let Some(ks_key_alias) = &self.ks_key_alias {
232 build_apks.arg("--ks-key-alias").arg(ks_key_alias);
233 }
234 if let Some(key_pass_pass) = &self.key_pass_pass {
235 build_apks
236 .arg("--key-pass")
237 .arg(format!("pass:{}", key_pass_pass));
238 }
239 if let Some(key_pass_file) = &self.key_pass_file {
240 build_apks.arg("--key-pass").arg(format!(
241 "file:{}",
242 key_pass_file
243 .to_str()
244 .expect("Wrong key_pass_file provided")
245 ));
246 }
247 if self.connected_device {
248 build_apks.arg("--connected-device");
249 }
250 if let Some(device_id) = &self.device_id {
251 build_apks.arg("--device-id").arg(device_id);
252 }
253 if let Some(device_spec) = &self.device_spec {
254 build_apks.arg("--device-spec").arg(device_spec);
255 }
256 if self.mode_universal {
257 build_apks.arg("--mode").arg("universal");
258 }
259 if self.local_testing {
260 build_apks.arg("--local-testing");
261 }
262 build_apks.output_err(true)?;
263 Ok(self.output.clone())
264 }
265}