android_build/
java_build.rs

1//! Builder for customizing and invoking a `javac` command.
2
3use std::path::PathBuf;
4use std::ffi::{OsStr, OsString};
5use std::process::{Command, ExitStatus};
6use crate::env_paths::{self, PathExt};
7
8/// A builder for a `javac` command that can be invoked.
9///
10/// If you need to customize the `javac` command beyond what is provided here,
11/// you can use the [`JavaBuild::command()`] method to get a [`Command`]
12/// that can be further customized with additional arguments.
13///
14/// Documentation on `javac` options are based on
15/// <https://dev.java/learn/jvm/tools/core/javac/>.
16#[derive(Clone, Debug, Default)]
17pub struct JavaBuild {
18    /// Override the default `JAVA_HOME` path.
19    /// Otherwise, the default path is found using the `JAVA_HOME` env var.
20    java_home: Option<PathBuf>,
21    /// Debug info to include in the output ("-g" flag).
22    debug_info: Option<DebugInfo>,
23    /// If `true`, all warnings are disabled.
24    nowarn: bool,
25    /// Enable verbose output.
26    verbose: bool,
27    /// If `true`, warnings are treated as compilation errors.
28    warnings_as_errors: bool,
29    /// If `true`, show full descriptions of all places where
30    /// deprecated members/classes are used or overridden.
31    /// If `false`, is to show only a summary on a per-source file basis).
32    deprecation: bool,
33    /// If `true`, enable preview language features.
34    enable_preview_features: bool,
35    /// Specify where to find user class files and annotation processors.
36    /// If not provided, the current directory will be used.
37    class_paths: Vec<OsString>,
38    /// Specify where to find input source files.
39    /// If not specified, `class_paths` will be searched for source files.
40    source_paths: Vec<OsString>,
41    /// Override the location of bootstrap class files.
42    boot_class_paths: Vec<OsString>,
43    /// Override the location of installed extensions.
44    extension_dirs: Vec<OsString>,
45    /// Override the location of endorsed standards path.
46    endorsed_dirs: Vec<OsString>,
47    /// Specify names of the annotation processors to run.
48    /// Setting this will bypass the default discovery process.
49    annotation_processors: Vec<OsString>,
50    /// Specify where to find annotation processors.
51    /// If not provided, the `class_paths` will be searched.
52    annotation_processor_paths: Vec<OsString>,
53    /// Enable generation of metadata on method parameters
54    /// such that the reflection API can be used to retrieve parameter info.
55    method_paramater_metadata: bool,
56    /// Specify where to place generated class files.
57    /// If not provided, class files will be placed
58    /// in the same directory as the source files.
59    #[doc(alias = "-d")]
60    classes_out_dir: Option<OsString>,
61    /// Specify where to place generated source files.
62    #[doc(alias = "-s")]
63    sources_out_dir: Option<OsString>,
64    /// Specify where to place generated native header files.
65    #[doc(alias = "-h")]
66    headers_out_dir: Option<OsString>,
67    /// Pass an option to an annotation processor.
68    #[doc(alias = "-A")]
69    annotation_parameters: Vec<(String, String)>,
70    /// Paths to the java source files to be compiled.
71    files: Vec<OsString>,
72}
73
74/// Debug information to include in the output of a `javac` build.
75///
76/// The default value for this struct is for everything to be `true`,
77/// meaning all debug information is included.
78/// This is only relevant *if* you set the `debug_info` field in [`JavaBuild`].
79#[derive(Clone, Debug)]
80pub struct DebugInfo {
81    pub line_numbers: bool,
82    pub variables: bool,
83    pub source_files: bool,
84}
85impl Default for DebugInfo {
86    fn default() -> Self {
87        Self {
88            line_numbers: true,
89            variables: true,
90            source_files: true,
91        }
92    }
93}
94impl DebugInfo {
95    fn add_as_args_to<'c>(&self, cmd: &'c mut Command) -> &'c mut Command {
96        if self.line_numbers {
97            cmd.arg("-g:lines");
98        }
99        if self.variables {
100            cmd.arg("-g:vars");
101        }
102        if self.source_files {
103            cmd.arg("-g:source");
104        }
105        if !self.line_numbers && !self.variables && !self.source_files {
106            cmd.arg("-g:none");
107        }
108        cmd
109    }
110}
111
112impl JavaBuild {
113    /// Creates a new `JavaBuild` instance with default values,
114    /// which can be further customized using the builder methods.
115    pub fn new() -> Self {
116        Default::default()
117    }
118
119    /// Executes the `javac` command based on this `JavaBuild` instance.
120    pub fn compile(&self) -> std::io::Result<ExitStatus> {
121        self.command()?.status()
122    }
123
124    /// Returns a [`Command`] based on this `JavaBuild` instance
125    /// that can be inspected or customized before being executed.
126    pub fn command(&self) -> std::io::Result<Command> {
127        let jh_clone = self.java_home.clone();
128        let java_home = jh_clone
129            .and_then(PathExt::path_if_exists)
130            .or_else(env_paths::java_home)
131            .ok_or_else(|| std::io::Error::other(
132                "JAVA_HOME not provided, and could not be auto-discovered."
133            ))?;
134
135        let mut cmd = Command::new(java_home.join("bin").join("javac"));
136        if let Some(d) = self.debug_info.as_ref() {
137            d.add_as_args_to(&mut cmd);
138        }
139
140        self.class_paths     .iter().for_each(|p| { cmd.arg("-cp").arg(p); });
141        self.source_paths    .iter().for_each(|p| { cmd.arg("-sourcepath").arg(p); });
142        self.boot_class_paths.iter().for_each(|p| { cmd.arg("-bootclasspath").arg(p); });
143        self.extension_dirs  .iter().for_each(|p| { cmd.arg("-extdirs").arg(p); });
144
145        let processors = self.annotation_processors.join(OsStr::new(","));
146        if processors.len() != 0 {
147            cmd.arg("-processor").arg(processors); 
148        }
149
150        self.annotation_processor_paths.iter()
151            .for_each(|p| { cmd.arg("-processorpath").arg(p); });
152
153        for (flag, dir) in [
154            ("-d", self.classes_out_dir.as_ref()),
155            ("-s", self.sources_out_dir.as_ref()),
156            ("-h", self.headers_out_dir.as_ref()),
157        ].iter() {
158            if let Some(dir) = dir {
159                cmd.arg(flag).arg(dir);
160            }
161        }
162
163        for (flag, cond) in [
164            ("-nowarn",          self.nowarn),
165            ("-verbose",         self.verbose),
166            ("-deprecation",     self.deprecation),
167            ("-parameters",      self.method_paramater_metadata),
168            ("-Werror",          self.warnings_as_errors),
169            ("--enable-preview", self.enable_preview_features)
170        ].into_iter() {
171            if cond { cmd.arg(flag); }
172        }
173
174        self.annotation_parameters.iter()
175            .for_each(|(k,v)| { cmd.arg(format!("-A{}={}", k, v)); });
176        self.files.iter().for_each(|f| { cmd.arg(f); });
177
178        Ok(cmd)
179    }
180
181    ///////////////////////////////////////////////////////////////////////////
182    //////////////////////// Builder methods below ////////////////////////////
183    ///////////////////////////////////////////////////////////////////////////
184
185    /// Override the default `JAVA_HOME` path.
186    ///
187    /// If not set, the default path is found using the `JAVA_HOME` env var.
188    pub fn java_home<P: Into<PathBuf>>(&mut self, java_home: P) -> &mut Self {
189        self.java_home = Some(java_home.into());
190        self
191    }
192
193    /// Set which debug info should be included in the generated class files
194    #[doc(alias("-g"))]
195    pub fn debug_info(&mut self, debug_info: DebugInfo) -> &mut Self {
196        self.debug_info = Some(debug_info);
197        self
198    }
199
200    /// If set to `true`, all warnings are disabled.
201    pub fn nowarn(&mut self, nowarn: bool) -> &mut Self {
202        self.nowarn = nowarn;
203        self
204    }
205
206    /// Enable verbose output.
207    pub fn verbose(&mut self, verbose: bool) -> &mut Self {
208        self.verbose = verbose;
209        self
210    }
211
212    /// Configure the output about `deprecation` usage.
213    ///
214    /// * If `true`, javac will output full descriptions of all places
215    ///   where deprecated members/classes are used or overridden.
216    /// * If `false`, javac will output only a summary on a per-source file basis.
217    pub fn deprecation(&mut self, deprecation: bool) -> &mut Self {
218        self.deprecation = deprecation;
219        self
220    }
221
222    /// Enable or disable preview language features.
223    pub fn enable_preview_features(&mut self, enable_preview_features: bool) -> &mut Self {
224        self.enable_preview_features = enable_preview_features;
225        self
226    }
227
228    /// Specify where to find user class files and annotation processors.
229    ///
230    /// If no class paths are provided, the current directory will be used.
231    pub fn class_path<P: AsRef<OsStr>>(&mut self, class_path: P) -> &mut Self {
232        self.class_paths.push(class_path.as_ref().into());
233        self
234    }
235
236    /// Specify where to find input source files.
237    ///
238    /// If not specified, `class_paths` will be searched for source files.
239    pub fn source_path<P: AsRef<OsStr>>(&mut self, source_path: P) -> &mut Self {
240        self.source_paths.push(source_path.as_ref().into());
241        self
242    }
243
244    /// Specify where to find bootstrap class files.
245    ///
246    /// If set, this will override the default search locations.
247    pub fn boot_class_path<P: AsRef<OsStr>>(&mut self, boot_class_path: P) -> &mut Self {
248        self.boot_class_paths.push(boot_class_path.as_ref().into());
249        self
250    }
251
252    /// Specify where to find installed extensions.
253    ///
254    /// If set, this will override the default search locations.
255    pub fn extension_dir<P: AsRef<OsStr>>(&mut self, extension_dir: P) -> &mut Self {
256        self.extension_dirs.push(extension_dir.as_ref().into());
257        self
258    }
259
260    /// Specify where to find endorsed standards.
261    ///
262    /// If set, this will override the default endorsed standards path.
263    pub fn endorsed_dir<P: AsRef<OsStr>>(&mut self, endorsed_dir: P) -> &mut Self {
264        self.endorsed_dirs.push(endorsed_dir.as_ref().into());
265        self
266    }
267
268    /// Add an annotation processor to be run during compilation.
269    ///
270    /// Setting this will bypass the default discovery process.
271    pub fn annotation_processor<S: AsRef<OsStr>>(&mut self, annotation_processor: S) -> &mut Self {
272        self.annotation_processors.push(annotation_processor.as_ref().into());
273        self
274    }
275
276    /// Add a path to search for annotation processors.
277    ///
278    /// If not provided, the class paths will be searched by default.
279    pub fn annotation_processor_path<P: AsRef<OsStr>>(&mut self, annotation_processor_path: P) -> &mut Self {
280        self.annotation_processor_paths.push(annotation_processor_path.as_ref().into());
281        self
282    }
283
284    /// Enable generation of metadata on method parameters
285    /// such that the reflection API can be used to retrieve parameter info.
286    pub fn method_paramater_metadata(&mut self, method_paramater_metadata: bool) -> &mut Self {
287        self.method_paramater_metadata = method_paramater_metadata;
288        self
289    }
290
291    /// Specify where to place generated class files.
292    ///
293    /// If not provided, class files will be placed
294    /// in the same directory as the source files.
295    #[doc(alias("-d"))]
296    pub fn classes_out_dir<P: AsRef<OsStr>>(&mut self, classes_out_dir: P) -> &mut Self {
297        self.classes_out_dir = Some(classes_out_dir.as_ref().into());
298        self
299    }
300
301    /// Specify where to place generated source files.
302    #[doc(alias("-s"))]
303    pub fn sources_out_dir<P: AsRef<OsStr>>(&mut self, sources_out_dir: P) -> &mut Self {
304        self.sources_out_dir = Some(sources_out_dir.as_ref().into());
305        self
306    }
307
308    /// Specify where to place generated native header files.
309    #[doc(alias("-h"))]
310    pub fn headers_out_dir<P: AsRef<OsStr>>(&mut self, headers_out_dir: P) -> &mut Self {
311        self.headers_out_dir = Some(headers_out_dir.as_ref().into());
312        self
313    }
314
315    /// Add a key-value pair to be passed as an option to an annotation processor.
316    #[doc(alias("-A"))]
317    pub fn annotation_parameter<K, V>(&mut self, key: K, value: V) -> &mut Self 
318    where
319        K: Into<String>,
320        V: Into<String>,
321    {
322        self.annotation_parameters.push((key.into(), value.into()));
323        self
324    }
325
326    /// If set to `true`, warnings are treated as compilation errors.
327    pub fn warnings_as_errors(&mut self, warnings_as_errors: bool) -> &mut Self {
328        self.warnings_as_errors = warnings_as_errors;
329        self
330    }
331
332    /// Adds a Java source file to be compiled by javac.
333    #[doc(alias("source file"))]
334    pub fn file<P: AsRef<OsStr>>(&mut self, file: P) -> &mut Self {
335        self.files.push(file.as_ref().into());
336        self
337    }
338
339    /// Adds multiple Java source files to be compiled by javac.
340    ///
341    /// This is the same as calling [`JavaBuild::file()`] multiple times.
342    #[doc(alias("source files"))]
343    pub fn files<P>(&mut self, files: P) -> &mut Self
344    where
345        P: IntoIterator,
346        P::Item: AsRef<OsStr>,
347    {
348        self.files.extend(files.into_iter().map(|f| f.as_ref().into()));
349        self
350    }
351}