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}