-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial jre detection commit * added WinReg searching * added windows support + refactored * Cargo lock * fixed linux + added canonicalization * fixed bug in mac * Added dunce; handling UNC paths better for canonicalization * missed cargo lock * removed tests, added comments * cargo fmt * removed redundant mac address, loop over folder --------- Co-authored-by: Wyatt <wyatt@modrinth.com>
- Loading branch information
1 parent
5363cd0
commit 8512b45
Showing
4 changed files
with
261 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
use lazy_static::lazy_static; | ||
use regex::Regex; | ||
use std::collections::HashSet; | ||
use std::env; | ||
use std::path::PathBuf; | ||
use std::process::Command; | ||
|
||
#[cfg(target_os = "windows")] | ||
use winreg::{ | ||
enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, KEY_WOW64_64KEY}, | ||
RegKey, | ||
}; | ||
|
||
// Uses dunce canonicalization to resolve symlinks without UNC prefixes | ||
#[cfg(target_os = "windows")] | ||
use dunce::canonicalize; | ||
#[cfg(not(target_os = "windows"))] | ||
use std::fs::canonicalize; | ||
|
||
#[derive(Debug, PartialEq, Eq, Hash)] | ||
pub struct JavaVersion { | ||
pub path: String, | ||
pub version: String, | ||
} | ||
|
||
// Entrypoint function (Windows) | ||
// Returns a Vec of unique JavaVersions from the PATH, Windows Registry Keys and common Java locations | ||
#[cfg(target_os = "windows")] | ||
#[tracing::instrument] | ||
pub fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> { | ||
// Use HashSet to avoid duplicates | ||
let mut jres = HashSet::new(); | ||
|
||
// Add JRES directly on PATH | ||
jres.extend(get_all_jre_path()?); | ||
|
||
// Hard paths for locations for commonly installed .exes | ||
let java_paths = [r"C:/Program Files/Java", r"C:/Program Files (x86)/Java"]; | ||
for java_path in java_paths { | ||
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue }; | ||
for java_subpath in java_subpaths { | ||
let path = java_subpath?.path(); | ||
if let Some(j) = | ||
check_java_at_filepath(PathBuf::from(path).join("bin")) | ||
{ | ||
jres.insert(j); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// Windows Registry Keys | ||
let key_paths = [ | ||
r"SOFTWARE\JavaSoft\Java Runtime Environment", // Oracle | ||
r"SOFTWARE\JavaSoft\Java Development Kit", | ||
r"SOFTWARE\\JavaSoft\\JRE", // Oracle | ||
r"SOFTWARE\\JavaSoft\\JDK", | ||
r"SOFTWARE\\Eclipse Foundation\\JDK", // Eclipse | ||
r"SOFTWARE\\Eclipse Adoptium\\JRE", // Eclipse | ||
r"SOFTWARE\\Eclipse Foundation\\JDK", // Eclipse | ||
r"SOFTWARE\\Microsoft\\JDK", // Microsoft | ||
]; | ||
for key in key_paths { | ||
if let Ok(jre_key) = RegKey::predef(HKEY_LOCAL_MACHINE) | ||
.open_subkey_with_flags(key, KEY_READ | KEY_WOW64_32KEY) | ||
{ | ||
jres.extend(get_all_jre_winregkey(jre_key)?); | ||
} | ||
if let Ok(jre_key) = RegKey::predef(HKEY_LOCAL_MACHINE) | ||
.open_subkey_with_flags(key, KEY_READ | KEY_WOW64_64KEY) | ||
{ | ||
jres.extend(get_all_jre_winregkey(jre_key)?); | ||
} | ||
} | ||
|
||
Ok(jres.into_iter().collect()) | ||
} | ||
|
||
#[cfg(target_os = "windows")] | ||
#[tracing::instrument] | ||
pub fn get_all_jre_winregkey( | ||
jre_key: RegKey, | ||
) -> Result<HashSet<JavaVersion>, JREError> { | ||
let mut jres = HashSet::new(); | ||
|
||
for subkey in jre_key.enum_keys() { | ||
let subkey = subkey?; | ||
let subkey = jre_key.open_subkey(subkey)?; | ||
|
||
let subkey_value_names = | ||
[r"JavaHome", r"InstallationPath", r"\\hotspot\\MSI"]; | ||
|
||
for subkey_value in subkey_value_names { | ||
let path: Result<String, std::io::Error> = | ||
subkey.get_value(subkey_value); | ||
let Ok(path) = path else {continue}; | ||
if let Some(j) = | ||
check_java_at_filepath(PathBuf::from(path).join("bin")) | ||
{ | ||
jres.insert(j); | ||
break; | ||
} | ||
} | ||
} | ||
Ok(jres) | ||
} | ||
|
||
// Entrypoint function (Mac) | ||
// Returns a Vec of unique JavaVersions from the PATH, and common Java locations | ||
#[cfg(target_os = "macos")] | ||
#[tracing::instrument] | ||
pub fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> { | ||
// Use HashSet to avoid duplicates | ||
let mut jres = HashSet::new(); | ||
|
||
// Add JREs directly on PATH | ||
jres.extend(get_all_jre_path()?); | ||
|
||
// Hard paths for locations for commonly installed .exes | ||
let java_paths = [ | ||
r"/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java", | ||
r"/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home", | ||
r"/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands", | ||
]; | ||
for path in java_paths { | ||
if let Some(j) = check_java_at_filepath(PathBuf::from(path).join("bin")) | ||
{ | ||
jres.insert(j); | ||
break; | ||
} | ||
} | ||
Ok(jres.into_iter().collect()) | ||
} | ||
|
||
// Entrypoint function (Linux) | ||
// Returns a Vec of unique JavaVersions from the PATH, and common Java locations | ||
#[cfg(target_os = "linux")] | ||
#[tracing::instrument] | ||
pub fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> { | ||
// Use HashSet to avoid duplicates | ||
let mut jres = HashSet::new(); | ||
|
||
// Add JREs directly on PATH | ||
jres.extend(get_all_jre_path()?); | ||
|
||
// Hard paths for locations for commonly installed locations | ||
let java_paths = [ | ||
r"/usr", | ||
r"/usr/java", | ||
r"/usr/lib/jvm", | ||
r"/usr/lib64/jvm", | ||
r"/opt/jdk", | ||
r"/opt/jdks", | ||
]; | ||
for path in java_paths { | ||
if let Some(j) = | ||
check_java_at_filepath(PathBuf::from(path).join("jre").join("bin")) | ||
{ | ||
jres.insert(j); | ||
break; | ||
} | ||
if let Some(j) = check_java_at_filepath(PathBuf::from(path).join("bin")) | ||
{ | ||
jres.insert(j); | ||
break; | ||
} | ||
} | ||
Ok(jres.into_iter().collect()) | ||
} | ||
|
||
#[tracing::instrument] | ||
pub fn get_all_jre_path() -> Result<HashSet<JavaVersion>, JREError> { | ||
// Iterate over values in PATH variable, where accessible JREs are referenced | ||
let paths = env::var("PATH")?; | ||
let paths = env::split_paths(&paths); | ||
|
||
let mut jres = HashSet::new(); | ||
for path in paths { | ||
if let Some(j) = check_java_at_filepath(path) { | ||
jres.insert(j); | ||
} | ||
} | ||
Ok(jres) | ||
} | ||
|
||
#[cfg(target_os = "windows")] | ||
#[allow(dead_code)] | ||
const JAVA_BIN: &'static str = "java.exe"; | ||
|
||
#[cfg(not(target_os = "windows"))] | ||
#[allow(dead_code)] | ||
const JAVA_BIN: &'static str = "java"; | ||
|
||
// For example filepath 'path', attempt to resolve it and get a Java version at this path | ||
// If no such path exists, or no such valid java at this path exists, returns None | ||
#[tracing::instrument] | ||
pub fn check_java_at_filepath(path: PathBuf) -> Option<JavaVersion> { | ||
// Attempt to canonicalize the potential java filepath | ||
// If it fails, this path does not exist and None is returned (no Java here) | ||
let Ok(path) = canonicalize(path) else { return None }; | ||
let Some(path_str) = path.to_str() else { return None }; | ||
|
||
// Checks for existence of Java at this filepath | ||
let java = path.join(JAVA_BIN); | ||
if !java.exists() { | ||
return None; | ||
}; | ||
|
||
// Run 'java -version' using found java binary | ||
let output = Command::new(&java).arg("-version").output().ok()?; | ||
let stderr = String::from_utf8_lossy(&output.stderr); | ||
|
||
// Match: version "1.8.0_361" | ||
// Extracting version numbers | ||
lazy_static! { | ||
static ref JAVA_VERSION_CAPTURE: Regex = | ||
Regex::new(r#"version "([\d\._]+)""#).unwrap(); | ||
} | ||
|
||
// Extract version info from it | ||
if let Some(captures) = JAVA_VERSION_CAPTURE.captures(&stderr) { | ||
if let Some(version) = captures.get(1) { | ||
let path = path_str.to_string(); | ||
return Some(JavaVersion { | ||
path, | ||
version: version.as_str().to_string(), | ||
}); | ||
} | ||
} | ||
None | ||
} | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum JREError { | ||
#[error("Command error : {0}")] | ||
IOError(#[from] std::io::Error), | ||
|
||
#[error("Env error: {0}")] | ||
EnvError(#[from] env::VarError), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters