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}