actually properly check for AWT
This commit is contained in:
parent
26c5aa8c49
commit
2a711c1ef3
14 changed files with 215 additions and 22 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT absolute_path, major, version, vendor, os_arch, is_jdk as \"is_jdk: bool\"\n FROM java_versions\n ORDER BY major DESC, version DESC\n ",
|
||||
"query": "\n SELECT absolute_path, major, version, vendor, os_arch, is_jdk as \"is_jdk: bool\", probe_version\n FROM java_versions\n ORDER BY major DESC, version DESC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
|
@ -68,6 +68,17 @@
|
|||
"name": "is_jdk"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_version",
|
||||
"ordinal": 6,
|
||||
"type_info": "Integer",
|
||||
"origin": {
|
||||
"Table": {
|
||||
"table": "java_versions",
|
||||
"name": "probe_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
|
@ -79,8 +90,9 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0d9fb5d2fa8a788770e946241ea2f62f4b794c0fdda3ccbde49384689911aeba"
|
||||
"hash": "0c3651cf175cb900c2466f0e97beca338b95637d43d9d067b974327f7720637b"
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n\t\tSELECT absolute_path, major, version, vendor, os_arch, is_jdk as \"is_jdk: bool\"\n\t\tFROM java_versions\n\t\tWHERE major = ?\n\t\tORDER BY version DESC\n\t\tLIMIT 1\n\t\t",
|
||||
"query": "\n\t\tSELECT absolute_path, major, version, vendor, os_arch, is_jdk as \"is_jdk: bool\", probe_version\n\t\tFROM java_versions\n\t\tWHERE major = ?\n\t\tORDER BY version DESC\n\t\tLIMIT 1\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
|
@ -68,6 +68,17 @@
|
|||
"name": "is_jdk"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_version",
|
||||
"ordinal": 6,
|
||||
"type_info": "Integer",
|
||||
"origin": {
|
||||
"Table": {
|
||||
"table": "java_versions",
|
||||
"name": "probe_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
|
@ -79,8 +90,9 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "33771b7003e64c8f19d15d663de773afa568d626c60a37bbc32cc932746ad49b"
|
||||
"hash": "14ab205c5b8d4c1c435814e4b7a155acbae41a67925b6b179b82dea1758d621a"
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT absolute_path, major, version, vendor, os_arch, is_jdk as \"is_jdk: bool\"\n FROM java_versions\n WHERE absolute_path = ?\n ",
|
||||
"query": "\n SELECT absolute_path, major, version, vendor, os_arch, is_jdk as \"is_jdk: bool\", probe_version\n FROM java_versions\n WHERE absolute_path = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
|
@ -68,6 +68,17 @@
|
|||
"name": "is_jdk"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_version",
|
||||
"ordinal": 6,
|
||||
"type_info": "Integer",
|
||||
"origin": {
|
||||
"Table": {
|
||||
"table": "java_versions",
|
||||
"name": "probe_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
|
@ -79,8 +90,9 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f5992c9cec6a6d7015cda4c04b463bd5fd9c42ac38b434fa8a0c0fe4f7517cf2"
|
||||
"hash": "331bee3da454f39a8a2cc8ed4e5338b743a7cd033fdef023c4b3b93827248be1"
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n\t\tINSERT INTO java_versions (absolute_path, major, version, vendor, os_arch, is_jdk)\n\t\tVALUES (?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(absolute_path) DO UPDATE SET\n\t\t\tmajor = excluded.major,\n\t\t\tversion = excluded.version,\n\t\t\tvendor = excluded.vendor,\n\t\t\tos_arch = excluded.os_arch,\n\t\t\tis_jdk = excluded.is_jdk\n\t\tRETURNING absolute_path, major, version, vendor, os_arch, is_jdk as \"is_jdk: bool\"\n\t\t",
|
||||
"query": "\n\t\tINSERT INTO java_versions (absolute_path, major, version, vendor, os_arch, is_jdk, probe_version)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(absolute_path) DO UPDATE SET\n\t\t\tmajor = excluded.major,\n\t\t\tversion = excluded.version,\n\t\t\tvendor = excluded.vendor,\n\t\t\tos_arch = excluded.os_arch,\n\t\t\tis_jdk = excluded.is_jdk,\n\t\t\tprobe_version = excluded.probe_version\n\t\tRETURNING absolute_path, major, version, vendor, os_arch, is_jdk as \"is_jdk: bool\", probe_version\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
|
@ -68,10 +68,21 @@
|
|||
"name": "is_jdk"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "probe_version",
|
||||
"ordinal": 6,
|
||||
"type_info": "Integer",
|
||||
"origin": {
|
||||
"Table": {
|
||||
"table": "java_versions",
|
||||
"name": "probe_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
"Right": 7
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
|
|
@ -79,8 +90,9 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "010f5dcda59ec32eaad1b9c3e2e9807f547a1376372c8b201653a36d6dd14cbc"
|
||||
"hash": "f5dbcf7798afb495efe228a4ba7d1ddd9904cc25f34bef3c16f29cfb141158f0"
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ fn row_to_runtime(row: JavaVersionRow) -> JavaRuntime {
|
|||
vendor: JavaVendor::from_str(&row.vendor).unwrap_or(JavaVendor::Other(row.vendor)),
|
||||
os_arch: row.os_arch,
|
||||
is_jdk: row.is_jdk,
|
||||
probe_version: row.probe_version.try_into().unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ impl JavaStore for SqlJavaStore {
|
|||
&runtime.vendor.to_string(),
|
||||
&runtime.os_arch,
|
||||
runtime.is_jdk,
|
||||
runtime.probe_version,
|
||||
)
|
||||
.await
|
||||
.map_err(StoreError::new)?;
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `java_versions` ADD COLUMN `probe_version` INTEGER NOT NULL DEFAULT 0;
|
||||
|
|
@ -3,6 +3,7 @@ use sqlx::SqlitePool;
|
|||
use crate::DbError;
|
||||
use crate::models::JavaVersionRow;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn insert(
|
||||
pool: &SqlitePool,
|
||||
absolute_path: &str,
|
||||
|
|
@ -11,19 +12,21 @@ pub async fn insert(
|
|||
vendor: &str,
|
||||
os_arch: &str,
|
||||
is_jdk: bool,
|
||||
probe_version: u32,
|
||||
) -> Result<JavaVersionRow, DbError> {
|
||||
sqlx::query_as!(
|
||||
JavaVersionRow,
|
||||
r#"
|
||||
INSERT INTO java_versions (absolute_path, major, version, vendor, os_arch, is_jdk)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO java_versions (absolute_path, major, version, vendor, os_arch, is_jdk, probe_version)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(absolute_path) DO UPDATE SET
|
||||
major = excluded.major,
|
||||
version = excluded.version,
|
||||
vendor = excluded.vendor,
|
||||
os_arch = excluded.os_arch,
|
||||
is_jdk = excluded.is_jdk
|
||||
RETURNING absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool"
|
||||
is_jdk = excluded.is_jdk,
|
||||
probe_version = excluded.probe_version
|
||||
RETURNING absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool", probe_version
|
||||
"#,
|
||||
absolute_path,
|
||||
i64::from(major),
|
||||
|
|
@ -31,6 +34,7 @@ pub async fn insert(
|
|||
vendor,
|
||||
os_arch,
|
||||
is_jdk,
|
||||
i64::from(probe_version),
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
|
|
@ -44,7 +48,7 @@ pub async fn get_by_path(
|
|||
let row = sqlx::query_as!(
|
||||
JavaVersionRow,
|
||||
r#"
|
||||
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool"
|
||||
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool", probe_version
|
||||
FROM java_versions
|
||||
WHERE absolute_path = ?
|
||||
"#,
|
||||
|
|
@ -63,7 +67,7 @@ pub async fn get_latest_by_major(
|
|||
let row = sqlx::query_as!(
|
||||
JavaVersionRow,
|
||||
r#"
|
||||
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool"
|
||||
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool", probe_version
|
||||
FROM java_versions
|
||||
WHERE major = ?
|
||||
ORDER BY version DESC
|
||||
|
|
@ -94,7 +98,7 @@ pub async fn list_all(pool: &SqlitePool) -> Result<Vec<JavaVersionRow>, DbError>
|
|||
let rows = sqlx::query_as!(
|
||||
JavaVersionRow,
|
||||
r#"
|
||||
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool"
|
||||
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool", probe_version
|
||||
FROM java_versions
|
||||
ORDER BY major DESC, version DESC
|
||||
"#,
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ pub struct JavaVersionRow {
|
|||
pub vendor: String,
|
||||
pub os_arch: String,
|
||||
pub is_jdk: bool,
|
||||
pub probe_version: i64,
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,3 +1,6 @@
|
|||
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"};
|
||||
|
||||
|
|
@ -15,6 +18,7 @@ public final class JavaInfo {
|
|||
}
|
||||
|
||||
System.out.println("java.awt=" + hasClass("java.awt.Toolkit"));
|
||||
System.out.println("java.awt.natives=" + hasAwtNatives());
|
||||
System.out.println("java.jdk=" + hasCompiler());
|
||||
|
||||
System.exit(returnCode);
|
||||
|
|
@ -30,6 +34,48 @@ public final class JavaInfo {
|
|||
}
|
||||
}
|
||||
|
||||
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 hasCompiler() {
|
||||
try {
|
||||
return javax.tools.ToolProvider.getSystemJavaCompiler() != null;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ pub async fn check_java_runtime(absolute_path: String) -> JavaResult<JavaCheckIn
|
|||
.arg("-cp")
|
||||
.arg(temp_dir)
|
||||
.arg("JavaInfo")
|
||||
.env_remove("_JAVA_OPTIONS");
|
||||
.env_remove("_JAVA_OPTIONS")
|
||||
.env_remove("JAVA_TOOL_OPTIONS")
|
||||
.env_remove("JDK_JAVA_OPTIONS");
|
||||
|
||||
let program = command.as_std().get_program().to_string_lossy();
|
||||
let args: Vec<String> = command
|
||||
|
|
@ -66,10 +68,12 @@ pub async fn check_java_runtime(absolute_path: String) -> JavaResult<JavaCheckIn
|
|||
};
|
||||
|
||||
// Minecraft needs AWT a headless image launches fine and then dies mid-game
|
||||
if !probe_flag(&info, "java.awt") {
|
||||
if !has_usable_awt(&info) {
|
||||
tracing::warn!(
|
||||
path = %absolute_path,
|
||||
"java installation has no java.awt support"
|
||||
classes = probe_flag(&info, "java.awt"),
|
||||
natives = probe_flag(&info, "java.awt.natives"),
|
||||
"java installation has no usable java.awt support"
|
||||
);
|
||||
return Err(JavaError::MissingAwtSupport { path: absolute_path });
|
||||
}
|
||||
|
|
@ -88,6 +92,10 @@ 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")
|
||||
}
|
||||
|
||||
fn probe_flag(info: &HashMap<String, String>, key: &str) -> bool {
|
||||
info.get(key)
|
||||
.is_some_and(|value| value.eq_ignore_ascii_case("true"))
|
||||
|
|
@ -132,4 +140,22 @@ mod tests {
|
|||
assert!(probe_flag(&info(&[("java.awt", "TRUE")]), "java.awt"));
|
||||
assert!(!probe_flag(&info(&[("java.awt", "false")]), "java.awt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn awt_classes_without_their_natives_are_not_usable() {
|
||||
assert!(!has_usable_awt(&info(&[
|
||||
("java.awt", "true"),
|
||||
("java.awt.natives", "false"),
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn awt_needs_both_halves() {
|
||||
assert!(has_usable_awt(&info(&[
|
||||
("java.awt", "true"),
|
||||
("java.awt.natives", "true"),
|
||||
])));
|
||||
assert!(!has_usable_awt(&info(&[("java.awt", "true")])));
|
||||
assert!(!has_usable_awt(&info(&[("java.awt.natives", "true")])));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub struct JavaRuntime {
|
|||
pub vendor: JavaVendor,
|
||||
pub os_arch: String,
|
||||
pub is_jdk: bool,
|
||||
pub probe_version: u32,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ pub enum JavaError {
|
|||
#[error("selected path is not a valid java installation: {path}")]
|
||||
InvalidJavaPath { path: String },
|
||||
|
||||
#[error("java installation at '{path}' is missing java.awt (the java.desktop module)")]
|
||||
#[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")]
|
||||
|
|
@ -61,6 +63,16 @@ pub enum JavaError {
|
|||
}
|
||||
|
||||
impl JavaError {
|
||||
#[must_use]
|
||||
pub fn is_invalid_installation(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
JavaError::InvalidJavaPath { .. }
|
||||
| JavaError::MissingAwtSupport { .. }
|
||||
| JavaError::ParseVersion { .. }
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether this is an expected outcome or the user's environment rather
|
||||
/// than a launcher bug
|
||||
/// Callers decide what to do with that (e.g. suppress error reporting)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ pub const JAVA_VENDOR_HINT: &str = "java-vendor";
|
|||
|
||||
pub const INSTALLABLE_MAJORS: &[u32] = &[8, 11, 16, 17, 18, 19, 20, 21, 22, 23];
|
||||
|
||||
pub const PROBE_VERSION: u32 = 1;
|
||||
|
||||
const _: () = assert!(PROBE_VERSION > 0);
|
||||
|
||||
enum JavaPromptAnswer {
|
||||
Download,
|
||||
PickFolder,
|
||||
|
|
@ -68,7 +72,47 @@ impl JavaService {
|
|||
let Some(path) = java_path else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(self.store.get_by_path(path).await?)
|
||||
let Some(runtime) = self.store.get_by_path(path).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.revalidate(runtime).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn revalidate(&self, runtime: JavaRuntime) -> JavaResult<Option<JavaRuntime>> {
|
||||
if runtime.probe_version == PROBE_VERSION {
|
||||
if Path::new(&runtime.absolute_path).is_file() {
|
||||
return Ok(Some(runtime));
|
||||
}
|
||||
|
||||
tracing::warn!(
|
||||
path = %runtime.absolute_path,
|
||||
"forgetting recorded Java runtime: the executable is gone"
|
||||
);
|
||||
self.store.delete_by_path(&runtime.absolute_path).await?;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match checker::check_java_runtime(runtime.absolute_path.clone()).await {
|
||||
Ok(info) => Ok(Some(
|
||||
self.persist(Path::new(&runtime.absolute_path), &info).await?,
|
||||
)),
|
||||
Err(err) if err.is_invalid_installation() => {
|
||||
tracing::warn!(
|
||||
path = %runtime.absolute_path,
|
||||
"forgetting recorded Java runtime: {err}"
|
||||
);
|
||||
self.store.delete_by_path(&runtime.absolute_path).await?;
|
||||
Ok(None)
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
path = %runtime.absolute_path,
|
||||
"could not re-probe recorded Java runtime, trusting the record: {err}"
|
||||
);
|
||||
Ok(Some(runtime))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
|
|
@ -125,7 +169,14 @@ impl JavaService {
|
|||
auto_install: bool,
|
||||
progress: Option<&GroupedProgressSession>,
|
||||
) -> JavaResult<JavaRuntime> {
|
||||
let recorded = self.store.latest_by_major(major).await?;
|
||||
let recorded = loop {
|
||||
let Some(runtime) = self.store.latest_by_major(major).await? else {
|
||||
break None;
|
||||
};
|
||||
if let Some(valid) = self.revalidate(runtime).await? {
|
||||
break Some(valid);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(runtime) = &recorded
|
||||
&& runtime.is_jdk
|
||||
|
|
@ -314,6 +365,7 @@ impl JavaService {
|
|||
.unwrap_or_else(|_| JavaVendor::Other(info.vendor.clone())),
|
||||
os_arch: info.os_arch.clone(),
|
||||
is_jdk: info.is_jdk,
|
||||
probe_version: PROBE_VERSION,
|
||||
};
|
||||
|
||||
Ok(self.store.upsert(&runtime).await?)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue