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    /// The Java version for source compatibility.
71    java_source_version: Option<u32>,
72    /// The Java version for target compatibility.
73    java_target_version: Option<u32>,
74    /// Paths to the java source files to be compiled.
75    files: Vec<OsString>,
76}
77
78/// Debug information to include in the output of a `javac` build.
79///
80/// The default value for this struct is for everything to be `true`,
81/// meaning all debug information is included.
82/// This is only relevant *if* you set the `debug_info` field in [`JavaBuild`].
83#[derive(Clone, Debug)]
84pub struct DebugInfo {
85    pub line_numbers: bool,
86    pub variables: bool,
87    pub source_files: bool,
88}
89impl Default for DebugInfo {
90    fn default() -> Self {
91        Self {
92            line_numbers: true,
93            variables: true,
94            source_files: true,
95        }
96    }
97}
98impl DebugInfo {
99    fn add_as_args_to<'c>(&self, cmd: &'c mut Command) -> &'c mut Command {
100        if self.line_numbers {
101            cmd.arg("-g:lines");
102        }
103        if self.variables {
104            cmd.arg("-g:vars");
105        }
106        if self.source_files {
107            cmd.arg("-g:source");
108        }
109        if !self.line_numbers && !self.variables && !self.source_files {
110            cmd.arg("-g:none");
111        }
112        cmd
113    }
114}
115
116impl JavaBuild {
117    /// Creates a new `JavaBuild` instance with default values,
118    /// which can be further customized using the builder methods.
119    pub fn new() -> Self {
120        Default::default()
121    }
122
123    /// Executes the `javac` command based on this `JavaBuild` instance.
124    pub fn compile(&self) -> std::io::Result<ExitStatus> {
125        self.command()?.status()
126    }
127
128    /// Returns a [`Command`] based on this `JavaBuild` instance
129    /// that can be inspected or customized before being executed.
130    pub fn command(&self) -> std::io::Result<Command> {
131        let jh_clone = self.java_home.clone();
132        let java_home = jh_clone
133            .and_then(PathExt::path_if_exists)
134            .or_else(env_paths::java_home)
135            .ok_or_else(|| std::io::Error::other(
136                "JAVA_HOME not provided, and could not be auto-discovered."
137            ))?;
138        
139        let mut cmd = Command::new(java_home.join("bin").join("javac"));
140        if let Some(d) = self.debug_info.as_ref() {
141            d.add_as_args_to(&mut cmd);
142        }
143
144        let java_ver = crate::check_javac_version(&java_home)?;
145        if java_ver < 8 {
146            return Err(std::io::Error::other(
147                format!("The minimum required Java version is Java 8. Your Java version: {java_ver}")
148            ));
149        }
150        
151        if let Some(source) = self.java_source_version.or(env_paths::java_source_version()) {
152            if java_ver < source {
153                return Err(std::io::Error::other(
154                    format!("'-source {source}' is higher than your Java version: {java_ver}")
155                ));
156            }
157            cmd.arg("-source").arg(source.to_string());
158        }
159
160        if let Some(target) = self.java_target_version.or(env_paths::java_target_version()) {
161            if java_ver < target {
162                return Err(std::io::Error::other(
163                    format!("'-target {target}' is higher than your Java version: {java_ver}")
164                ));
165            }
166            cmd.arg("-target").arg(target.to_string());
167        }
168
169        let add_path_arg = |cmd: &mut Command, arg_name, paths: &[OsString]| {
170            if paths.is_empty() { return; }
171            let seperator = if std::path::MAIN_SEPARATOR == '\\' { ";" } else { ":" };
172            cmd.arg(arg_name).arg(paths.join(OsStr::new(seperator)));
173        };
174        add_path_arg(&mut cmd, "-cp",            &self.class_paths);
175        add_path_arg(&mut cmd, "-sourcepath",    &self.source_paths);
176        add_path_arg(&mut cmd, "-bootclasspath", &self.boot_class_paths);
177        add_path_arg(&mut cmd, "-extdirs",       &self.extension_dirs);
178
179        let processors = self.annotation_processors.join(OsStr::new(","));
180        if processors.len() != 0 {
181            cmd.arg("-processor").arg(processors); 
182        }
183
184        self.annotation_processor_paths.iter()
185            .for_each(|p| { cmd.arg("-processorpath").arg(p); });
186
187        for (flag, dir) in [
188            ("-d", self.classes_out_dir.as_ref()),
189            ("-s", self.sources_out_dir.as_ref()),
190            ("-h", self.headers_out_dir.as_ref()),
191        ].iter() {
192            if let Some(dir) = dir {
193                cmd.arg(flag).arg(dir);
194            }
195        }
196
197        for (flag, cond) in [
198            ("-nowarn",          self.nowarn),
199            ("-verbose",         self.verbose),
200            ("-deprecation",     self.deprecation),
201            ("-parameters",      self.method_paramater_metadata),
202            ("-Werror",          self.warnings_as_errors),
203            ("--enable-preview", self.enable_preview_features)
204        ].into_iter() {
205            if cond { cmd.arg(flag); }
206        }
207
208        self.annotation_parameters.iter()
209            .for_each(|(k,v)| { cmd.arg(format!("-A{}={}", k, v)); });
210        self.files.iter().for_each(|f| { cmd.arg(f); });
211
212        Ok(cmd)
213    }
214
215    ///////////////////////////////////////////////////////////////////////////
216    //////////////////////// Builder methods below ////////////////////////////
217    ///////////////////////////////////////////////////////////////////////////
218
219    /// Override the default `JAVA_HOME` path.
220    ///
221    /// If not set, the default path is found using the `JAVA_HOME` env var.
222    pub fn java_home<P: Into<PathBuf>>(&mut self, java_home: P) -> &mut Self {
223        self.java_home = Some(java_home.into());
224        self
225    }
226
227    /// Set which debug info should be included in the generated class files
228    #[doc(alias("-g"))]
229    pub fn debug_info(&mut self, debug_info: DebugInfo) -> &mut Self {
230        self.debug_info = Some(debug_info);
231        self
232    }
233
234    /// If set to `true`, all warnings are disabled.
235    pub fn nowarn(&mut self, nowarn: bool) -> &mut Self {
236        self.nowarn = nowarn;
237        self
238    }
239
240    /// Enable verbose output.
241    pub fn verbose(&mut self, verbose: bool) -> &mut Self {
242        self.verbose = verbose;
243        self
244    }
245
246    /// Configure the output about `deprecation` usage.
247    ///
248    /// * If `true`, javac will output full descriptions of all places
249    ///   where deprecated members/classes are used or overridden.
250    /// * If `false`, javac will output only a summary on a per-source file basis.
251    pub fn deprecation(&mut self, deprecation: bool) -> &mut Self {
252        self.deprecation = deprecation;
253        self
254    }
255
256    /// Enable or disable preview language features.
257    pub fn enable_preview_features(&mut self, enable_preview_features: bool) -> &mut Self {
258        self.enable_preview_features = enable_preview_features;
259        self
260    }
261
262    /// Specify where to find user class files and annotation processors.
263    ///
264    /// If no class paths are provided, the current directory will be used.
265    pub fn class_path<P: AsRef<OsStr>>(&mut self, class_path: P) -> &mut Self {
266        self.class_paths.push(class_path.as_ref().into());
267        self
268    }
269
270    /// Specify where to find input source files.
271    ///
272    /// If not specified, `class_paths` will be searched for source files.
273    pub fn source_path<P: AsRef<OsStr>>(&mut self, source_path: P) -> &mut Self {
274        self.source_paths.push(source_path.as_ref().into());
275        self
276    }
277
278    /// Specify where to find bootstrap class files.
279    ///
280    /// If set, this will override the default search locations.
281    pub fn boot_class_path<P: AsRef<OsStr>>(&mut self, boot_class_path: P) -> &mut Self {
282        self.boot_class_paths.push(boot_class_path.as_ref().into());
283        self
284    }
285
286    /// Specify where to find installed extensions.
287    ///
288    /// If set, this will override the default search locations.
289    pub fn extension_dir<P: AsRef<OsStr>>(&mut self, extension_dir: P) -> &mut Self {
290        self.extension_dirs.push(extension_dir.as_ref().into());
291        self
292    }
293
294    /// Specify where to find endorsed standards.
295    ///
296    /// If set, this will override the default endorsed standards path.
297    pub fn endorsed_dir<P: AsRef<OsStr>>(&mut self, endorsed_dir: P) -> &mut Self {
298        self.endorsed_dirs.push(endorsed_dir.as_ref().into());
299        self
300    }
301
302    /// Add an annotation processor to be run during compilation.
303    ///
304    /// Setting this will bypass the default discovery process.
305    pub fn annotation_processor<S: AsRef<OsStr>>(&mut self, annotation_processor: S) -> &mut Self {
306        self.annotation_processors.push(annotation_processor.as_ref().into());
307        self
308    }
309
310    /// Add a path to search for annotation processors.
311    ///
312    /// If not provided, the class paths will be searched by default.
313    pub fn annotation_processor_path<P: AsRef<OsStr>>(&mut self, annotation_processor_path: P) -> &mut Self {
314        self.annotation_processor_paths.push(annotation_processor_path.as_ref().into());
315        self
316    }
317
318    /// Enable generation of metadata on method parameters
319    /// such that the reflection API can be used to retrieve parameter info.
320    pub fn method_paramater_metadata(&mut self, method_paramater_metadata: bool) -> &mut Self {
321        self.method_paramater_metadata = method_paramater_metadata;
322        self
323    }
324
325    /// Specify where to place generated class files.
326    ///
327    /// If not provided, class files will be placed
328    /// in the same directory as the source files.
329    #[doc(alias("-d"))]
330    pub fn classes_out_dir<P: AsRef<OsStr>>(&mut self, classes_out_dir: P) -> &mut Self {
331        self.classes_out_dir = Some(classes_out_dir.as_ref().into());
332        self
333    }
334
335    /// Specify where to place generated source files.
336    #[doc(alias("-s"))]
337    pub fn sources_out_dir<P: AsRef<OsStr>>(&mut self, sources_out_dir: P) -> &mut Self {
338        self.sources_out_dir = Some(sources_out_dir.as_ref().into());
339        self
340    }
341
342    /// Specify where to place generated native header files.
343    #[doc(alias("-h"))]
344    pub fn headers_out_dir<P: AsRef<OsStr>>(&mut self, headers_out_dir: P) -> &mut Self {
345        self.headers_out_dir = Some(headers_out_dir.as_ref().into());
346        self
347    }
348
349    /// Add a key-value pair to be passed as an option to an annotation processor.
350    #[doc(alias("-A"))]
351    pub fn annotation_parameter<K, V>(&mut self, key: K, value: V) -> &mut Self 
352    where
353        K: Into<String>,
354        V: Into<String>,
355    {
356        self.annotation_parameters.push((key.into(), value.into()));
357        self
358    }
359
360    /// Specify the Java version for source compatibility.
361    pub fn java_source_version(&mut self, version: u32) -> &mut Self {
362        self.java_source_version.replace(version);
363        self
364    }
365
366    /// Specify the Java version for target compatibility.
367    pub fn java_target_version(&mut self, version: u32) -> &mut Self {
368        self.java_target_version.replace(version);
369        self
370    }
371
372    /// If set to `true`, warnings are treated as compilation errors.
373    pub fn warnings_as_errors(&mut self, warnings_as_errors: bool) -> &mut Self {
374        self.warnings_as_errors = warnings_as_errors;
375        self
376    }
377
378    /// Adds a Java source file to be compiled by javac.
379    #[doc(alias("source file"))]
380    pub fn file<P: AsRef<OsStr>>(&mut self, file: P) -> &mut Self {
381        self.files.push(file.as_ref().into());
382        self
383    }
384
385    /// Adds multiple Java source files to be compiled by javac.
386    ///
387    /// This is the same as calling [`JavaBuild::file()`] multiple times.
388    #[doc(alias("source files"))]
389    pub fn files<P>(&mut self, files: P) -> &mut Self
390    where
391        P: IntoIterator,
392        P::Item: AsRef<OsStr>,
393    {
394        self.files.extend(files.into_iter().map(|f| f.as_ref().into()));
395        self
396    }
397}