use std::path::PathBuf;
use std::ffi::{OsStr, OsString};
use std::process::{Command, ExitStatus};
use crate::env_paths::{self, PathExt};
#[derive(Clone, Debug, Default)]
pub struct JavaBuild {
java_home: Option<PathBuf>,
debug_info: Option<DebugInfo>,
nowarn: bool,
verbose: bool,
warnings_as_errors: bool,
deprecation: bool,
enable_preview_features: bool,
class_paths: Vec<OsString>,
source_paths: Vec<OsString>,
boot_class_paths: Vec<OsString>,
extension_dirs: Vec<OsString>,
endorsed_dirs: Vec<OsString>,
annotation_processors: Vec<OsString>,
annotation_processor_paths: Vec<OsString>,
method_paramater_metadata: bool,
#[doc(alias = "-d")]
classes_out_dir: Option<OsString>,
#[doc(alias = "-s")]
sources_out_dir: Option<OsString>,
#[doc(alias = "-h")]
headers_out_dir: Option<OsString>,
#[doc(alias = "-A")]
annotation_parameters: Vec<(String, String)>,
java_source_version: Option<u32>,
java_target_version: Option<u32>,
files: Vec<OsString>,
}
#[derive(Clone, Debug)]
pub struct DebugInfo {
pub line_numbers: bool,
pub variables: bool,
pub source_files: bool,
}
impl Default for DebugInfo {
fn default() -> Self {
Self {
line_numbers: true,
variables: true,
source_files: true,
}
}
}
impl DebugInfo {
fn add_as_args_to<'c>(&self, cmd: &'c mut Command) -> &'c mut Command {
if self.line_numbers {
cmd.arg("-g:lines");
}
if self.variables {
cmd.arg("-g:vars");
}
if self.source_files {
cmd.arg("-g:source");
}
if !self.line_numbers && !self.variables && !self.source_files {
cmd.arg("-g:none");
}
cmd
}
}
impl JavaBuild {
pub fn new() -> Self {
Default::default()
}
pub fn compile(&self) -> std::io::Result<ExitStatus> {
self.command()?.status()
}
pub fn command(&self) -> std::io::Result<Command> {
let jh_clone = self.java_home.clone();
let java_home = jh_clone
.and_then(PathExt::path_if_exists)
.or_else(env_paths::java_home)
.ok_or_else(|| std::io::Error::other(
"JAVA_HOME not provided, and could not be auto-discovered."
))?;
let mut cmd = Command::new(java_home.join("bin").join("javac"));
if let Some(d) = self.debug_info.as_ref() {
d.add_as_args_to(&mut cmd);
}
let java_ver = crate::check_javac_version(&java_home)?;
if java_ver < 8 {
return Err(std::io::Error::other(
format!("The minimum required Java version is Java 8. Your Java version: {java_ver}")
));
}
if let Some(source) = self.java_source_version.or(env_paths::java_source_version()) {
if java_ver < source {
return Err(std::io::Error::other(
format!("'-source {source}' is higher than your Java version: {java_ver}")
));
}
cmd.arg("-source").arg(source.to_string());
}
if let Some(target) = self.java_target_version.or(env_paths::java_target_version()) {
if java_ver < target {
return Err(std::io::Error::other(
format!("'-target {target}' is higher than your Java version: {java_ver}")
));
}
cmd.arg("-target").arg(target.to_string());
}
let add_path_arg = |cmd: &mut Command, arg_name, paths: &[OsString]| {
if paths.is_empty() { return; }
let seperator = if std::path::MAIN_SEPARATOR == '\\' { ";" } else { ":" };
cmd.arg(arg_name).arg(paths.join(OsStr::new(seperator)));
};
add_path_arg(&mut cmd, "-cp", &self.class_paths);
add_path_arg(&mut cmd, "-sourcepath", &self.source_paths);
add_path_arg(&mut cmd, "-bootclasspath", &self.boot_class_paths);
add_path_arg(&mut cmd, "-extdirs", &self.extension_dirs);
let processors = self.annotation_processors.join(OsStr::new(","));
if processors.len() != 0 {
cmd.arg("-processor").arg(processors);
}
self.annotation_processor_paths.iter()
.for_each(|p| { cmd.arg("-processorpath").arg(p); });
for (flag, dir) in [
("-d", self.classes_out_dir.as_ref()),
("-s", self.sources_out_dir.as_ref()),
("-h", self.headers_out_dir.as_ref()),
].iter() {
if let Some(dir) = dir {
cmd.arg(flag).arg(dir);
}
}
for (flag, cond) in [
("-nowarn", self.nowarn),
("-verbose", self.verbose),
("-deprecation", self.deprecation),
("-parameters", self.method_paramater_metadata),
("-Werror", self.warnings_as_errors),
("--enable-preview", self.enable_preview_features)
].into_iter() {
if cond { cmd.arg(flag); }
}
self.annotation_parameters.iter()
.for_each(|(k,v)| { cmd.arg(format!("-A{}={}", k, v)); });
self.files.iter().for_each(|f| { cmd.arg(f); });
Ok(cmd)
}
pub fn java_home<P: Into<PathBuf>>(&mut self, java_home: P) -> &mut Self {
self.java_home = Some(java_home.into());
self
}
#[doc(alias("-g"))]
pub fn debug_info(&mut self, debug_info: DebugInfo) -> &mut Self {
self.debug_info = Some(debug_info);
self
}
pub fn nowarn(&mut self, nowarn: bool) -> &mut Self {
self.nowarn = nowarn;
self
}
pub fn verbose(&mut self, verbose: bool) -> &mut Self {
self.verbose = verbose;
self
}
pub fn deprecation(&mut self, deprecation: bool) -> &mut Self {
self.deprecation = deprecation;
self
}
pub fn enable_preview_features(&mut self, enable_preview_features: bool) -> &mut Self {
self.enable_preview_features = enable_preview_features;
self
}
pub fn class_path<P: AsRef<OsStr>>(&mut self, class_path: P) -> &mut Self {
self.class_paths.push(class_path.as_ref().into());
self
}
pub fn source_path<P: AsRef<OsStr>>(&mut self, source_path: P) -> &mut Self {
self.source_paths.push(source_path.as_ref().into());
self
}
pub fn boot_class_path<P: AsRef<OsStr>>(&mut self, boot_class_path: P) -> &mut Self {
self.boot_class_paths.push(boot_class_path.as_ref().into());
self
}
pub fn extension_dir<P: AsRef<OsStr>>(&mut self, extension_dir: P) -> &mut Self {
self.extension_dirs.push(extension_dir.as_ref().into());
self
}
pub fn endorsed_dir<P: AsRef<OsStr>>(&mut self, endorsed_dir: P) -> &mut Self {
self.endorsed_dirs.push(endorsed_dir.as_ref().into());
self
}
pub fn annotation_processor<S: AsRef<OsStr>>(&mut self, annotation_processor: S) -> &mut Self {
self.annotation_processors.push(annotation_processor.as_ref().into());
self
}
pub fn annotation_processor_path<P: AsRef<OsStr>>(&mut self, annotation_processor_path: P) -> &mut Self {
self.annotation_processor_paths.push(annotation_processor_path.as_ref().into());
self
}
pub fn method_paramater_metadata(&mut self, method_paramater_metadata: bool) -> &mut Self {
self.method_paramater_metadata = method_paramater_metadata;
self
}
#[doc(alias("-d"))]
pub fn classes_out_dir<P: AsRef<OsStr>>(&mut self, classes_out_dir: P) -> &mut Self {
self.classes_out_dir = Some(classes_out_dir.as_ref().into());
self
}
#[doc(alias("-s"))]
pub fn sources_out_dir<P: AsRef<OsStr>>(&mut self, sources_out_dir: P) -> &mut Self {
self.sources_out_dir = Some(sources_out_dir.as_ref().into());
self
}
#[doc(alias("-h"))]
pub fn headers_out_dir<P: AsRef<OsStr>>(&mut self, headers_out_dir: P) -> &mut Self {
self.headers_out_dir = Some(headers_out_dir.as_ref().into());
self
}
#[doc(alias("-A"))]
pub fn annotation_parameter<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: Into<String>,
V: Into<String>,
{
self.annotation_parameters.push((key.into(), value.into()));
self
}
pub fn java_source_version(&mut self, version: u32) -> &mut Self {
self.java_source_version.replace(version);
self
}
pub fn java_target_version(&mut self, version: u32) -> &mut Self {
self.java_target_version.replace(version);
self
}
pub fn warnings_as_errors(&mut self, warnings_as_errors: bool) -> &mut Self {
self.warnings_as_errors = warnings_as_errors;
self
}
#[doc(alias("source file"))]
pub fn file<P: AsRef<OsStr>>(&mut self, file: P) -> &mut Self {
self.files.push(file.as_ref().into());
self
}
#[doc(alias("source files"))]
pub fn files<P>(&mut self, files: P) -> &mut Self
where
P: IntoIterator,
P::Item: AsRef<OsStr>,
{
self.files.extend(files.into_iter().map(|f| f.as_ref().into()));
self
}
}