android_tools/aapt2/
compile.rs

1use super::aapt2_tool;
2use crate::error::*;
3use std::path::{Path, PathBuf};
4
5/// # Compile
6/// `AAPT2` supports compilation of all Android resource types, such as drawables and XML
7/// files. When you invoke `AAPT2` for compilation, you should pass a single resource file
8/// as an input per invocation. `AAPT2` then parses the file and generates an intermediate
9/// binary file with a `.flat` extension.
10///
11/// Although you can pass resource directories containing more than one resource files to
12/// AAPT2 using the `--dir` flag, you do not gain the benefits of incremental resource
13/// compilation when doing so. That is, when passing whole directories, `AAPT2` recompiles
14/// all files in the directory even when only one resource has changed.
15///
16/// The output file types can differ based on the input you provide for compilation.
17/// The files `AAPT2` outputs are not executables and you must later include these binary
18/// files as input in the link phase to generate an APK. However, the generated APK file
19/// is not an executable that you can deploy on an Android device right away, as it does
20/// not contain DEX files (compiled bytecode) and is not signed.
21///
22/// ## Compile syntax
23/// The general syntax for using compile is as follows:
24///
25/// ```sh
26/// `aapt2 compile path-to-input-files [options] -o output-directory/`
27/// ```
28/// ### Note
29/// For resource files, the path to input files must match the following structure:
30/// ```sh
31/// `path/resource-type[-config]/file`
32/// ```
33///
34/// In the following example, `AAPT2` compiles resource files named `values.xml` and
35/// `myImage.png` individually:
36///
37/// ```sh
38/// `aapt2 compile project_root/module_root/src/main/res/values-en/strings.xml -o compiled/`
39/// `aapt2 compile project_root/module_root/src/main/res/drawable/myImage.png -o compiled/`
40/// ```
41///
42/// As shown in the table above, the name of the output file depends on the input file
43/// name and the name of its parent directory (the resource type and configuration).
44/// For the example above with strings.xml as input, aapt2 automatically names the output
45/// file as `values-en_strings.arsc.flat`. On the other hand, the file name for the
46/// compiled drawable file stored in the drawable directory will be
47/// `drawable_img.png.flat`.
48///
49/// ## [Compile options](https://developer.android.com/studio/command-line/aapt2#compile_options)
50#[derive(Clone, Default)]
51pub struct Aapt2Compile {
52    res_path: Option<PathBuf>,
53    compiled_res: PathBuf,
54    res_dir: Option<PathBuf>,
55    res_zip: Option<PathBuf>,
56    output_text_symbols: Option<String>,
57    pseudo_localize: bool,
58    no_crunch: bool,
59    legacy: bool,
60    preserve_visibility_of_styleables: bool,
61    visibility: Option<Visibility>,
62    verbose: bool,
63    trace_folder: Option<PathBuf>,
64    help: bool,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum Visibility {
69    Public,
70    Private,
71    Default,
72}
73
74impl std::fmt::Display for Visibility {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        match *self {
77            Self::Public => write!(f, "public"),
78            Self::Private => write!(f, "private"),
79            Self::Default => write!(f, "default"),
80        }
81    }
82}
83
84impl Aapt2Compile {
85    /// Initialize aapt2 compile then specifies input resource(s) to compile and
86    /// specifies the output path for the compiled resource(s)
87    pub fn new(res_path: &Path, compiled_res: &Path) -> Self {
88        Self {
89            res_path: Some(res_path.to_owned()),
90            compiled_res: compiled_res.to_owned(),
91            ..Default::default()
92        }
93    }
94
95    /// Specifies the directory to scan for resources and specifies the output path for
96    /// the compiled resource(s).
97    ///
98    /// Although you can use this flag to compile multiple resource files with one
99    /// command, it disables the benefits of incremental compilation and thus, should not
100    /// be used for large projects
101    pub fn new_from_res_dir(res_dir: &Path, compiled_res: &Path) -> Self {
102        Self {
103            res_path: None,
104            compiled_res: compiled_res.to_owned(),
105            res_dir: Some(res_dir.to_owned()),
106            ..Default::default()
107        }
108    }
109
110    /// Specifies zip file containing the res directory to scan for resources and
111    /// specifies the output path for the compiled resource(s)
112    pub fn new_from_res_zip(res_zip: &Path, compiled_res: &Path) -> Self {
113        Self {
114            res_path: None,
115            compiled_res: compiled_res.to_owned(),
116            res_dir: None,
117            res_zip: Some(res_zip.to_owned()),
118            ..Default::default()
119        }
120    }
121
122    /// Generates a text file containing the resource symbols in the specified file
123    pub fn output_text_symbols(&mut self, output_text_symbols: String) -> &mut Self {
124        self.output_text_symbols = Some(output_text_symbols);
125        self
126    }
127
128    /// Generates pseudo-localized versions of default strings, such as en-XA and en-XB
129    pub fn pseudo_localize(&mut self, pseudo_localize: bool) -> &mut Self {
130        self.pseudo_localize = pseudo_localize;
131        self
132    }
133
134    /// Disables PNG processing.
135    ///
136    /// Use this option if you have already processed the PNG files, or if you are
137    /// creating debug builds that do not require file size reduction. Enabling this
138    /// option results in a faster execution, but increases the output file size
139    pub fn no_crunch(&mut self, no_crunch: bool) -> &mut Self {
140        self.no_crunch = no_crunch;
141        self
142    }
143
144    /// Treats errors that are permissible when using earlier versions of `AAPT` as
145    /// warnings.
146    ///
147    /// This flag should be used for unexpected compile time errors.
148    /// To resolve known behavior changes that you might get while using `AAPT2`, read
149    /// [Behavior changes in AAPT2.](https://developer.android.com/studio/command-line/aapt2#aapt2_changes)
150    pub fn legacy(&mut self, legacy: bool) -> &mut Self {
151        self.legacy = legacy;
152        self
153    }
154
155    /// If specified, apply the same visibility rules for styleables as are used for all
156    /// other resources. Otherwise, all stylesables will be made public
157    pub fn preserve_visibility_of_styleables(
158        &mut self,
159        preserve_visibility_of_styleables: bool,
160    ) -> &mut Self {
161        self.preserve_visibility_of_styleables = preserve_visibility_of_styleables;
162        self
163    }
164
165    /// Sets the visibility of the compiled resources to the specified level.
166    /// Accepted levels: public, private, default
167    pub fn visibility(&mut self, visibility: Visibility) -> &mut Self {
168        self.visibility = Some(visibility);
169        self
170    }
171
172    /// Enable verbose logging
173    pub fn verbose(&mut self, verbose: bool) -> &mut Self {
174        self.verbose = verbose;
175        self
176    }
177
178    /// Generate systrace json trace fragment to specified folder
179    pub fn trace_folder(&mut self, trace_folder: &Path) -> &mut Self {
180        self.trace_folder = Some(trace_folder.to_owned());
181        self
182    }
183
184    /// Displays this help menu
185    pub fn help(&mut self, help: bool) -> &mut Self {
186        self.help = help;
187        self
188    }
189
190    /// Executes aapt2 compile with arguments
191    pub fn run(&self) -> Result<PathBuf> {
192        let mut aapt2 = aapt2_tool()?;
193        aapt2.arg("compile");
194        if let Some(res_path) = &self.res_path {
195            walkdir::WalkDir::new(res_path)
196                .into_iter()
197                .filter_map(|e| e.ok())
198                .for_each(|input| {
199                    if input.file_type().is_file() {
200                        println!("{}", input.path().to_string_lossy());
201                        aapt2.arg(input.path());
202                    }
203                });
204        }
205        aapt2.arg("-o");
206        aapt2.arg(&self.compiled_res);
207        if let Some(res_dir) = &self.res_dir {
208            aapt2.arg("--dir").arg(res_dir);
209        }
210        if let Some(visibility) = &self.visibility {
211            aapt2.arg("--visibility").arg(visibility.to_string());
212        }
213        if let Some(res_zip) = &self.res_zip {
214            aapt2.arg("--zip").arg(res_zip);
215        }
216        if let Some(output_text_symbols) = &self.output_text_symbols {
217            aapt2.arg("--output-text-symbols").arg(output_text_symbols);
218        }
219        if self.pseudo_localize {
220            aapt2.arg("--pseudo-localize");
221        }
222        if self.no_crunch {
223            aapt2.arg("--no-crunch");
224        }
225        if self.legacy {
226            aapt2.arg("--legacy");
227        }
228        if self.preserve_visibility_of_styleables {
229            aapt2.arg("--preserve-visibility-of-styleables");
230        }
231        if let Some(trace_folder) = &self.trace_folder {
232            aapt2.arg("--trace-folder").arg(trace_folder);
233        }
234        if self.verbose {
235            aapt2.arg("-v");
236        }
237        if self.help {
238            aapt2.arg("-h");
239        }
240        aapt2.output_err(true)?;
241        Ok(self.compiled_res.clone())
242    }
243}