feat: detect installed Java runtime

This commit is contained in:
emil28092005
2026-09-06 00:47:01 +03:00
parent d8a06bcee6
commit f4cfe601fd
5 changed files with 127 additions and 1 deletions
+89
View File
@@ -0,0 +1,89 @@
use serde::Serialize;
use std::{env, path::PathBuf, process::Command};
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JavaInstallation {
pub executable: String,
pub major: u8,
pub version: String,
}
/// Finds a usable Java runtime without modifying the machine.
///
/// The launcher will later use this result to decide whether Java 21 needs to
/// be bundled. We deliberately check JAVA_HOME first: it is explicit and makes
/// development installations predictable across all supported platforms.
pub fn detect() -> Option<JavaInstallation> {
candidates().into_iter().find_map(check_candidate)
}
fn candidates() -> Vec<PathBuf> {
let executable = if cfg!(target_os = "windows") {
"java.exe"
} else {
"java"
};
let mut candidates = Vec::new();
if let Some(java_home) = env::var_os("JAVA_HOME") {
candidates.push(PathBuf::from(java_home).join("bin").join(executable));
}
candidates.push(PathBuf::from(executable));
candidates
}
fn check_candidate(candidate: PathBuf) -> Option<JavaInstallation> {
let output = Command::new(&candidate).arg("-version").output().ok()?;
if !output.status.success() {
return None;
}
let source = format!(
"{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let version = parse_version(&source)?;
let major = parse_major(&version)?;
Some(JavaInstallation {
executable: candidate.display().to_string(),
major,
version,
})
}
fn parse_version(source: &str) -> Option<String> {
let marker = "version \"";
let start = source.find(marker)? + marker.len();
let remainder = &source[start..];
let end = remainder.find('"')?;
Some(remainder[..end].to_owned())
}
fn parse_major(version: &str) -> Option<u8> {
let mut segments = version.split('.');
let first = segments.next()?.parse::<u8>().ok()?;
if first == 1 {
segments.next()?.parse::<u8>().ok()
} else {
Some(first)
}
}
#[cfg(test)]
mod tests {
use super::{parse_major, parse_version};
#[test]
fn parses_modern_java_version() {
let output = "openjdk version \"21.0.8\" 2025-07-15";
assert_eq!(parse_version(output).as_deref(), Some("21.0.8"));
assert_eq!(parse_major("21.0.8"), Some(21));
}
#[test]
fn parses_legacy_java_version() {
assert_eq!(parse_major("1.8.0_452"), Some(8));
}
}
+8
View File
@@ -1,3 +1,4 @@
mod java;
mod manifest;
mod profile;
mod settings;
@@ -29,6 +30,12 @@ fn native_host(app: AppHandle) -> Result<NativeHost, String> {
})
}
/// Detects an existing Java installation. This is read-only and never downloads Java.
#[tauri::command]
fn detect_java() -> Option<java::JavaInstallation> {
java::detect()
}
/// Validates an untrusted profile manifest before any file is downloaded.
#[tauri::command]
fn validate_manifest(manifest_json: String) -> Result<(), String> {
@@ -97,6 +104,7 @@ pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
native_host,
detect_java,
validate_manifest,
inspect_profile,
sync_profile,