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}