use std::env;
use std::path::PathBuf;
use std::process::Command;
use errors::{JavaLocatorError, Result};
use glob::{glob, Pattern};
pub mod errors;
#[cfg(not(feature = "locate-jdk-only"))]
const LOCATE_BINARY: &str = "java";
#[cfg(feature = "locate-jdk-only")]
const LOCATE_BINARY: &str = "javac";
pub fn get_jvm_dyn_lib_file_name() -> &'static str {
if cfg!(target_os = "windows") {
"jvm.dll"
} else if cfg!(target_os = "macos") {
"libjvm.dylib"
} else {
"libjvm.so"
}
}
pub fn locate_java_home() -> Result<String> {
match &env::var("JAVA_HOME") {
Ok(s) if s.is_empty() => do_locate_java_home(),
Ok(java_home_env_var) => Ok(java_home_env_var.clone()),
Err(_) => do_locate_java_home(),
}
}
#[cfg(target_os = "windows")]
fn do_locate_java_home() -> Result<String> {
let output = Command::new("where")
.arg(LOCATE_BINARY)
.output()
.map_err(|e| JavaLocatorError::new(format!("Failed to run command `where` ({e})")))?;
let java_exec_path_raw = std::str::from_utf8(&output.stdout)?;
java_exec_path_validation(java_exec_path_raw)?;
let paths_found = java_exec_path_raw.lines().count();
if paths_found > 1 {
eprintln!("WARNING: java_locator found {paths_found} possible java locations. Using the first one. To silence this warning set JAVA_HOME env var.")
}
let java_exec_path = java_exec_path_raw
.lines()
.next()
.expect("gauranteed to have at least one line by java_exec_path_validation")
.trim();
let mut home_path = follow_symlinks(java_exec_path);
home_path.pop();
home_path.pop();
home_path
.into_os_string()
.into_string()
.map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
}
#[cfg(target_os = "macos")]
fn do_locate_java_home() -> Result<String> {
let output = Command::new("/usr/libexec/java_home")
.output()
.map_err(|e| {
JavaLocatorError::new(format!(
"Failed to run command `/usr/libexec/java_home` ({e})"
))
})?;
let java_exec_path = std::str::from_utf8(&output.stdout)?.trim();
java_exec_path_validation(java_exec_path)?;
let home_path = follow_symlinks(java_exec_path);
home_path
.into_os_string()
.into_string()
.map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))] fn do_locate_java_home() -> Result<String> {
let output = Command::new("which")
.arg(LOCATE_BINARY)
.output()
.map_err(|e| JavaLocatorError::new(format!("Failed to run command `which` ({e})")))?;
let java_exec_path = std::str::from_utf8(&output.stdout)?.trim();
java_exec_path_validation(java_exec_path)?;
let mut home_path = follow_symlinks(java_exec_path);
home_path.pop();
home_path.pop();
home_path
.into_os_string()
.into_string()
.map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
}
fn java_exec_path_validation(path: &str) -> Result<()> {
if path.is_empty() {
return Err(JavaLocatorError::new(
"Java is not installed or not in the system PATH".into(),
));
}
Ok(())
}
fn follow_symlinks(path: &str) -> PathBuf {
let mut test_path = PathBuf::from(path);
while let Ok(path) = test_path.read_link() {
test_path = if path.is_absolute() {
path
} else {
test_path.pop();
test_path.push(path);
test_path
};
}
test_path
}
pub fn locate_jvm_dyn_library() -> Result<String> {
if cfg!(target_os = "windows") {
locate_file("jvm.dll")
} else {
locate_file("libjvm.*")
}
}
pub fn locate_file(file_name: &str) -> Result<String> {
let java_home = locate_java_home()?;
let query = format!("{}/**/{}", Pattern::escape(&java_home), file_name);
let path = glob(&query)?.filter_map(|x| x.ok()).next().ok_or_else(|| {
JavaLocatorError::new(format!(
"Could not find the {file_name} library in any subdirectory of {java_home}",
))
})?;
let parent_path = path.parent().unwrap();
match parent_path.to_str() {
Some(parent_path) => Ok(parent_path.to_owned()),
None => Err(JavaLocatorError::new(format!(
"Java path {parent_path:?} is invalid utf8"
))),
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn locate_java_home_test() {
println!("locate_java_home: {}", locate_java_home().unwrap());
println!(
"locate_jvm_dyn_library: {}",
locate_jvm_dyn_library().unwrap()
);
}
#[test]
fn locate_java_from_exec_test() {
println!("do_locate_java_home: {}", do_locate_java_home().unwrap());
}
#[test]
fn jni_headers_test() {
let java_home = do_locate_java_home().unwrap();
assert!(PathBuf::from(java_home)
.join("include")
.join("jni.h")
.exists());
}
}