use std::env;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::string::FromUtf8Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("UTF-8 conversion error: {0}")]
Utf8(#[from] FromUtf8Error),
#[error("javac compiler not found: {0}")]
CompilerNotFound(String),
#[error("No source files specified for compilation")]
NoSourceFiles,
#[error("javac compilation failed:\nstdout: {stdout}\nstderr: {stderr}")]
CompilationFailed { stdout: String, stderr: String },
#[error(
"javac compilation succeeded but no [wrote ...] lines were found in stderr.\nThis may indicate javac is not outputting verbose messages.\nstdout: {stdout}\nstderr: {stderr}"
)]
NoClassFilesGenerated { stdout: String, stderr: String },
#[error("invalid classpath: {0}")]
InvalidClasspath(String),
#[error("not a directory: {0}")]
InvalidDirectory(String),
#[error("OUT_DIR environment variable not set and no output directory specified")]
OutDirNotSet,
#[error("Unsupported: {0}")]
Unsupported(String),
}
#[derive(Debug, Clone)]
pub struct Build {
files: Vec<PathBuf>,
source_dirs: Vec<PathBuf>,
classpath: Vec<PathBuf>,
output_dir: Option<PathBuf>,
output_src_dir: Option<PathBuf>,
output_subdir: Option<PathBuf>,
source_version: Option<String>,
target_version: Option<String>,
release: Option<String>,
encoding: Option<String>,
debug: bool,
warnings: bool,
werror: bool,
emit_rerun_if_changed: bool,
cargo_metadata: bool,
extra_args: Vec<String>,
#[allow(dead_code)]
javac_version: Option<u32>,
}
impl Default for Build {
fn default() -> Self {
Self::new()
}
}
impl Build {
pub fn new() -> Self {
Build {
files: Vec::new(),
source_dirs: Vec::new(),
classpath: Vec::new(),
output_dir: None,
output_src_dir: None,
output_subdir: None,
source_version: None,
target_version: None,
release: None,
encoding: None,
debug: false,
warnings: true,
werror: false,
emit_rerun_if_changed: false,
cargo_metadata: true,
extra_args: Vec::new(),
javac_version: None,
}
}
pub fn file<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.files.push(path.as_ref().to_path_buf());
self
}
pub fn files<P: AsRef<Path>>(&mut self, paths: impl IntoIterator<Item = P>) -> &mut Self {
self.files
.extend(paths.into_iter().map(|p| p.as_ref().to_path_buf()));
self
}
pub fn source_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.source_dirs.push(path.as_ref().to_path_buf());
self
}
pub fn emit_rerun_if_changed(&mut self, enable: bool) -> &mut Self {
self.emit_rerun_if_changed = enable;
self
}
pub fn cargo_metadata(&mut self, enable: bool) -> &mut Self {
self.cargo_metadata = enable;
self
}
pub fn classpath<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.classpath.push(path.as_ref().to_path_buf());
self
}
pub fn output_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.output_dir = Some(path.as_ref().to_path_buf());
if self.output_src_dir.is_none() {
self.output_src_dir = self.output_dir.clone();
}
self
}
fn get_output_src_dir(&self) -> Result<PathBuf> {
if let Some(ref dir) = self.output_src_dir {
return Ok(dir.clone());
}
if let Some(ref dir) = self.output_dir {
return Ok(dir.clone());
}
let out_dir = std::env::var("OUT_DIR")
.map(PathBuf::from)
.map_err(|_| Error::OutDirNotSet)?;
Ok(out_dir.join("javac-build/generated-sources/"))
}
pub fn output_subdir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.output_subdir = Some(path.as_ref().to_path_buf());
self
}
pub fn output_src_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.output_src_dir = Some(path.as_ref().to_path_buf());
self
}
pub fn source_version(&mut self, version: impl Into<String>) -> &mut Self {
self.source_version = Some(version.into());
self
}
pub fn target_version(&mut self, version: impl Into<String>) -> &mut Self {
self.target_version = Some(version.into());
self
}
pub fn release(&mut self, version: impl Into<String>) -> &mut Self {
self.release = Some(version.into());
self
}
pub fn encoding(&mut self, encoding: impl Into<String>) -> &mut Self {
self.encoding = Some(encoding.into());
self
}
pub fn debug(&mut self, enable: bool) -> &mut Self {
self.debug = enable;
self
}
pub fn warnings(&mut self, enable: bool) -> &mut Self {
self.warnings = enable;
self
}
pub fn werror(&mut self, enable: bool) -> &mut Self {
self.werror = enable;
self
}
pub fn arg(&mut self, arg: impl Into<String>) -> &mut Self {
self.extra_args.push(arg.into());
self
}
pub fn args<Iter>(&mut self, args: Iter) -> &mut Self
where
Iter: IntoIterator,
Iter::Item: AsRef<OsStr>,
{
for arg in args {
if let Some(arg_str) = arg.as_ref().to_str() {
self.extra_args.push(arg_str.to_string());
}
}
self
}
pub fn remove_arg(&mut self, arg: &str) -> &mut Self {
self.extra_args.retain(|a| a != arg);
self
}
pub fn compile(&self) -> Vec<PathBuf> {
let compiler = self.get_compiler();
compiler.compile()
}
pub fn try_compile(&self) -> Result<Vec<PathBuf>> {
let compiler = self.try_get_compiler()?;
compiler.try_compile()
}
pub fn get_compiler(&self) -> JavaCompiler {
self.try_get_compiler()
.unwrap_or_else(|err| panic!("failed to find javac compiler: {err}"))
}
pub fn try_get_compiler(&self) -> Result<JavaCompiler> {
JavaCompiler::new(self)
}
fn find_javac(&self) -> Result<PathBuf> {
if let Ok(java_home) = env::var("JAVA_HOME") {
let javac_path = Path::new(&java_home).join("bin").join(if cfg!(windows) {
"javac.exe"
} else {
"javac"
});
if javac_path.exists() {
return Ok(javac_path);
}
}
which::which("javac").map_err(|e| {
Error::CompilerNotFound(format!(
"Could not find javac: {}. Please set JAVA_HOME or ensure javac is in PATH.",
e
))
})
}
fn check_javac_version(&self, javac: &Path) -> Result<u32> {
let output = Command::new(javac)
.arg("-version")
.arg("-J-XX:+PerfDisableSharedMem")
.output()?;
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let version_output = stderr.as_ref();
let stdout_output = stdout.as_ref();
let actual_output = if !version_output.trim().is_empty() {
version_output
} else if !stdout_output.trim().is_empty() {
stdout_output
} else {
return Err(Error::CompilerNotFound(
"Could not capture javac version output".to_string(),
));
};
if let Some(version_str) = actual_output.split_whitespace().nth(1) {
let parts: Vec<&str> = version_str.split('.').collect();
if parts.len() >= 2
&& parts[0] == "1"
&& let Ok(minor) = parts[1].split('_').next().unwrap_or("").parse::<u32>()
{
return Ok(minor);
}
if let Some(major_str) = parts.first()
&& let Ok(major) = major_str.parse::<u32>()
{
return Ok(major);
}
}
Err(Error::CompilerNotFound(format!(
"Could not parse javac version from output: '{}'",
actual_output.trim()
)))
}
fn collect_source_files(&self) -> Result<Vec<PathBuf>> {
let mut files = self.files.clone();
for source_dir in &self.source_dirs {
Self::scan_directory(source_dir, &mut files)?;
}
Ok(files)
}
fn scan_directory(dir: &Path, files: &mut Vec<PathBuf>) -> Result<()> {
if !dir.is_dir() {
return Err(Error::InvalidDirectory(dir.display().to_string()));
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
Self::scan_directory(&path, files)?;
} else if path.extension().and_then(|s| s.to_str()) == Some("java") {
files.push(path);
}
}
Ok(())
}
fn get_output_dir(&self) -> Result<PathBuf> {
let base_dir = if let Some(ref dir) = self.output_dir {
dir.clone()
} else {
let out_dir = env::var("OUT_DIR")
.map(PathBuf::from)
.map_err(|_| Error::OutDirNotSet)?;
out_dir.join("javac-build/classes/")
};
Ok(if let Some(ref subdir) = self.output_subdir {
base_dir.join(subdir)
} else {
base_dir
})
}
}
#[derive(Debug, Clone)]
pub struct JavaCompiler {
path: PathBuf,
source_files: Vec<PathBuf>,
source_dirs: Vec<PathBuf>,
output_dir: PathBuf,
output_src_dir: PathBuf,
classpath: Vec<PathBuf>,
source_version: Option<String>,
target_version: Option<String>,
release: Option<String>,
encoding: Option<String>,
debug: bool,
warnings: bool,
werror: bool,
extra_args: Vec<String>,
emit_rerun_if_changed: bool,
cargo_metadata: bool,
javac_version: u32,
}
impl JavaCompiler {
fn new(build: &Build) -> Result<Self> {
let path = build.find_javac()?;
let javac_version = build.check_javac_version(&path)?;
let source_files = build.collect_source_files()?;
let output_dir = build.get_output_dir()?;
let output_src_dir = build.get_output_src_dir()?;
Ok(JavaCompiler {
path,
source_files,
source_dirs: build.source_dirs.clone(),
output_dir,
output_src_dir,
classpath: build.classpath.clone(),
source_version: build.source_version.clone(),
target_version: build.target_version.clone(),
release: build.release.clone(),
encoding: build.encoding.clone(),
debug: build.debug,
warnings: build.warnings,
werror: build.werror,
extra_args: build.extra_args.clone(),
emit_rerun_if_changed: build.emit_rerun_if_changed,
cargo_metadata: build.cargo_metadata,
javac_version,
})
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn to_command(&self) -> Result<Command> {
if self.source_files.is_empty() {
return Err(Error::NoSourceFiles);
}
fs::create_dir_all(&self.output_dir)?;
fs::create_dir_all(&self.output_src_dir)?;
let mut cmd = Command::new(&self.path);
cmd.arg("-J-Dfile.encoding=UTF-8");
cmd.arg("-J-Duser.language=en");
cmd.arg("-J-Dsun.stdout.encoding=UTF-8");
cmd.arg("-J-Dsun.stderr.encoding=UTF-8");
cmd.arg("-J-Dstdout.encoding=UTF-8");
cmd.arg("-J-Dstderr.encoding=UTF-8");
cmd.arg("-J-XX:+PerfDisableSharedMem");
cmd.arg("-d").arg(&self.output_dir);
cmd.arg("-s").arg(&self.output_src_dir);
if !self.classpath.is_empty() {
let classpath = env::join_paths(&self.classpath)
.map_err(|e| Error::InvalidClasspath(format!("{}", e)))?;
cmd.arg("-classpath").arg(classpath);
}
if let Some(ref release) = self.release {
if self.javac_version <= 8 {
return Err(Error::Unsupported(format!(
"--release flag is not supported in Java {} (requires Java 9+)",
self.javac_version
)));
}
cmd.arg("--release").arg(release);
} else {
if let Some(ref source) = self.source_version {
cmd.arg("-source").arg(source);
}
if let Some(ref target) = self.target_version {
cmd.arg("-target").arg(target);
}
}
if let Some(ref encoding) = self.encoding {
cmd.arg("-encoding").arg(encoding);
}
if self.debug {
cmd.arg("-g");
}
cmd.arg("-verbose");
if !self.warnings {
cmd.arg("-nowarn");
}
if self.werror {
cmd.arg("-Werror");
}
for arg in &self.extra_args {
cmd.arg(arg);
}
let file_list = self.create_file_list()?;
cmd.arg(format!("@{}", file_list.display()));
Ok(cmd)
}
pub fn compile(&self) -> Vec<PathBuf> {
self.try_compile().unwrap_or_else(|err| panic!("{err}"))
}
pub fn try_compile(&self) -> Result<Vec<PathBuf>> {
if self.cargo_metadata {
println!("cargo:rerun-if-env-changed=JAVA_HOME");
if self.emit_rerun_if_changed {
use std::collections::HashSet;
let mut seen: HashSet<PathBuf> = HashSet::new();
for f in &self.source_files {
if seen.insert(f.clone()) {
println!("cargo:rerun-if-changed={}", f.display());
}
}
for d in &self.source_dirs {
if seen.insert(d.clone()) {
println!("cargo:rerun-if-changed={}", d.display());
}
}
}
}
let mut cmd = self.to_command()?;
let output = cmd.output()?;
let file_list = self.output_dir.join("javac_file_list.txt");
let _ = fs::remove_file(&file_list);
let stderr = String::from_utf8(output.stderr)?;
let stdout = String::from_utf8(output.stdout)?;
if !output.status.success() {
return Err(Error::CompilationFailed { stdout, stderr });
}
let mut seen = std::collections::HashSet::new();
let mut class_files = Vec::new();
for line in stderr.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("[wrote ")
&& let Some(end_idx) = rest.rfind(']')
{
let mut path_str = &rest[..end_idx];
if let Some(inner_path) = path_str.strip_prefix("RegularFileObject[") {
if let Some(final_path) = inner_path.strip_suffix("]") {
path_str = final_path;
}
}
if path_str.ends_with(".class") && seen.insert(path_str.to_owned()) {
class_files.push(PathBuf::from(path_str));
}
}
}
if class_files.is_empty() {
return Err(Error::NoClassFilesGenerated { stdout, stderr });
}
Ok(class_files)
}
fn create_file_list(&self) -> Result<PathBuf> {
let file_list_path = self.output_dir.join("javac_file_list.txt");
let mut file = File::create(&file_list_path)?;
for source_file in &self.source_files {
let abs_path = std::path::absolute(source_file).unwrap_or_else(|_| source_file.clone());
let path_str = abs_path.display().to_string();
writeln!(file, "{}", path_str)?;
}
Ok(file_list_path)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_pattern() {
let build = Build::new()
.file("Foo.java")
.file("Bar.java")
.source_version("11")
.target_version("11")
.debug(true)
.clone();
assert_eq!(build.files.len(), 2);
assert_eq!(build.source_version, Some("11".to_string()));
assert_eq!(build.target_version, Some("11".to_string()));
assert!(build.debug);
}
#[test]
fn test_files_method() {
let files = vec!["Foo.java", "Bar.java", "Baz.java"];
let build = Build::new().files(files).clone();
assert_eq!(build.files.len(), 3);
}
#[test]
fn test_only_reported_class_files() -> Result<()> {
let temp_root = std::env::temp_dir().join(format!(
"javac_test_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let src_dir = temp_root.join("src");
let out_dir = temp_root.join("classes");
fs::create_dir_all(&src_dir)?;
fs::create_dir_all(&out_dir)?;
let foo_java = src_dir.join("Foo.java");
let bar_java = src_dir.join("Bar.java");
fs::write(&foo_java, "public class Foo { }\n")?;
fs::write(&bar_java, "public class Bar { }\n")?;
let unrelated = out_dir.join("Unrelated.class");
fs::write(&unrelated, b"CAFEBABE")?;
let result = Build::new().file(&foo_java).output_dir(&out_dir).compile();
println!("Reported class files: {:?}", result);
let foo_class = out_dir.join("Foo.class");
assert!(
result.contains(&foo_class) || result.contains(&PathBuf::from("Foo.class")),
"Expected Foo.class in reported class files"
);
let bar_class = out_dir.join("Bar.class");
assert!(
!result.contains(&bar_class) && !result.contains(&PathBuf::from("Bar.class")),
"Bar.class should not be reported"
);
assert!(
!result.contains(&unrelated) && !result.contains(&PathBuf::from("Unrelated.class")),
"Unrelated.class should not be reported"
);
Ok(())
}
#[test]
fn test_unicode_filename() -> Result<()> {
let temp_root = std::env::temp_dir().join(format!(
"javac_test_unicode_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let src_dir = temp_root.join("src");
let out_dir = temp_root.join("classes");
fs::create_dir_all(&src_dir)?;
fs::create_dir_all(&out_dir)?;
let unicode_java = src_dir.join("Tëstñamé.java");
fs::write(&unicode_java, "public class Tëstñamé { }\n")?;
let result = Build::new()
.file(&unicode_java)
.output_dir(&out_dir)
.encoding("UTF-8")
.compile();
assert!(
!result.is_empty(),
"Expected at least one class file to be reported"
);
let found_unicode = result.iter().any(|p| {
p.to_str()
.map(|s| s.contains("Tëstñamé.class"))
.unwrap_or(false)
});
assert!(
found_unicode,
"Expected to find Tëstñamé.class in results, but got: {:?}\n\
This likely means stderr encoding detection is incorrect.",
result
);
let expected_class = out_dir.join("Tëstñamé.class");
assert!(
expected_class.exists(),
"Expected class file to exist at {}",
expected_class.display()
);
Ok(())
}
#[test]
fn test_unicode_filename_beyond_windows1252() -> Result<()> {
let temp_root = std::env::temp_dir().join(format!(
"javac_test_unicode_complex_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let src_dir = temp_root.join("src");
let out_dir = temp_root.join("classes");
fs::create_dir_all(&src_dir)?;
fs::create_dir_all(&out_dir)?;
let unicode_java = src_dir.join("Test你好Привет.java");
fs::write(&unicode_java, "public class Test你好Привет { }\n")?;
let result = Build::new()
.file(&unicode_java)
.output_dir(&out_dir)
.encoding("UTF-8")
.compile();
assert!(
!result.is_empty(),
"Expected at least one class file to be reported"
);
let found_unicode = result.iter().any(|p| {
p.to_str()
.map(|s| s.contains("Test你好Привет.class"))
.unwrap_or(false)
});
assert!(
found_unicode,
"Expected to find Test你好Привет.class in results, but got: {:?}\n\
This likely means stderr encoding detection is incorrect for non-Windows-1252 characters.",
result
);
let expected_class = out_dir.join("Test你好Привет.class");
assert!(
expected_class.exists(),
"Expected class file to exist at {}",
expected_class.display()
);
Ok(())
}
#[test]
fn test_utf16_encoding() -> Result<()> {
let temp_root = std::env::temp_dir().join(format!(
"javac_test_utf16_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let src_dir = temp_root.join("src");
let out_dir = temp_root.join("classes");
fs::create_dir_all(&src_dir)?;
fs::create_dir_all(&out_dir)?;
let utf16_java = src_dir.join("Utf16Test.java");
let java_source = "public class Utf16Test {\n // 你好世界 🌍\n public static void main(String[] args) {\n System.out.println(\"Hello UTF-16! 🎉\");\n }\n}\n";
let mut utf16_bytes = vec![0xFF, 0xFE]; for c in java_source.encode_utf16() {
utf16_bytes.push((c & 0xFF) as u8);
utf16_bytes.push((c >> 8) as u8);
}
fs::write(&utf16_java, utf16_bytes)?;
let result = Build::new()
.file(&utf16_java)
.output_dir(&out_dir)
.encoding("UTF-16")
.compile();
assert!(
!result.is_empty(),
"Expected at least one class file to be reported"
);
let expected_class = out_dir.join("Utf16Test.class");
assert!(
expected_class.exists(),
"Expected class file to exist at {}",
expected_class.display()
);
let found = result.iter().any(|p| {
p.to_str()
.map(|s| s.contains("Utf16Test.class"))
.unwrap_or(false)
});
assert!(
found,
"Expected to find Utf16Test.class in results, but got: {:?}",
result
);
Ok(())
}
#[test]
fn test_shift_jis_encoding() -> Result<()> {
let temp_root = std::env::temp_dir().join(format!(
"javac_test_shiftjis_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let src_dir = temp_root.join("src");
let out_dir = temp_root.join("classes");
fs::create_dir_all(&src_dir)?;
fs::create_dir_all(&out_dir)?;
let shiftjis_java = src_dir.join("ShiftJisTest.java");
let java_source = "public class ShiftJisTest {\n // こんにちは世界\n public static void main(String[] args) {\n System.out.println(\"こんにちは!\");\n }\n}\n";
use encoding_rs::SHIFT_JIS;
let (encoded, _, _) = SHIFT_JIS.encode(java_source);
fs::write(&shiftjis_java, encoded.as_ref())?;
let result = Build::new()
.file(&shiftjis_java)
.output_dir(&out_dir)
.encoding("Shift_JIS")
.compile();
assert!(
!result.is_empty(),
"Expected at least one class file to be reported"
);
let expected_class = out_dir.join("ShiftJisTest.class");
assert!(
expected_class.exists(),
"Expected class file to exist at {}",
expected_class.display()
);
let found = result.iter().any(|p| {
p.to_str()
.map(|s| s.contains("ShiftJisTest.class"))
.unwrap_or(false)
});
assert!(
found,
"Expected to find ShiftJisTest.class in results, but got: {:?}",
result
);
Ok(())
}
#[test]
fn test_output_subdir() -> Result<()> {
let temp_root = std::env::temp_dir().join(format!(
"javac_test_output_subdir_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let base_out = temp_root.join("base");
let src_dir = temp_root.join("src");
fs::create_dir_all(&src_dir)?;
let foo_java = src_dir.join("Foo.java");
let bar_java = src_dir.join("Bar.java");
fs::write(&foo_java, "public class Foo { }\n")?;
fs::write(&bar_java, "public class Bar { }\n")?;
Build::new()
.file(&foo_java)
.output_dir(&base_out)
.output_subdir("foo-classes")
.compile();
Build::new()
.file(&bar_java)
.output_dir(&base_out)
.output_subdir("bar-classes")
.compile();
let foo_class = base_out.join("foo-classes").join("Foo.class");
let bar_class = base_out.join("bar-classes").join("Bar.class");
assert!(
foo_class.exists(),
"Expected Foo.class at {}",
foo_class.display()
);
assert!(
bar_class.exists(),
"Expected Bar.class at {}",
bar_class.display()
);
assert_ne!(
foo_class.parent().unwrap(),
bar_class.parent().unwrap(),
"Class files should be in separate subdirectories"
);
Ok(())
}
#[test]
fn test_args_api() {
let build = Build::new()
.file("Foo.java")
.arg("-Xlint:all")
.args(["-encoding", "UTF-8"])
.clone();
assert_eq!(build.extra_args.len(), 3);
assert_eq!(build.extra_args[0], "-Xlint:all");
assert_eq!(build.extra_args[1], "-encoding");
assert_eq!(build.extra_args[2], "UTF-8");
}
#[test]
fn test_remove_arg() {
let build = Build::new()
.file("Foo.java")
.arg("-g")
.arg("-Xlint:all")
.arg("-deprecation")
.remove_arg("-Xlint:all")
.clone();
assert_eq!(build.extra_args.len(), 2);
assert!(build.extra_args.contains(&"-g".to_string()));
assert!(build.extra_args.contains(&"-deprecation".to_string()));
assert!(!build.extra_args.contains(&"-Xlint:all".to_string()));
}
#[test]
fn test_get_compiler() -> Result<()> {
let temp_root = std::env::temp_dir().join(format!(
"javac_test_get_compiler_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let src_dir = temp_root.join("src");
let out_dir = temp_root.join("classes");
fs::create_dir_all(&src_dir)?;
let foo_java = src_dir.join("Foo.java");
fs::write(&foo_java, "public class Foo { }\n")?;
let compiler = Build::new()
.file(&foo_java)
.output_dir(&out_dir)
.get_compiler();
let path = compiler.path();
assert!(
path.to_str().unwrap().contains("javac")
|| path.to_str().unwrap().contains("javac.exe")
);
Ok(())
}
#[test]
fn test_java_compiler_to_command() -> Result<()> {
let temp_root = std::env::temp_dir().join(format!(
"javac_test_to_command_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let src_dir = temp_root.join("src");
let out_dir = temp_root.join("classes");
fs::create_dir_all(&src_dir)?;
let foo_java = src_dir.join("Foo.java");
fs::write(&foo_java, "public class Foo { }\n")?;
let compiler = Build::new()
.file(&foo_java)
.output_dir(&out_dir)
.arg("-g")
.get_compiler();
let cmd = compiler.to_command()?;
let program = cmd.get_program();
assert!(
program.to_str().unwrap().contains("javac")
|| program.to_str().unwrap().contains("javac.exe"),
"Expected javac in command program, got: {:?}",
program
);
Ok(())
}
#[test]
fn test_try_get_compiler() {
let mut valid_build = Build::new();
valid_build.file("Foo.java");
let result = valid_build.try_get_compiler();
if let Ok(compiler) = result {
let path = compiler.path();
assert!(
path.to_str().unwrap().contains("javac")
|| path.to_str().unwrap().contains("javac.exe"),
"Expected javac in compiler path"
);
}
}
}