creator_tools/tools/aapt2/
link.rs

1use std::{
2    path::{Path, PathBuf},
3    process::Command,
4};
5
6/// ## Link
7/// In the link phase, AAPT2 merges all the intermediate files generated from the
8/// compilation phase such as resource tables, binary XML files, and processed
9/// PNG files and packages them into a single APK. Additionally, other auxiliary
10/// files like `R.java` and ProGuard rules files can be generated during this phase.
11/// However, the generated APK does not contain DEX bytecode and is unsigned.
12/// That is, you can't deploy this APK to a device. If you're not using the Android
13/// Gradle Plugin to [`build your app from the command line`], you can use other command
14/// line tools, such as [`d8`] to compile Java bytecode into DEX bytecode and
15/// [`apksigner`] to sign your APK.
16///
17/// ## Link syntax
18/// The general syntax for using link is as follows:
19///
20/// ```sh
21/// aapt2 link path-to-input-files [options] -o
22/// outputdirectory/outputfilename.apk --manifest AndroidManifest.xml
23/// ```
24///
25/// In the following example, AAPT2 merges the two intermediate files -
26/// `drawable_Image.flat` and `values_values.arsc.flat`, and the `AndroidManifest.xml`
27/// file. AAPT2 links the result against `android.jar` file which holds the resources
28/// defined in the android package:
29///
30/// ```sh
31///  aapt2 link -o output.apk
32///  -I android_sdk/platforms/android_version/android.jar
33///     compiled/res/values_values.arsc.flat
34///     compiled/res/drawable_Image.flat --manifest /path/to/AndroidManifest.xml -v
35/// ```
36///
37/// [`d8`]: https://developer.android.com/studio/command-line/d8
38/// [`apksigner`]: https://developer.android.com/studio/command-line/apksigner
39/// [`build your app from the command line`]: https://developer.android.com/studio/build/building-cmdline
40#[derive(Debug, PartialEq)]
41pub struct Aapt2Link {
42    inputs: Vec<PathBuf>,
43    /// Specifies the output path for the linked resource APK.
44    ///
45    /// This is a required flag because you must specify the path for the output APK that
46    /// can hold the linked resources.
47    o: PathBuf,
48    /// Specifies the path to the Android manifest file to build.
49    ///
50    /// This is a required flag because the manifest file encloses essential information
51    /// about your app like package name and application ID.
52    manifest: PathBuf,
53    /// Provides the path to the platform's android.jar or other APKs like
54    /// framework-res.apk  which might be useful while building features. This flag is
55    /// required if you are using attributes with android namespace (for example,
56    /// android:id) in your resource files.
57    i: Option<PathBuf>,
58    /// Specifies an assets directory to be included in the APK.
59    ///
60    /// You can use this directory to store original unprocessed files. To learn more,
61    /// read [`Accessing original`] files.
62    ///
63    /// [`Accessing original`]: https://developer.android.com/guide/topics/resources/providing-resources#OriginalFiles
64    a: Option<PathBuf>,
65    /// Pass individual .flat file to link, using `overlay` semantics without using the
66    /// `<add-resource>` tag.
67    ///
68    /// When you a provide a resource file that overlays (extends or modifies) an existing
69    /// file, the last conflicting resource given is used.
70    r: Option<PathBuf>,
71    /// Specifies the package ID to use for your app.
72    ///
73    /// The package ID that you specify must be greater than or equal to 0x7f unless used
74    /// in combination with `--allow-reserved-package-id`.
75    package_id: Option<String>,
76    /// Allows the use of a reserved package ID.
77    ///
78    /// Reserved package IDs are IDs that are normally assigned to shared libraries and
79    /// are in the range from 0x02 to 0x7e inclusive. By using
80    /// --allow-reserved-package-id, you can assign IDs that fall in the range of reserved
81    /// package IDs.
82    ///
83    /// This should only be used for packages with a min-sdk version of 26 or lower.
84    allow_reserved_package_id: bool,
85    /// Specifies the directory in which to generate R.java.
86    java_directory: Option<PathBuf>,
87    /// Generates output file for ProGuard rules.
88    proguard_options: Option<PathBuf>,
89    /// Generates output file for ProGuard rules for the main dex.
90    proguard_conditional_keep_rules: bool,
91    /// Disables automatic style and layout SDK versioning.
92    no_auto_version: bool,
93    /// Disables automatic versioning of vector drawables. Use this only when building
94    /// your APK with the Vector Drawable Library.
95    no_version_vectors: bool,
96    /// Disables automatic versioning of transition resources. Use this only when building
97    /// your APK with Transition Support library.
98    no_version_transitions: bool,
99    /// Disables automatic de-duplication of resources with identical values across
100    /// compatible configurations.
101    no_resource_deduping: bool,
102    /// Enables encoding of sparse entries using a binary search tree. This is useful for
103    /// optimization of APK size, but at the cost of resource retrieval performance.
104    enable_sparse_encoding: bool,
105    /// Requires localization of strings marked 'suggested'.
106    z: bool,
107    /// Provides a list of configurations separated by commas.
108    ///
109    /// For example, if you have dependencies on the support library (which contains
110    /// translations for multiple languages), you can filter resources just for the given
111    /// language configuration, like English or Spanish.
112    ///
113    /// You must define the language configuration by a two-letter ISO 639-1 language
114    /// code, optionally followed by a two letter ISO 3166-1-alpha-2 region code preceded
115    /// by lowercase 'r' (for example, en-rUS).
116    config: Vec<String>,
117    /// Allows AAPT2 to select the closest matching density and strip out all others.
118    ///
119    /// There are several pixel density qualifiers available to use in your app, such as
120    /// ldpi, hdpi, and xhdpi. When you specify a preferred density, AAPT2 selects and
121    /// stores the closest matching density in the resource table and removes all others.
122    preferred_density: Option<i32>,
123    /// Outputs the APK contents to a directory specified by -o.
124    ///
125    /// If you get any errors using this flag, you can resolve them by upgrading to
126    /// [`Android SDK Build Tools 28.0.0 or higher`].
127    ///
128    /// [`Android SDK Build Tools 28.0.0 or higher`]: https://developer.android.com/studio/releases/build-tools
129    output_to_dir: bool,
130    /// Sets the default minimum SDK version to use for `AndroidManifest.xml`.
131    min_sdk_version: Option<i32>,
132    /// Sets the default target SDK version to use for `AndroidManifest.xml`.
133    target_sdk_version: Option<i32>,
134    /// Specifies the version code (integer) to inject into the AndroidManifest.xml if
135    /// none is present.
136    version_code: Option<String>,
137    /// Specifies the version name to inject into the AndroidManifest.xml if none is
138    /// present.
139    compile_sdk_version_name: Option<String>,
140    /// Generates compiled resources in Protobuf format.
141    /// Suitable as input to the [`bundle tool`] for generating an Android App Bundle.
142    ///
143    /// [`bundle tool`]: https://developer.android.com/studio/build/building-cmdline#bundletool-build
144    proto_format: bool,
145    /// Generates `R.java` with non-final resource IDs (references to the IDs from app’s
146    /// code will not get inlined during kotlinc/javac compilation).
147    non_final_ids: bool,
148    /// Emits a file at the given path with a list of names of resource types and their ID
149    /// mappings. It is suitable to use with --stable-ids.
150    emit_ids: Option<PathBuf>,
151    /// Consumes the file generated with --emit-ids containing the list of names of
152    /// resource types and their assigned IDs.
153    ///
154    /// This option allows assigned IDs to remain stable even when you delete or add new
155    /// resources while linking
156    stable_ids: Option<PathBuf>,
157    /// Specifies custom Java package under which to generate R.java.
158    custom_package: Option<PathBuf>,
159    /// Generates the same R.java file but with different package names.
160    extra_packages: Option<PathBuf>,
161    /// Adds a JavaDoc annotation to all generated Java classes.
162    add_javadoc_annotation: Option<String>,
163    /// Generates a text file containing the resource symbols of the R class in the
164    /// specified file.
165    ///
166    /// You must specify the path to the output file.
167    output_text_symbols: Option<PathBuf>,
168    /// Allows the addition of new resources in overlays without using the <add-resource>
169    /// tag.
170    auto_add_overlay: bool,
171    /// Renames the package in AndroidManifest.xml.
172    rename_manifest_package: Option<String>,
173    /// Changes the name of the target package for [`instrumentation`].
174    ///
175    /// It should be used in conjunction with --rename-manifest-package.
176    ///
177    /// [`instrumentation`]: https://developer.android.com/reference/android/app/Instrumentation
178    rename_instrumentation_target_package: Option<String>,
179    /// Specifies the extensions of files that you do not want to compress.
180    extensions: Vec<String>,
181    /// Splits resources based on a set of configurations to generate a different version
182    /// of the APK.
183    ///
184    /// You must specify the path to the output APK along with the set of configurations.
185    split: Option<PathBuf>,
186    /// Enables increased verbosity of the output.
187    v: bool,
188}
189
190impl Aapt2Link {
191    pub fn new(inputs: &[PathBuf], o: &Path, manifest: &Path) -> Self {
192        Self {
193            inputs: inputs.to_vec(),
194            o: o.to_owned(),
195            manifest: manifest.to_owned(),
196            i: None,
197            a: None,
198            r: None,
199            package_id: None,
200            allow_reserved_package_id: false,
201            java_directory: None,
202            proguard_options: None,
203            proguard_conditional_keep_rules: false,
204            no_auto_version: false,
205            no_version_vectors: false,
206            no_version_transitions: false,
207            no_resource_deduping: false,
208            enable_sparse_encoding: false,
209            z: false,
210            config: Vec::new(),
211            preferred_density: None,
212            output_to_dir: false,
213            min_sdk_version: None,
214            target_sdk_version: None,
215            version_code: None,
216            compile_sdk_version_name: None,
217            proto_format: false,
218            non_final_ids: false,
219            emit_ids: None,
220            stable_ids: None,
221            custom_package: None,
222            extra_packages: None,
223            add_javadoc_annotation: None,
224            output_text_symbols: None,
225            auto_add_overlay: false,
226            rename_manifest_package: None,
227            rename_instrumentation_target_package: None,
228            extensions: Vec::new(),
229            split: None,
230            v: false,
231        }
232    }
233
234    pub fn i(&mut self, i: PathBuf) -> &mut Self {
235        self.i = Some(i);
236        self
237    }
238
239    pub fn a(&mut self, a: PathBuf) -> &mut Self {
240        self.a = Some(a);
241        self
242    }
243
244    pub fn r(&mut self, r: PathBuf) -> &mut Self {
245        self.r = Some(r);
246        self
247    }
248
249    pub fn package_id(&mut self, package_id: String) -> &mut Self {
250        self.package_id = Some(package_id);
251        self
252    }
253
254    pub fn allow_reserved_package_id(&mut self, allow_reserved_package_id: bool) -> &mut Self {
255        self.allow_reserved_package_id = allow_reserved_package_id;
256        self
257    }
258
259    pub fn java_directory(&mut self, java_directory: PathBuf) -> &mut Self {
260        self.java_directory = Some(java_directory);
261        self
262    }
263
264    pub fn proguard_options(&mut self, proguard_options: PathBuf) -> &mut Self {
265        self.proguard_options = Some(proguard_options);
266        self
267    }
268
269    pub fn proguard_conditional_keep_rules(
270        &mut self,
271        proguard_conditional_keep_rules: bool,
272    ) -> &mut Self {
273        self.proguard_conditional_keep_rules = proguard_conditional_keep_rules;
274        self
275    }
276
277    pub fn no_auto_version(&mut self, no_auto_version: bool) -> &mut Self {
278        self.no_auto_version = no_auto_version;
279        self
280    }
281
282    pub fn no_version_vectors(&mut self, no_version_vectors: bool) -> &mut Self {
283        self.no_version_vectors = no_version_vectors;
284        self
285    }
286
287    pub fn no_version_transitions(&mut self, no_version_transitions: bool) -> &mut Self {
288        self.no_version_transitions = no_version_transitions;
289        self
290    }
291
292    pub fn no_resource_deduping(&mut self, no_resource_deduping: bool) -> &mut Self {
293        self.no_resource_deduping = no_resource_deduping;
294        self
295    }
296
297    pub fn enable_sparse_encoding(&mut self, enable_sparse_encoding: bool) -> &mut Self {
298        self.enable_sparse_encoding = enable_sparse_encoding;
299        self
300    }
301
302    pub fn z(&mut self, z: bool) -> &mut Self {
303        self.z = z;
304        self
305    }
306
307    pub fn config(&mut self, config: String) -> &mut Self {
308        self.config.push(config);
309        self
310    }
311
312    pub fn preferred_density(&mut self, preferred_density: i32) -> &mut Self {
313        self.preferred_density = Some(preferred_density);
314        self
315    }
316
317    pub fn output_to_dir(&mut self, output_to_dir: bool) -> &mut Self {
318        self.output_to_dir = output_to_dir;
319        self
320    }
321
322    pub fn min_sdk_version(&mut self, min_sdk_version: i32) -> &mut Self {
323        self.min_sdk_version = Some(min_sdk_version);
324        self
325    }
326
327    pub fn target_sdk_version(&mut self, target_sdk_version: i32) -> &mut Self {
328        self.target_sdk_version = Some(target_sdk_version);
329        self
330    }
331
332    pub fn version_code(&mut self, version_code: String) -> &mut Self {
333        self.version_code = Some(version_code);
334        self
335    }
336
337    pub fn compile_sdk_version_name(&mut self, compile_sdk_version_name: String) -> &mut Self {
338        self.compile_sdk_version_name = Some(compile_sdk_version_name);
339        self
340    }
341
342    pub fn proto_format(&mut self, proto_format: bool) -> &mut Self {
343        self.proto_format = proto_format;
344        self
345    }
346
347    pub fn non_final_ids(&mut self, non_final_ids: bool) -> &mut Self {
348        self.non_final_ids = non_final_ids;
349        self
350    }
351
352    pub fn emit_ids(&mut self, emit_ids: PathBuf) -> &mut Self {
353        self.emit_ids = Some(emit_ids);
354        self
355    }
356
357    pub fn stable_ids(&mut self, stable_ids: PathBuf) -> &mut Self {
358        self.stable_ids = Some(stable_ids);
359        self
360    }
361
362    pub fn custom_package(&mut self, custom_package: PathBuf) -> &mut Self {
363        self.custom_package = Some(custom_package);
364        self
365    }
366
367    pub fn extra_packages(&mut self, extra_packages: PathBuf) -> &mut Self {
368        self.extra_packages = Some(extra_packages);
369        self
370    }
371
372    pub fn add_javadoc_annotation(&mut self, add_javadoc_annotation: String) -> &mut Self {
373        self.add_javadoc_annotation = Some(add_javadoc_annotation);
374        self
375    }
376
377    pub fn output_text_symbols(&mut self, output_text_symbols: PathBuf) -> &mut Self {
378        self.output_text_symbols = Some(output_text_symbols);
379        self
380    }
381
382    pub fn auto_add_overlay(&mut self, auto_add_overlay: bool) -> &mut Self {
383        self.auto_add_overlay = auto_add_overlay;
384        self
385    }
386
387    pub fn rename_manifest_package(&mut self, rename_manifest_package: String) -> &mut Self {
388        self.rename_manifest_package = Some(rename_manifest_package);
389        self
390    }
391
392    pub fn rename_instrumentation_target_package(
393        &mut self,
394        rename_instrumentation_target_package: String,
395    ) -> &mut Self {
396        self.rename_instrumentation_target_package = Some(rename_instrumentation_target_package);
397        self
398    }
399
400    pub fn extension(&mut self, extension: String) -> &mut Self {
401        self.extensions.push(extension);
402        self
403    }
404
405    pub fn split(&mut self, split: PathBuf) -> &mut Self {
406        self.split = Some(split);
407        self
408    }
409
410    pub fn v(&mut self, v: bool) -> &mut Self {
411        self.v = v;
412        self
413    }
414
415    pub fn run(&self) {
416        let mut aapt2 = Command::new("aapt2");
417        aapt2.arg("compile");
418        self.inputs.iter().for_each(|input| {
419            aapt2.arg(input);
420        });
421        aapt2.arg("-o").arg(&self.o);
422        aapt2.arg("--manifest").arg(&self.manifest);
423        if let Some(i) = &self.i {
424            aapt2.arg("-I").arg(i);
425        }
426        if let Some(a) = &self.a {
427            aapt2.arg("-A").arg(a);
428        }
429        if let Some(r) = &self.r {
430            aapt2.arg("-R").arg(r);
431        }
432        if let Some(package_id) = &self.package_id {
433            aapt2.arg("--package-id").arg(package_id);
434        }
435        if self.allow_reserved_package_id {
436            aapt2.arg("--allow-reserved-package-id");
437        }
438        if let Some(java_directory) = &self.java_directory {
439            aapt2.arg("--java").arg(java_directory);
440        }
441        if let Some(proguard_options) = &self.proguard_options {
442            aapt2.arg("--proguard").arg(proguard_options);
443        }
444        if self.proguard_conditional_keep_rules {
445            aapt2.arg("--proguard-conditional-keep-rules");
446        }
447        if self.no_auto_version {
448            aapt2.arg("--no-auto-version");
449        }
450        if self.no_version_vectors {
451            aapt2.arg("--no-version-vectors");
452        }
453        if self.no_version_transitions {
454            aapt2.arg("--no-version-transitions");
455        }
456        if self.no_resource_deduping {
457            aapt2.arg("--no-resource-deduping");
458        }
459        if self.enable_sparse_encoding {
460            aapt2.arg("--enable-sparse-encoding");
461        }
462        if self.z {
463            aapt2.arg("-z");
464        }
465        if !self.config.is_empty() {
466            aapt2.arg("-c").arg(self.config.join(","));
467        }
468        if let Some(preferred_density) = self.preferred_density {
469            aapt2
470                .arg("--preferred-density")
471                .arg(preferred_density.to_string());
472        }
473        if self.output_to_dir {
474            aapt2.arg("--output-to-dir");
475        }
476        if let Some(min_sdk_version) = self.min_sdk_version {
477            aapt2
478                .arg("--min-sdk-version")
479                .arg(min_sdk_version.to_string());
480        }
481        if let Some(target_sdk_version) = self.target_sdk_version {
482            aapt2
483                .arg("--target-sdk-version")
484                .arg(target_sdk_version.to_string());
485        }
486        if let Some(version_code) = &self.version_code {
487            aapt2.arg("--version-code").arg(version_code);
488        }
489        if let Some(compile_sdk_version_name) = &self.compile_sdk_version_name {
490            aapt2
491                .arg("--compile-sdk-version-name")
492                .arg(compile_sdk_version_name);
493        }
494        if self.proto_format {
495            aapt2.arg("--proto-format");
496        }
497        if self.non_final_ids {
498            aapt2.arg("--non-final-ids");
499        }
500        if let Some(emit_ids) = &self.emit_ids {
501            aapt2.arg("--emit-ids").arg(emit_ids);
502        }
503        if let Some(stable_ids) = &self.stable_ids {
504            aapt2.arg("--stable-ids").arg(stable_ids);
505        }
506        if let Some(custom_package) = &self.custom_package {
507            aapt2.arg("--custom-package").arg(custom_package);
508        }
509        if let Some(extra_packages) = &self.extra_packages {
510            aapt2.arg("--extra-packages").arg(extra_packages);
511        }
512        if let Some(add_javadoc_annotation) = &self.add_javadoc_annotation {
513            aapt2
514                .arg("--add-javadoc-annotation")
515                .arg(add_javadoc_annotation);
516        }
517        if let Some(output_text_symbols) = &self.output_text_symbols {
518            aapt2.arg("--output-text-symbols").arg(output_text_symbols);
519        }
520        if self.auto_add_overlay {
521            aapt2.arg("--auto-add-overlay");
522        }
523        if let Some(rename_manifest_package) = &self.rename_manifest_package {
524            aapt2
525                .arg("--rename-manifest-package")
526                .arg(rename_manifest_package);
527        }
528        if let Some(rename_instrumentation_target_package) =
529            &self.rename_instrumentation_target_package
530        {
531            aapt2
532                .arg("--rename-instrumentation-target-package")
533                .arg(rename_instrumentation_target_package);
534        }
535        self.extensions.iter().for_each(|extension| {
536            aapt2.arg("-0").arg(extension);
537        });
538        if let Some(split) = &self.split {
539            aapt2.arg("--split").arg(split);
540        }
541        if self.v {
542            aapt2.arg("-v");
543        }
544        aapt2.output().expect("failed to execute process"); // TODO: Fix this expect
545    }
546}
547
548#[cfg(test)]
549mod tests {
550    use super::*;
551
552    #[test]
553    fn builder_test() {
554        let aapt2 = Aapt2Link::new(
555            &[Path::new("bla/bla/bla").to_owned()],
556            &Path::new("bla/bla/bla"),
557            &Path::new("bla/bla/bla"),
558        );
559        aapt2.run();
560    }
561}