fix: stop rejecting java installations over an awt probe

This commit is contained in:
LynithDev 2026-09-05 17:37:11 +02:00
parent 84d1cf558c
commit 09073319b1
5 changed files with 7 additions and 131 deletions

View file

@ -1,6 +1,3 @@
import java.io.File;
import java.util.Locale;
public final class JavaInfo {
private static final String[] CHECKED_PROPERTIES = new String[] {"os.arch", "java.version", "java.vendor", "java.home"};
@ -17,77 +14,11 @@ public final class JavaInfo {
}
}
System.out.println("java.awt=" + hasClass("java.awt.Toolkit"));
System.out.println("java.awt.natives=" + hasAwtNatives());
System.out.println("java.awt.link=" + awtLinks());
System.out.println("java.jdk=" + hasCompiler());
System.exit(returnCode);
}
/** Loads the class without initialising it, so no display is ever needed. */
private static boolean hasClass(String name) {
try {
Class.forName(name, false, JavaInfo.class.getClassLoader());
return true;
} catch (Throwable ignored) {
return false;
}
}
private static boolean hasAwtNatives() {
try {
String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
String[] required;
if (os.contains("win")) {
required = new String[] {"awt.dll", "fontmanager.dll"};
} else if (os.contains("mac") || os.contains("darwin")) {
required = new String[] {"libawt_lwawt.dylib", "libfontmanager.dylib"};
} else {
required = new String[] {"libawt_xawt.so", "libfontmanager.so"};
}
for (String name : required) {
if (!findNative(name)) {
return false;
}
}
return true;
} catch (Throwable ignored) {
return false;
}
}
private static boolean findNative(String name) {
File home = new File(System.getProperty("java.home", "."));
File[] roots = new File[] {
new File(home, "lib"),
new File(home, "bin"),
new File(new File(home, "lib"), System.getProperty("os.arch", "")),
};
for (File root : roots) {
if (new File(root, name).isFile()) {
return true;
}
}
return false;
}
private static boolean awtLinks() {
try {
java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getClass();
return true;
} catch (LinkageError broken) {
return false;
} catch (Throwable ignored) {
return true;
}
}
private static boolean hasCompiler() {
try {
return javax.tools.ToolProvider.getSystemJavaCompiler() != null;

View file

@ -6,8 +6,7 @@ java.vendor=Azul Systems, Inc.
```
> [!NOTE]
> The checked-in `JavaInfo.class` predates the `java.home`, `java.awt` and `java.jdk`
> lines and does not print them yet. Recompiling it against a Java 8 target
> (`javac --release 8 JavaInfo.java`) is what turns the probe-side answers on; until
> then [`checker.rs`](../../src/checker.rs) falls back to inspecting the installation
> on disk, which reaches the same verdict for every image seen in the wild.
> `JavaInfo.class` is checked in rather than built, so recompile it against a Java 8
> target (`javac -source 8 -target 8 JavaInfo.java`) whenever the source changes,
> and commit both files together. [`checker.rs`](../../src/checker.rs) reads only the
> `key=value` lines it prints.

View file

@ -68,18 +68,6 @@ pub async fn check_java_runtime(absolute_path: String) -> JavaResult<JavaCheckIn
return Err(JavaError::InvalidJavaPath { path: absolute_path });
};
// Minecraft needs AWT a headless image launches fine and then dies mid-game
if !has_usable_awt(&info) {
tracing::warn!(
path = %absolute_path,
classes = probe_flag(&info, "java.awt"),
natives = probe_flag(&info, "java.awt.natives"),
links = probe_flag(&info, "java.awt.link"),
"java installation has no usable java.awt support"
);
return Err(JavaError::MissingAwtSupport { path: absolute_path });
}
Ok(JavaCheckInfo {
os_arch: info
.get("os.arch")
@ -94,12 +82,6 @@ pub async fn check_java_runtime(absolute_path: String) -> JavaResult<JavaCheckIn
})
}
fn has_usable_awt(info: &HashMap<String, String>) -> bool {
probe_flag(info, "java.awt")
&& probe_flag(info, "java.awt.natives")
&& probe_flag(info, "java.awt.link")
}
fn probe_flag(info: &HashMap<String, String>, key: &str) -> bool {
info.get(key)
.is_some_and(|value| value.eq_ignore_ascii_case("true"))
@ -136,42 +118,12 @@ mod tests {
#[test]
fn a_flag_the_probe_never_printed_is_false() {
assert!(!probe_flag(&info(&[]), "java.awt"));
assert!(!probe_flag(&info(&[]), "java.jdk"));
}
#[test]
fn a_flag_is_read_regardless_of_case() {
assert!(probe_flag(&info(&[("java.awt", "TRUE")]), "java.awt"));
assert!(!probe_flag(&info(&[("java.awt", "false")]), "java.awt"));
}
fn awt(classes: &str, natives: &str, link: &str) -> HashMap<String, String> {
info(&[
("java.awt", classes),
("java.awt.natives", natives),
("java.awt.link", link),
])
}
#[test]
fn a_complete_image_is_usable() {
assert!(has_usable_awt(&awt("true", "true", "true")));
}
#[test]
fn awt_classes_without_their_natives_are_not_usable() {
assert!(!has_usable_awt(&awt("true", "false", "true")));
}
#[test]
fn natives_that_refuse_to_link_are_not_usable() {
assert!(!has_usable_awt(&awt("true", "true", "false")));
}
#[test]
fn every_half_is_required() {
assert!(!has_usable_awt(&info(&[("java.awt", "true")])));
assert!(!has_usable_awt(&info(&[("java.awt.natives", "true")])));
assert!(!has_usable_awt(&info(&[("java.awt.link", "true")])));
assert!(probe_flag(&info(&[("java.jdk", "TRUE")]), "java.jdk"));
assert!(!probe_flag(&info(&[("java.jdk", "false")]), "java.jdk"));
}
}

View file

@ -41,11 +41,6 @@ pub enum JavaError {
#[error("selected path is not a valid java installation: {path}")]
InvalidJavaPath { path: String },
#[error(
"java installation at '{path}' has no usable java.awt (the java.desktop module or its native libraries are missing)"
)]
MissingAwtSupport { path: String },
#[error("selected java is version {found}, but version {expected} is required")]
VersionMismatch { expected: u32, found: u32 },
@ -68,7 +63,6 @@ impl JavaError {
matches!(
self,
JavaError::InvalidJavaPath { .. }
| JavaError::MissingAwtSupport { .. }
| JavaError::ParseVersion { .. }
)
}