java_runtimes/lib.rs
1//! `java-runtimes` is a rust library for detecting java runtimes in current system.
2//!
3//! * To detect java runtimes, see [`detector`]
4//!
5//! # Examples
6//!
7
8//! Detect Java runtime from environment variables
9//!
10//! ```rust
11//! use java_runtimes::detector;
12//!
13//! let runtimes = detector::detect_java_in_environments();
14//! println!("Detected Java runtimes: {:?}", runtimes);
15//! ```
16//!
17//! Detect Java runtimes recursively within multiple paths
18//!
19//! ```rust
20//! use java_runtimes::detector;
21//!
22//! let runtimes = detector::detect_java_in_paths(&[
23//! "/usr".as_ref(),
24//! "/opt".as_ref(),
25//! ], 2);
26//! println!("Detected Java runtimes in multiple paths: {:?}", runtimes);
27//! ```
28
29pub mod detector;
30pub mod error;
31
32use crate::error::{Error, ErrorKind};
33use regex::Regex;
34use serde::{Deserialize, Serialize};
35use std::env;
36use std::ffi::OsString;
37use std::path::{Path, PathBuf};
38use std::process::Command;
39
40/// Struct [`JavaRuntime`] Represents a java runtime in specific path.
41///
42/// To detect java runtimes from specific path, see [`detector`]
43#[derive(Serialize, Deserialize, Debug)]
44pub struct JavaRuntime {
45 os: String,
46 path: PathBuf,
47 version_string: String,
48}
49
50impl JavaRuntime {
51 /// Used to match the version string in the command output
52 ///
53 const VERSION_PATTERN: &'static str = r#".*"((\d+)\.(\d+)([\d._]+)?)".*"#;
54 /// Create a [`JavaRuntime`] object from the path of java executable file
55 ///
56 /// It executes command `java -version` to get the version information
57 ///
58 /// # Parameters
59 ///
60 /// * `path` Path to java executable file.
61 ///
62 /// # Examples
63 ///
64 /// ```rust
65 /// use java_runtimes::JavaRuntime;
66 ///
67 /// let _ = JavaRuntime::from_executable(r"D:\java\jdk-17.0.4.1\bin\java.exe".as_ref());
68 /// let _ = JavaRuntime::from_executable(r"../../runtimes/jdk-1.8.0_291/bin/java".as_ref());
69 /// ```
70 pub fn from_executable(path: &Path) -> Result<Self, Error> {
71 let mut java = Self {
72 os: env::consts::OS.to_string(),
73 path: path.to_path_buf(),
74 version_string: String::new(),
75 };
76 java.update()?;
77 Ok(java)
78 }
79
80 /// Mannually create a [`JavaRuntime`] instance, without checking if it's available
81 ///
82 /// # Parameters
83 ///
84 /// * `os` Got from [`env::consts::OS`]
85 /// * `path` The path of java executable file, can be either relative or absolute
86 /// * `version_string` can be like `"17.0.4.1"` or the output of command `java -version`
87 ///
88 /// # Examples
89 ///
90 /// ```rust
91 /// use java_runtimes::JavaRuntime;
92 /// use std::env;
93 /// use std::path::Path;
94 ///
95 /// let java_exe_path = Path::new("../java/jdk-17.0.4.1/bin/java");
96 /// let version_outputs = r#"java version "17.0.4.1" 2022-08-18 LTS
97 /// Java(TM) SE Runtime Environment (build 17.0.4.1+1-LTS-2)
98 /// Java HotSpot(TM) 64-Bit Server VM (build 17.0.4.1+1-LTS-2, mixed mode, sharing)
99 /// "#;
100 /// let runtime = JavaRuntime::new(env::consts::OS, java_exe_path, version_outputs).unwrap();
101 /// assert_eq!(runtime.get_version_string(), "17.0.4.1");
102 /// assert!(runtime.is_same_os());
103 /// ```
104 pub fn new(os: &str, path: &Path, version_string: &str) -> Result<Self, Error> {
105 let version_string = Self::extract_version(version_string)?;
106 Ok(Self {
107 os: os.to_string(),
108 path: path.to_path_buf(),
109 version_string: version_string.to_string(),
110 })
111 }
112
113 /// Get the operating system of the java runtime
114 ///
115 /// The os string comes from [`env::consts::OS`] when this object was created.
116 pub fn get_os(&self) -> &str {
117 &self.os
118 }
119 pub fn is_windows(&self) -> bool {
120 self.os == "windows"
121 }
122 /// Get the path of java executable file
123 ///
124 /// It can be absolute or relative, depends on how you created it.
125 ///
126 /// # Examples
127 ///
128 /// * `D:\Java\jdk-17.0.4.1\bin\java.exe` (Windows, absolute)
129 /// * `../../runtimes/jdk-1.8.0_291/bin/java` (Linux, relative)
130 pub fn get_executable(&self) -> &Path {
131 &self.path
132 }
133
134 /// Returns `true` if the `Path` has a root.
135 ///
136 /// Refer to [`Path::has_root`]
137 ///
138 /// # Examples
139 ///
140 /// ```rust
141 /// use java_runtimes::JavaRuntime;
142 ///
143 /// let runtime = JavaRuntime::new("linux", "/jdk/bin/java".as_ref(), "21.0.3").unwrap();
144 /// assert!(runtime.has_root());
145 ///
146 /// let runtime = JavaRuntime::new("windows", r"D:\jdk\bin\java.exe".as_ref(), "21.0.3").unwrap();
147 /// assert!(runtime.has_root());
148 ///
149 /// let runtime = JavaRuntime::new("linux", "../jdk/bin/java".as_ref(), "21.0.3").unwrap();
150 /// assert!(!runtime.has_root());
151 ///
152 /// let runtime = JavaRuntime::new("windows", r"..\jdk\bin\java.exe".as_ref(), "21.0.3").unwrap();
153 /// assert!(!runtime.has_root());
154 /// ```
155 pub fn has_root(&self) -> bool {
156 self.path.has_root()
157 }
158
159 /// Get the version string
160 ///
161 /// # Examples
162 ///
163 /// ```rust
164 /// use java_runtimes::JavaRuntime;
165 ///
166 /// let runtime = JavaRuntime::new("linux", "/jdk/bin/java".as_ref(), "21.0.3").unwrap();
167 /// assert_eq!(runtime.get_version_string(), "21.0.3");
168 /// ```
169 pub fn get_version_string(&self) -> &str {
170 &self.version_string
171 }
172
173 /// Check if this is the same os as current
174 pub fn is_same_os(&self) -> bool {
175 self.os == env::consts::OS
176 }
177
178 /// Create a new [`JavaRuntime`] with absolute path.
179 ///
180 /// # Errors
181 ///
182 /// Returns an [`Err`] if the current working directory value is invalid. Refer to [`env::current_dir`]
183 ///
184 /// Possible cases:
185 ///
186 /// * Current directory does not exist.
187 /// * There are insufficient permissions to access the current directory.
188 pub fn to_absolute(&self) -> Result<Self, Error> {
189 let cwd = env::current_dir().or(Err(Error::new(ErrorKind::InvalidWorkDir)))?;
190 let path_absolute = self.path.join(cwd);
191 let new_runtime = Self::new(&self.os, &path_absolute, &self.version_string)?;
192 Ok(new_runtime)
193 }
194
195 /// Try executing `java -version` and parse the output to get the version.
196 ///
197 /// If success, it will update the version value in this [`JavaRuntime`] instance.
198 pub fn update(&mut self) -> Result<(), Error> {
199 if !Self::looks_like_java_executable_file(&self.path) {
200 return Err(Error::new(ErrorKind::LooksNotLikeJavaExecutableFile(
201 self.path.clone(),
202 )));
203 }
204
205 let output = Command::new(&self.path)
206 .arg("-version")
207 .output()
208 .map_err(|err| Error::new(ErrorKind::JavaOutputFailed(err)))?;
209
210 if output.status.success() {
211 let version_output = String::from_utf8_lossy(&output.stderr).to_string();
212 self.version_string = Self::extract_version(&version_output)?;
213 Ok(())
214 } else {
215 Err(Error::new(ErrorKind::GettingJavaVersionFailed(
216 self.path.clone(),
217 )))
218 }
219 }
220
221 /// Test if this runtime is available currently
222 ///
223 /// It executes command `java -version` to see if it works
224 pub fn is_available(&self) -> bool {
225 self.is_same_os() && Self::from_executable(&self.path).is_ok()
226 }
227
228 /// Parse version string
229 ///
230 /// # Return
231 ///
232 /// `(version_string, version_major)`
233 ///
234 /// # Examples
235 ///
236 /// ```rust
237 /// use java_runtimes::JavaRuntime;
238 ///
239 /// assert_eq!(JavaRuntime::extract_version("1.8.0_333").unwrap(), "1.8.0_333");
240 /// assert_eq!(JavaRuntime::extract_version("17.0.4.1").unwrap(), "17.0.4.1");
241 /// assert_eq!(JavaRuntime::extract_version("\"17.0.4.1").unwrap(), "17.0.4.1");
242 /// assert_eq!(JavaRuntime::extract_version("java version \"17.0.4.1\"").unwrap(), "17.0.4.1");
243 /// ```
244 pub fn extract_version(version_string: &str) -> Result<String, Error> {
245 Ok(Regex::new(Self::VERSION_PATTERN)
246 .unwrap()
247 .captures(&format!("\"{}\"", &version_string))
248 .ok_or(Error::new(ErrorKind::NoJavaVersionStringFound))?
249 .get(1)
250 .ok_or(Error::new(ErrorKind::NoJavaVersionStringFound))?
251 .as_str()
252 .to_string())
253 }
254
255 /// Check if the given path looks like a java executable file
256 ///
257 /// The file must exists.
258 ///
259 /// The given path must be `**/bin/java.exe` in windows, or `**/bin/java` in unix
260 fn looks_like_java_executable_file(path: &Path) -> bool {
261 if !path.is_file() {
262 return false;
263 }
264 // to absolute
265 let path_absolute = match path.canonicalize() {
266 Ok(path) => path,
267 _ => return false,
268 };
269 // check file name
270 if let Some(file_name) = path_absolute.file_name() {
271 if file_name == Self::get_java_executable_name() {
272 // check parent name
273 if let Some(parent) = path_absolute.parent() {
274 if let Some(dir_name) = parent.file_name() {
275 if dir_name == "bin" {
276 return true;
277 }
278 }
279 }
280 }
281 }
282 false
283 }
284
285 /// # Examples
286 /// * `java.exe` (windows)
287 /// * `java` (linux)
288 fn get_java_executable_name() -> OsString {
289 let mut java_exe = OsString::from("java");
290 java_exe.push(env::consts::EXE_SUFFIX);
291 java_exe
292 }
293}
294impl Clone for JavaRuntime {
295 /// # Examples
296 ///
297 /// ```rust
298 /// use java_runtimes::JavaRuntime;
299 ///
300 /// let r1 = JavaRuntime::new("linux", "/jdk/bin/java".as_ref(), "21.0.3").unwrap();
301 /// let r2 = r1.clone();
302 ///
303 /// assert_eq!(r1, r2);
304 /// ```
305 fn clone(&self) -> Self {
306 Self {
307 os: self.os.clone(),
308 path: self.path.clone(),
309 version_string: self.version_string.clone(),
310 }
311 }
312 /// # Examples
313 ///
314 /// ```rust
315 /// use java_runtimes::JavaRuntime;
316 ///
317 /// let mut r1 = JavaRuntime::new("windows", "/jdk/bin/java".as_ref(), "21.0.3").unwrap();
318 /// let r2 = JavaRuntime::new("windows", r"D:\jdk\bin\java.exe".as_ref(), "21.0.3").unwrap();
319 ///
320 /// r1.clone_from(&r2);
321 /// assert_eq!(r1, r2);
322 /// ```
323 fn clone_from(&mut self, source: &Self) {
324 self.os = source.os.clone();
325 self.path = source.path.clone();
326 self.version_string = source.version_string.clone();
327 }
328}
329
330impl PartialEq for JavaRuntime {
331 /// # Examples
332 ///
333 /// ```rust
334 /// use java_runtimes::JavaRuntime;
335 ///
336 /// let r1 = JavaRuntime::new("linux", "/jdk/bin/java".as_ref(), "21.0.3").unwrap();
337 /// let r2 = JavaRuntime::new("linux", "/jdk/bin/java".as_ref(), "21.0.3").unwrap();
338 /// let r3 = JavaRuntime::new("windows", r"D:\jdk\bin\java.exe".as_ref(), "21.0.3").unwrap();
339 /// let r4 = JavaRuntime::new("windows", r"D:\jdk-17\bin\java.exe".as_ref(), "21.0.3").unwrap();
340 ///
341 /// assert_eq!(r1, r2);
342 /// assert_ne!(r1, r3);
343 /// assert_ne!(r2, r3);
344 /// assert_ne!(r2, r4);
345 /// assert_ne!(r3, r4);
346 /// ```
347 fn eq(&self, other: &Self) -> bool {
348 self.os == other.os && self.path == other.path
349 }
350
351 fn ne(&self, other: &Self) -> bool {
352 !self.eq(other)
353 }
354}