android_tools_rs/aapt2/compile.rs
1use crate::error::*;
2use std::path::{Path, PathBuf};
3use std::process::Command;
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 compiled
46/// drawable file stored in the drawable directory will be drawable_img.png.flat.
47///
48/// ## [Compile options](https://developer.android.com/studio/command-line/aapt2#compile_options)
49#[derive(Clone, Default)]
50pub struct Aapt2Compile {
51 res_path: Option<PathBuf>,
52 compiled_res: PathBuf,
53 res_dir: Option<PathBuf>,
54 res_zip: Option<PathBuf>,
55 output_text_symbols: Option<String>,
56 pseudo_localize: bool,
57 no_crunch: bool,
58 legacy: bool,
59 preserve_visibility_of_styleables: bool,
60 visibility: Option<Visibility>,
61 verbose: bool,
62 trace_folder: Option<PathBuf>,
63 help: bool,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum Visibility {
68 Public,
69 Private,
70 Default,
71}
72
73impl std::fmt::Display for Visibility {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match *self {
76 Self::Public => write!(f, "public"),
77 Self::Private => write!(f, "private"),
78 Self::Default => write!(f, "default"),
79 }
80 }
81}
82
83impl Aapt2Compile {
84 /// Initialize struct Aapt2Compile then specifies input resource(s) to compile and
85 /// specifies the output path for the compiled resource(s)
86 pub fn new(res_path: &Path, compiled_res: &Path) -> Self {
87 Self {
88 res_path: Some(res_path.to_owned()),
89 compiled_res: compiled_res.to_owned(),
90 ..Default::default()
91 }
92 }
93
94 /// Specifies the directory to scan for resources and specifies the output path for
95 /// the compiled resource(s).
96 ///
97 /// Although you can use this flag to compile multiple resource files with one
98 /// command, it disables the benefits of incremental compilation and thus, should not
99 /// be used for large projects
100 pub fn new_from_res_dir(res_dir: &Path, compiled_res: &Path) -> Self {
101 Self {
102 res_path: None,
103 compiled_res: compiled_res.to_owned(),
104 res_dir: Some(res_dir.to_owned()),
105 ..Default::default()
106 }
107 }
108
109 /// Specifies zip file containing the res directory to scan for resources and
110 /// specifies the output path for the compiled resource(s)
111 pub fn new_from_res_zip(res_zip: &Path, compiled_res: &Path) -> Self {
112 Self {
113 res_path: None,
114 compiled_res: compiled_res.to_owned(),
115 res_dir: None,
116 res_zip: Some(res_zip.to_owned()),
117 ..Default::default()
118 }
119 }
120
121 /// Generates a text file containing the resource symbols in the specified file
122 pub fn output_text_symbols(&mut self, output_text_symbols: String) -> &mut Self {
123 self.output_text_symbols = Some(output_text_symbols);
124 self
125 }
126
127 /// Generates pseudo-localized versions of default strings, such as en-XA and en-XB
128 pub fn pseudo_localize(&mut self, pseudo_localize: bool) -> &mut Self {
129 self.pseudo_localize = pseudo_localize;
130 self
131 }
132
133 /// Disables PNG processing.
134 ///
135 /// Use this option if you have already processed the PNG files, or if you are
136 /// creating debug builds that do not require file size reduction. Enabling this
137 /// option results in a faster execution, but increases the output file size
138 pub fn no_crunch(&mut self, no_crunch: bool) -> &mut Self {
139 self.no_crunch = no_crunch;
140 self
141 }
142
143 /// Treats errors that are permissible when using earlier versions of AAPT as
144 /// warnings.
145 ///
146 /// This flag should be used for unexpected compile time errors.
147 /// To resolve known behavior changes that you might get while using AAPT2, read
148 /// [Behavior changes in AAPT2.](https://developer.android.com/studio/command-line/aapt2#aapt2_changes)
149 pub fn legacy(&mut self, legacy: bool) -> &mut Self {
150 self.legacy = legacy;
151 self
152 }
153
154 /// If specified, apply the same visibility rules for styleables as are used for all
155 /// other resources. Otherwise, all stylesables will be made public
156 pub fn preserve_visibility_of_styleables(
157 &mut self,
158 preserve_visibility_of_styleables: bool,
159 ) -> &mut Self {
160 self.preserve_visibility_of_styleables = preserve_visibility_of_styleables;
161 self
162 }
163
164 /// Sets the visibility of the compiled resources to the specified level.
165 /// Accepted levels: public, private, default
166 pub fn visibility(&mut self, visibility: Visibility) -> &mut Self {
167 self.visibility = Some(visibility);
168 self
169 }
170
171 /// Enable verbose logging
172 pub fn verbose(&mut self, verbose: bool) -> &mut Self {
173 self.verbose = verbose;
174 self
175 }
176
177 /// Generate systrace json trace fragment to specified folder
178 pub fn trace_folder(&mut self, trace_folder: &Path) -> &mut Self {
179 self.trace_folder = Some(trace_folder.to_owned());
180 self
181 }
182
183 /// Displays this help menu
184 pub fn help(&mut self, help: bool) -> &mut Self {
185 self.help = help;
186 self
187 }
188
189 /// Opens the command line and launches aapt2 compile with arguments
190 pub fn run(&self) -> Result<PathBuf> {
191 let mut aapt2 = Command::new("aapt2");
192 aapt2.arg("compile");
193 if let Some(res_path) = &self.res_path {
194 walkdir::WalkDir::new(res_path)
195 .into_iter()
196 .filter_map(|e| e.ok())
197 .for_each(|input| {
198 if input.file_type().is_file() {
199 println!("{}", input.path().to_string_lossy());
200 aapt2.arg(input.path());
201 }
202 });
203 }
204 aapt2.arg("-o");
205 aapt2.arg(&self.compiled_res);
206 if let Some(res_dir) = &self.res_dir {
207 aapt2.arg("--dir").arg(res_dir);
208 }
209 if let Some(visibility) = &self.visibility {
210 aapt2.arg("--visibility").arg(visibility.to_string());
211 }
212 if let Some(res_zip) = &self.res_zip {
213 aapt2.arg("--zip").arg(res_zip);
214 }
215 if let Some(output_text_symbols) = &self.output_text_symbols {
216 aapt2.arg("--output-text-symbols").arg(output_text_symbols);
217 }
218 if self.pseudo_localize {
219 aapt2.arg("--pseudo-localize");
220 }
221 if self.no_crunch {
222 aapt2.arg("--no-crunch");
223 }
224 if self.legacy {
225 aapt2.arg("--legacy");
226 }
227 if self.preserve_visibility_of_styleables {
228 aapt2.arg("--preserve-visibility-of-styleables");
229 }
230 if let Some(trace_folder) = &self.trace_folder {
231 aapt2.arg("--trace-folder").arg(trace_folder);
232 }
233 if self.verbose {
234 aapt2.arg("-v");
235 }
236 if self.help {
237 aapt2.arg("-h");
238 }
239 aapt2.output_err(true)?;
240 Ok(self.compiled_res.clone())
241 }
242}