Skip to content

Commit

Permalink
Jre detection (#54)
Browse files Browse the repository at this point in the history
* 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
thesuzerain and thesuzerain authored Mar 28, 2023
1 parent 5363cd0 commit 8512b45
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 1 deletion.
15 changes: 14 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions theseus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ thiserror = "1.0"
tracing = "0.1"
tracing-error = "0.2"


async-tungstenite = { version = "0.17", features = ["tokio-runtime", "tokio-native-tls"] }
futures = "0.3"
once_cell = "1.9.0"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
lazy_static = "1.4.0"

[target.'cfg(windows)'.dependencies]
winreg = "0.11.0"
dunce = "1.0.3"

[dev-dependencies]
argh = "0.1.6"
Expand Down
240 changes: 240 additions & 0 deletions theseus/src/util/jre.rs
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),
}
1 change: 1 addition & 0 deletions theseus/src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Theseus utility functions
pub mod fetch;
pub mod jre;
pub mod platform;

/// Wrap a builder which uses a mut reference into one which outputs an owned value
Expand Down

0 comments on commit 8512b45

Please sign in to comment.