1use std::path::PathBuf;
4use std::ffi::{OsStr, OsString};
5use std::process::{Command, ExitStatus};
6use crate::env_paths::{self, PathExt};
7
8#[derive(Clone, Debug, Default)]
17pub struct JavaBuild {
18 java_home: Option<PathBuf>,
21 debug_info: Option<DebugInfo>,
23 nowarn: bool,
25 verbose: bool,
27 warnings_as_errors: bool,
29 deprecation: bool,
33 enable_preview_features: bool,
35 class_paths: Vec<OsString>,
38 source_paths: Vec<OsString>,
41 boot_class_paths: Vec<OsString>,
43 extension_dirs: Vec<OsString>,
45 endorsed_dirs: Vec<OsString>,
47 annotation_processors: Vec<OsString>,
50 annotation_processor_paths: Vec<OsString>,
53 method_paramater_metadata: bool,
56 #[doc(alias = "-d")]
60 classes_out_dir: Option<OsString>,
61 #[doc(alias = "-s")]
63 sources_out_dir: Option<OsString>,
64 #[doc(alias = "-h")]
66 headers_out_dir: Option<OsString>,
67 #[doc(alias = "-A")]
69 annotation_parameters: Vec<(String, String)>,
70 java_source_version: Option<u32>,
72 java_target_version: Option<u32>,
74 files: Vec<OsString>,
76}
77
78#[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 pub fn new() -> Self {
120 Default::default()
121 }
122
123 pub fn compile(&self) -> std::io::Result<ExitStatus> {
125 self.command()?.status()
126 }
127
128 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 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 #[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 pub fn nowarn(&mut self, nowarn: bool) -> &mut Self {
236 self.nowarn = nowarn;
237 self
238 }
239
240 pub fn verbose(&mut self, verbose: bool) -> &mut Self {
242 self.verbose = verbose;
243 self
244 }
245
246 pub fn deprecation(&mut self, deprecation: bool) -> &mut Self {
252 self.deprecation = deprecation;
253 self
254 }
255
256 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 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 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 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 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 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 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 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 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 #[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 #[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 #[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 #[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 pub fn java_source_version(&mut self, version: u32) -> &mut Self {
362 self.java_source_version.replace(version);
363 self
364 }
365
366 pub fn java_target_version(&mut self, version: u32) -> &mut Self {
368 self.java_target_version.replace(version);
369 self
370 }
371
372 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 #[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 #[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}