1use std::env;
103use std::path::PathBuf;
104use std::process::Command;
105
106use errors::{JavaLocatorError, Result};
107use glob::{glob, Pattern};
108
109pub mod errors;
110
111#[cfg(not(feature = "locate-jdk-only"))]
112const LOCATE_BINARY: &str = "java";
113#[cfg(feature = "locate-jdk-only")]
114const LOCATE_BINARY: &str = "javac";
115
116pub fn get_jvm_dyn_lib_file_name() -> &'static str {
124 if cfg!(target_os = "windows") {
125 "jvm.dll"
126 } else if cfg!(target_os = "macos") {
127 "libjvm.dylib"
128 } else {
129 "libjvm.so"
130 }
131}
132
133pub fn locate_java_home() -> Result<String> {
139 match &env::var("JAVA_HOME") {
140 Ok(s) if s.is_empty() => do_locate_java_home(),
141 Ok(java_home_env_var) => Ok(java_home_env_var.clone()),
142 Err(_) => do_locate_java_home(),
143 }
144}
145
146#[cfg(target_os = "windows")]
147fn do_locate_java_home() -> Result<String> {
148 let output = Command::new("where")
149 .arg(LOCATE_BINARY)
150 .output()
151 .map_err(|e| JavaLocatorError::new(format!("Failed to run command `where` ({e})")))?;
152
153 let java_exec_path_raw = std::str::from_utf8(&output.stdout)?;
154 java_exec_path_validation(java_exec_path_raw)?;
155
156 let paths_found = java_exec_path_raw.lines().count();
158 if paths_found > 1 {
159 eprintln!("WARNING: java_locator found {paths_found} possible java locations. Using the first one. To silence this warning set JAVA_HOME env var.")
160 }
161
162 let java_exec_path = java_exec_path_raw
163 .lines()
164 .next()
166 .expect("gauranteed to have at least one line by java_exec_path_validation")
167 .trim();
168
169 let mut home_path = follow_symlinks(java_exec_path);
170
171 home_path.pop();
172 home_path.pop();
173
174 home_path
175 .into_os_string()
176 .into_string()
177 .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
178}
179
180#[cfg(target_os = "macos")]
181fn do_locate_java_home() -> Result<String> {
182 let output = Command::new("/usr/libexec/java_home")
183 .output()
184 .map_err(|e| {
185 JavaLocatorError::new(format!(
186 "Failed to run command `/usr/libexec/java_home` ({e})"
187 ))
188 })?;
189
190 let java_exec_path = std::str::from_utf8(&output.stdout)?.trim();
191
192 java_exec_path_validation(java_exec_path)?;
193 let home_path = follow_symlinks(java_exec_path);
194
195 home_path
196 .into_os_string()
197 .into_string()
198 .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
199}
200
201#[cfg(not(any(target_os = "windows", target_os = "macos")))] fn do_locate_java_home() -> Result<String> {
203 let output = Command::new("which")
204 .arg(LOCATE_BINARY)
205 .output()
206 .map_err(|e| JavaLocatorError::new(format!("Failed to run command `which` ({e})")))?;
207 let java_exec_path = std::str::from_utf8(&output.stdout)?.trim();
208
209 java_exec_path_validation(java_exec_path)?;
210 let mut home_path = follow_symlinks(java_exec_path);
211
212 home_path.pop();
214 home_path.pop();
215
216 home_path
217 .into_os_string()
218 .into_string()
219 .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
220}
221
222fn java_exec_path_validation(path: &str) -> Result<()> {
223 if path.is_empty() {
224 return Err(JavaLocatorError::new(
225 "Java is not installed or not in the system PATH".into(),
226 ));
227 }
228
229 Ok(())
230}
231
232fn follow_symlinks(path: &str) -> PathBuf {
233 let mut test_path = PathBuf::from(path);
234 while let Ok(path) = test_path.read_link() {
235 test_path = if path.is_absolute() {
236 path
237 } else {
238 test_path.pop();
239 test_path.push(path);
240 test_path
241 };
242 }
243 test_path
244}
245
246pub fn locate_jvm_dyn_library() -> Result<String> {
248 if cfg!(target_os = "windows") {
249 locate_file("jvm.dll")
250 } else {
251 locate_file("libjvm.*")
252 }
253}
254
255pub fn locate_file(file_name: &str) -> Result<String> {
259 let java_home = locate_java_home()?;
261
262 let query = format!("{}/**/{}", Pattern::escape(&java_home), file_name);
263
264 let path = glob(&query)?.filter_map(|x| x.ok()).next().ok_or_else(|| {
265 JavaLocatorError::new(format!(
266 "Could not find the {file_name} library in any subdirectory of {java_home}",
267 ))
268 })?;
269
270 let parent_path = path.parent().unwrap();
271 match parent_path.to_str() {
272 Some(parent_path) => Ok(parent_path.to_owned()),
273 None => Err(JavaLocatorError::new(format!(
274 "Java path {parent_path:?} is invalid utf8"
275 ))),
276 }
277}
278
279#[cfg(test)]
280mod unit_tests {
281 use super::*;
282
283 #[test]
284 fn locate_java_home_test() {
285 println!("locate_java_home: {}", locate_java_home().unwrap());
286 println!(
287 "locate_jvm_dyn_library: {}",
288 locate_jvm_dyn_library().unwrap()
289 );
290 }
291
292 #[test]
293 fn locate_java_from_exec_test() {
294 println!("do_locate_java_home: {}", do_locate_java_home().unwrap());
295 }
296
297 #[test]
298 fn jni_headers_test() {
299 let java_home = do_locate_java_home().unwrap();
300 assert!(PathBuf::from(java_home)
301 .join("include")
302 .join("jni.h")
303 .exists());
304 }
305}