1
2#![doc(html_root_url = "https://docs.rs/jvm-find/0.1.0")]
3
4use std::io::ErrorKind;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7use std::ops::Deref;
8
9#[derive(Debug, thiserror::Error)]
12pub enum Error {
13 #[error("Error running Java executable on system path")]
14 JavaExecution(#[source] std::io::Error),
15
16 #[error("Error accessing JavaHome path")]
17 IoError(#[source] std::io::Error),
18
19 #[error("The JavaHome path (possibly obtained from JAVA_HOME environment variable) contained bad data. It could have been outdated, or pointing to something other than a directory. (bad path: {})", .0.display())]
20 BadJavaHomePath(PathBuf),
21
22 #[error("The installed java executable did not report a `java.home` property")]
24 NoJavaHomeProperty,
25
26 #[cfg(feature = "glob")]
27 #[error("Attempted to perform an operation with a non-utf8 path that does not support non-utf8 paths")]
28 PathNotUTF8(PathBuf),
29
30 #[cfg(feature = "glob")]
31 #[error("Unspecified error while globbing JAVA_HOME")]
32 GlobError(#[from] glob::GlobError),
33
34 #[cfg(feature = "glob")]
35 #[error("Unable to find native library file within JAVA_HOME")]
36 NoNativeLibrary,
37}
38
39type Result<T> = std::result::Result<T, Error>;
40
41pub const NATIVE_LIBRARY_FILENAME_WIN: &str = "jvm.dll";
43pub const NATIVE_LIBRARY_FILENAME_LIN: &str = "libjvm.so";
45pub const NATIVE_LIBRARY_FILENAME_MAC: &str = "libjli.dylib";
49#[cfg(target_os = "windows")]
53pub const NATIVE_LIBRARY_FILENAME: &str = NATIVE_LIBRARY_FILENAME_WIN;
54#[cfg(any(
56 target_os = "freebsd",
57 target_os = "linux",
58 target_os = "netbsd",
59 target_os = "openbsd"
60))]
61pub const NATIVE_LIBRARY_FILENAME: &str = NATIVE_LIBRARY_FILENAME_LIN;
62#[cfg(target_os = "macos")]
64pub const NATIVE_LIBRARY_FILENAME: &str = NATIVE_LIBRARY_FILENAME_MAC;
65
66#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub struct JavaHome {
69 pub path: PathBuf,
70}
71impl JavaHome {
72 pub const ENV_VAR: &'static str = "JAVA_HOME";
74
75 pub fn find_home() -> Result<Self> {
80 std::env::var_os(JavaHome::ENV_VAR)
81 .filter(|var| !var.is_empty())
82 .map(PathBuf::from)
83 .map(|path| Ok(JavaHome { path }))
84 .unwrap_or_else(JavaHome::find_active_home)
85 }
86
87 pub fn find_valid_home() -> Result<Self> {
92 std::env::var_os(JavaHome::ENV_VAR)
93 .filter(|var| !var.is_empty())
94 .map(PathBuf::from)
95 .map(|path| {
96 match path.metadata() {
97 Err(e) if e.kind() != ErrorKind::NotFound => Some(Err(Error::IoError(e))),
99
100 Ok(meta) if meta.is_dir() => Some(Ok(JavaHome { path })),
102
103 _ => None
105 }
106 })
107 .flatten().transpose()?.map(Ok)
108
109 .unwrap_or_else(JavaHome::find_active_home)
111 }
112
113 pub fn find_active_home() -> Result<Self> {
118 let output = Command::new("java")
122 .arg("-XshowSettings:properties")
123 .arg("-version")
124 .output()
125 .map_err(|e| Error::JavaExecution(e))?;
126
127 let stdout = String::from_utf8_lossy(&output.stdout);
128 let stderr = String::from_utf8_lossy(&output.stderr);
129 let java_home = stdout.lines()
130 .chain(stderr.lines())
131 .filter(|line| line.contains("java.home"))
132 .find_map(|line| line.find('=').map(|i| line[i..].trim()));
133
134 match java_home {
135 Some(path) => Ok(JavaHome { path: PathBuf::from(path) }),
136 None => Err(Error::NoJavaHomeProperty),
137 }
138 }
139
140 #[cfg(feature = "glob")]
161 pub fn include(&self) -> Result<Option<Vec<PathBuf>>> {
162 let base = self.join("include");
163
164 match base.metadata() {
165 Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
166 Err(e) => Err(Error::IoError(e)),
167 Ok(meta) if meta.is_dir() => Ok(Some({
168 let escaped = glob::Pattern::escape(base.to_str().ok_or_else(|| Error::PathNotUTF8(base.clone()))?);
169 let pattern = escaped + "/**/*/";
170
171 std::iter::once(Ok(base.clone())) .chain(glob::glob(&pattern).unwrap()) .collect::<std::result::Result<Vec<PathBuf>, glob::GlobError>>()? })),
175 Ok(_) => Err(Error::BadJavaHomePath(self.path.clone())),
176 }
177 }
178
179 #[cfg(feature = "glob")]
181 pub fn native_library(&self) -> Result<PathBuf> {
182 let base = &self.path;
183 let escaped = glob::Pattern::escape(base.to_str().ok_or_else(|| Error::PathNotUTF8(base.clone()))?);
184 let pattern = escaped + "/**/" + NATIVE_LIBRARY_FILENAME;
185 Ok(glob::glob(&pattern)
186 .unwrap() .next().ok_or(Error::NoNativeLibrary)??)
188 }
189
190 #[cfg(feature = "glob")]
192 pub fn find_file(&self, file: &str) -> Result<Option<PathBuf>> {
193 let base = &self.path;
194 let base_escaped = glob::Pattern::escape(base.to_str().ok_or_else(|| Error::PathNotUTF8(base.clone()))?);
195 let file_escaped = glob::Pattern::escape(file);
196 let pattern = base_escaped + "/**/" + &file_escaped;
197 glob::glob(&pattern)
198 .unwrap() .next()
200 .transpose()
201 .map_err(Error::GlobError)
202 }
203
204 #[cfg(feature = "glob")]
206 pub fn find_folder(&self, folder: &str) -> Result<Option<PathBuf>> {
207 let base = &self.path;
208 let base_escaped = glob::Pattern::escape(base.to_str().ok_or_else(|| Error::PathNotUTF8(base.clone()))?);
209 let fold_escaped = glob::Pattern::escape(folder);
210 let pattern = base_escaped + "/**/" + &fold_escaped + "/";
211 glob::glob(&pattern)
212 .unwrap() .next()
214 .transpose()
215 .map_err(Error::GlobError)
216 }
217}
218impl Deref for JavaHome {
219 type Target = Path;
220 fn deref(&self) -> &Self::Target {
221 &self.path
222 }
223}