java_manager/lib.rs
1// Copyright 2026 TaimWay
2//
3// @file: lib.rs
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! # Java Manager Library
18//!
19//! A comprehensive Rust library for discovering, managing, and interacting with Java installations.
20//!
21//! This library provides functionality to:
22//! - Locate Java installations across different platforms
23//! - Extract detailed information about Java installations
24//! - Execute Java commands and capture output
25//! - Manage multiple Java installations
26//! - Search for files within Java installations
27//!
28//! ## Features
29//!
30//! - **Cross-platform support**: Works on Windows, macOS, and Linux/Unix
31//! - **Comprehensive Java detection**: Finds Java installations in common locations
32//! - **Detailed Java information**: Extracts version, architecture, supplier information
33//! - **Advanced searching**: Wildcard support for file searches
34//! - **Command execution**: Execute Java commands and capture output
35//!
36//! ## Quick Start
37//!
38//! ```rust
39//! use java_manager;
40//!
41//! fn main() -> java_manager::Result<()> {
42//! // Get detailed information about the default Java installation
43//! let java_info = java_manager::get_local_java_home()?;
44//! println!("Default Java: {}", java_info);
45//!
46//! // Find all Java installations on the system
47//! let installations = java_manager::find_all_java_installations()?;
48//! println!("Found {} Java installations", installations.len());
49//!
50//! // Execute a Java command
51//! let output = java_info.execute_with_output(&["-version"])?;
52//! println!("Java version output:\n{}", output);
53//!
54//! Ok(())
55//! }
56//! ```
57
58use std::env;
59use std::path::PathBuf;
60use std::process::Command;
61
62use glob::{glob, Pattern};
63
64/// Error handling module
65pub mod errors;
66/// Java information structures
67pub mod info;
68/// Local Java installation management
69pub mod local;
70/// Java installation manager
71pub mod manager;
72/// Utility functions
73pub mod utils;
74
75// Re-export commonly used types and functions
76pub use errors::{JavaLocatorError, Result};
77pub use info::JavaInfo;
78pub use utils::{get_java_architecture, get_java_info, get_java_suppliers, get_java_version};
79pub use local::{
80 find_all_java_installations, get_java_document, get_java_dyn_lib,
81 get_java_home as get_local_java_home,
82};
83
84/// Returns the platform-specific name of the JVM dynamic library.
85///
86/// # Returns
87///
88/// - `"jvm.dll"` on Windows
89/// - `"libjvm.dylib"` on macOS
90/// - `"libjvm.so"` on Linux/Unix
91///
92/// # Examples
93///
94/// ```rust
95/// use java_manager;
96///
97/// let lib_name = java_manager::get_jvm_dyn_lib_file_name();
98/// println!("JVM library name: {}", lib_name);
99/// ```
100pub fn get_jvm_dyn_lib_file_name() -> &'static str {
101 if cfg!(target_os = "windows") {
102 "jvm.dll"
103 } else if cfg!(target_os = "macos") {
104 "libjvm.dylib"
105 } else {
106 "libjvm.so"
107 }
108}
109
110/// Locates and returns the Java home directory path.
111///
112/// This function first checks the `JAVA_HOME` environment variable.
113/// If not set or empty, it attempts to locate Java using platform-specific methods.
114///
115/// # Platform-specific Behavior
116///
117/// - **Windows**: Uses the `where` command to find `java.exe`
118/// - **macOS**: Uses `/usr/libexec/java_home` system utility
119/// - **Linux/Unix**: Uses the `which` command to find `java`
120///
121/// # Returns
122///
123/// - `Ok(String)` containing the Java home path
124/// - `Err(JavaLocatorError)` if Java cannot be located
125///
126/// # Errors
127///
128/// This function may return an error if:
129/// - Java is not installed
130/// - Java is not in the system PATH
131/// - The platform-specific command fails
132///
133/// # Examples
134///
135/// ```rust
136/// use java_manager;
137///
138/// fn main() -> java_manager::Result<()> {
139/// let java_home = java_manager::locate_java_home()?;
140/// println!("Java home: {}", java_home);
141/// Ok(())
142/// }
143/// ```
144pub fn locate_java_home() -> Result<String> {
145 match &env::var("JAVA_HOME") {
146 Ok(s) if s.is_empty() => do_locate_java_home(),
147 Ok(java_home_env_var) => Ok(java_home_env_var.clone()),
148 Err(_) => do_locate_java_home(),
149 }
150}
151
152#[cfg(target_os = "windows")]
153fn do_locate_java_home() -> Result<String> {
154 let output = Command::new("where")
155 .arg("java")
156 .output()
157 .map_err(|e| JavaLocatorError::new(format!("Failed to run command `where` ({e})")))?;
158
159 let java_exec_path_raw = std::str::from_utf8(&output.stdout)?;
160 java_exec_path_validation(java_exec_path_raw)?;
161
162 let paths_found = java_exec_path_raw.lines().count();
163 if paths_found > 1 {
164 eprintln!("WARNING: Found {paths_found} possible java locations. Using the first one. Set JAVA_HOME env var to avoid this warning.")
165 }
166
167 let java_exec_path = java_exec_path_raw
168 .lines()
169 .next()
170 .expect("guaranteed to have at least one line by java_exec_path_validation")
171 .trim();
172
173 let mut home_path = follow_symlinks(java_exec_path);
174
175 // Remove "bin" and parent directory to get JAVA_HOME
176 home_path.pop();
177 home_path.pop();
178
179 home_path
180 .into_os_string()
181 .into_string()
182 .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
183}
184
185#[cfg(target_os = "macos")]
186fn do_locate_java_home() -> Result<String> {
187 let output = Command::new("/usr/libexec/java_home")
188 .output()
189 .map_err(|e| {
190 JavaLocatorError::new(format!(
191 "Failed to run command `/usr/libexec/java_home` ({e})"
192 ))
193 })?;
194
195 let java_exec_path = std::str::from_utf8(&output.stdout)?.trim();
196
197 java_exec_path_validation(java_exec_path)?;
198 let home_path = follow_symlinks(java_exec_path);
199
200 home_path
201 .into_os_string()
202 .into_string()
203 .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
204}
205
206#[cfg(not(any(target_os = "windows", target_os = "macos")))] // Unix
207fn do_locate_java_home() -> Result<String> {
208 let output = Command::new("which")
209 .arg("java")
210 .output()
211 .map_err(|e| JavaLocatorError::new(format!("Failed to run command `which` ({e})")))?;
212 let java_exec_path = std::str::from_utf8(&output.stdout)?.trim();
213
214 java_exec_path_validation(java_exec_path)?;
215 let mut home_path = follow_symlinks(java_exec_path);
216
217 // Remove "bin" directory to get JAVA_HOME
218 home_path.pop();
219 home_path.pop();
220
221 home_path
222 .into_os_string()
223 .into_string()
224 .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
225}
226
227/// Validates that a Java executable path is not empty.
228///
229/// # Arguments
230///
231/// * `path` - The Java executable path to validate
232///
233/// # Returns
234///
235/// - `Ok(())` if the path is valid (non-empty)
236/// - `Err(JavaLocatorError)` if the path is empty
237///
238/// # Examples
239///
240/// ```rust
241/// use java_manager;
242///
243/// let result = java_manager::locate_java_home();
244/// assert!(result.is_ok() || result.is_err());
245/// ```
246fn java_exec_path_validation(path: &str) -> Result<()> {
247 if path.is_empty() {
248 return Err(JavaLocatorError::new(
249 "Java is not installed or not in the system PATH".into(),
250 ));
251 }
252
253 Ok(())
254}
255
256/// Follows symbolic links to get the real path of an executable.
257///
258/// # Arguments
259///
260/// * `path` - The path to follow
261///
262/// # Returns
263///
264/// A `PathBuf` containing the real path after following all symbolic links
265///
266/// # Examples
267///
268/// ```rust
269/// use std::path::PathBuf;
270///
271/// let real_path = java_manager::locate_java_home().unwrap();
272/// println!("Real path: {:?}", real_path);
273/// ```
274fn follow_symlinks(path: &str) -> PathBuf {
275 let mut test_path = PathBuf::from(path);
276 while let Ok(path) = test_path.read_link() {
277 test_path = if path.is_absolute() {
278 path
279 } else {
280 test_path.pop();
281 test_path.push(path);
282 test_path
283 };
284 }
285 test_path
286}
287
288/// Locates the JVM dynamic library directory.
289///
290/// Searches for the JVM dynamic library (jvm.dll, libjvm.dylib, or libjvm.so)
291/// within the Java installation directory.
292///
293/// # Returns
294///
295/// - `Ok(String)` containing the directory path where the JVM library is located
296/// - `Err(JavaLocatorError)` if the JVM library cannot be found
297///
298/// # Examples
299///
300/// ```rust
301/// use java_manager;
302///
303/// fn main() -> java_manager::Result<()> {
304/// let jvm_lib_path = java_manager::locate_jvm_dyn_library()?;
305/// println!("JVM library directory: {}", jvm_lib_path);
306/// Ok(())
307/// }
308/// ```
309pub fn locate_jvm_dyn_library() -> Result<String> {
310 if cfg!(target_os = "windows") {
311 locate_file("jvm.dll")
312 } else {
313 locate_file("libjvm.*")
314 }
315}
316
317/// Searches for a file within the Java installation directory.
318///
319/// Supports wildcard patterns in the file name.
320///
321/// # Arguments
322///
323/// * `file_name` - The name of the file to search for (supports wildcards)
324///
325/// # Returns
326///
327/// - `Ok(String)` containing the directory path where the file is located
328/// - `Err(JavaLocatorError)` if the file cannot be found
329///
330/// # Examples
331///
332/// ```rust
333/// use java_manager;
334///
335/// fn main() -> java_manager::Result<()> {
336/// // Find libjsig.so
337/// let libjsig_dir = java_manager::locate_file("libjsig.so")?;
338/// println!("libjsig.so directory: {}", libjsig_dir);
339///
340/// // Search with wildcard
341/// let jvm_lib_dir = java_manager::locate_file("libjvm*")?;
342/// println!("JVM library directory: {}", jvm_lib_dir);
343///
344/// Ok(())
345/// }
346/// ```
347pub fn locate_file(file_name: &str) -> Result<String> {
348 let java_home = locate_java_home()?;
349
350 let query = format!("{}/**/{}", Pattern::escape(&java_home), file_name);
351
352 let path = glob(&query)?.filter_map(|x| x.ok()).next().ok_or_else(|| {
353 JavaLocatorError::new(format!(
354 "Could not find the {file_name} library in any subdirectory of {java_home}",
355 ))
356 })?;
357
358 let parent_path = path.parent().unwrap();
359 match parent_path.to_str() {
360 Some(parent_path) => Ok(parent_path.to_owned()),
361 None => Err(JavaLocatorError::new(format!(
362 "Java path {parent_path:?} is invalid utf8"
363 ))),
364 }
365}
366
367#[cfg(test)]
368mod unit_tests {
369 use super::*;
370
371 /// Tests basic Java home location functionality
372 #[test]
373 fn test_locate_java_home() {
374 match locate_java_home() {
375 Ok(path) => {
376 println!("Java home: {}", path);
377 // Verify the path exists
378 assert!(std::path::Path::new(&path).exists());
379 }
380 Err(e) => {
381 println!("Error locating Java home: {}", e);
382 // If Java is not installed, this test should pass (no panic)
383 }
384 }
385 }
386
387 /// Tests JVM dynamic library location functionality
388 #[test]
389 fn test_locate_jvm_dyn_library() {
390 match locate_jvm_dyn_library() {
391 Ok(path) => {
392 println!("JVM library path: {}", path);
393 // Verify the directory exists
394 assert!(std::path::Path::new(&path).exists());
395 }
396 Err(e) => {
397 println!("Error locating JVM library: {}", e);
398 // If Java is not installed or JVM library not found, this test should pass
399 }
400 }
401 }
402
403 /// Tests file searching with wildcards
404 #[test]
405 fn test_locate_file_with_wildcard() {
406 // This test requires a Java installation
407 if let Ok(java_home) = locate_java_home() {
408 // Search for Java executable
409 let java_exec = if cfg!(target_os = "windows") {
410 "java.exe"
411 } else {
412 "java"
413 };
414
415 match locate_file(java_exec) {
416 Ok(path) => {
417 println!("Found {} in: {}", java_exec, path);
418 assert!(std::path::Path::new(&path).exists());
419 }
420 Err(e) => {
421 println!("Error locating {}: {}", java_exec, e);
422 }
423 }
424 }
425 }
426
427 /// Tests platform-specific library name function
428 #[test]
429 fn test_get_jvm_dyn_lib_file_name() {
430 let lib_name = get_jvm_dyn_lib_file_name();
431 println!("Platform JVM library name: {}", lib_name);
432
433 // Verify platform-specific names
434 if cfg!(target_os = "windows") {
435 assert_eq!(lib_name, "jvm.dll");
436 } else if cfg!(target_os = "macos") {
437 assert_eq!(lib_name, "libjvm.dylib");
438 } else {
439 assert_eq!(lib_name, "libjvm.so");
440 }
441 }
442
443 /// Tests symbolic link following functionality
444 #[test]
445 fn test_follow_symlinks() {
446 // Create a test file structure with symlinks
447 let temp_dir = tempfile::tempdir().unwrap();
448 let target_path = temp_dir.path().join("target.txt");
449 std::fs::write(&target_path, "test content").unwrap();
450
451 let link_path = temp_dir.path().join("link.txt");
452 #[cfg(unix)]
453 std::os::unix::fs::symlink(&target_path, &link_path).unwrap();
454
455 #[cfg(windows)]
456 std::os::windows::fs::symlink_file(&target_path, &link_path).unwrap();
457
458 let followed = follow_symlinks(link_path.to_str().unwrap());
459 assert!(followed.exists());
460 }
461}