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}