refactor(java): simpler java check (#698)
This commit is contained in:
parent
fdb3b9a0a3
commit
b60ce5af12
15 changed files with 158 additions and 254 deletions
4
.gitattributes
vendored
4
.gitattributes
vendored
|
|
@ -1,6 +1,2 @@
|
||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
Cargo.lock -diff linguist-generated
|
Cargo.lock -diff linguist-generated
|
||||||
pnpm-lock.yaml -diff linguist-generated
|
|
||||||
packages/client/src/bindings.ts -diff linguist-generated=true
|
|
||||||
.github/actions/publish-artifacts/dist/index.js -diff linguist-generated=true
|
|
||||||
.github/actions/clippy/dist/index.js -diff linguist-generated=true
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n\t\tINSERT INTO java_versions (absolute_path, major, version, vendor, os_arch)\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\tRETURNING absolute_path, major, version, vendor, os_arch\n\t\t",
|
"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",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
|
@ -57,18 +57,30 @@
|
||||||
"name": "os_arch"
|
"name": "os_arch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_jdk: bool",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Integer",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "java_versions",
|
||||||
|
"name": "is_jdk"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 5
|
"Right": 6
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "a3bd6cf3048a6a5ed9d29e6bbb7f517676fe715c8af45eb5c58d69073e2d0304"
|
"hash": "010f5dcda59ec32eaad1b9c3e2e9807f547a1376372c8b201653a36d6dd14cbc"
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT absolute_path, major, version, vendor, os_arch 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\"\n FROM java_versions\n ORDER BY major DESC, version DESC\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
|
@ -57,6 +57,17 @@
|
||||||
"name": "os_arch"
|
"name": "os_arch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_jdk: bool",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Integer",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "java_versions",
|
||||||
|
"name": "is_jdk"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|
@ -67,8 +78,9 @@
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "c1d3cf930d5f38e8f1046da1a8f895ee1de2d3fe9a40ecb47731618962d2a4ad"
|
"hash": "0d9fb5d2fa8a788770e946241ea2f62f4b794c0fdda3ccbde49384689911aeba"
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n\t\tSELECT absolute_path, major, version, vendor, os_arch\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\"\n\t\tFROM java_versions\n\t\tWHERE major = ?\n\t\tORDER BY version DESC\n\t\tLIMIT 1\n\t\t",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
|
@ -57,6 +57,17 @@
|
||||||
"name": "os_arch"
|
"name": "os_arch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_jdk: bool",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Integer",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "java_versions",
|
||||||
|
"name": "is_jdk"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|
@ -67,8 +78,9 @@
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "b153f762849649456dd97b6a5cd35fb7dafc3d5539441328e03442cea195483a"
|
"hash": "33771b7003e64c8f19d15d663de773afa568d626c60a37bbc32cc932746ad49b"
|
||||||
}
|
}
|
||||||
62
.sqlx/query-bbbc24e8d4f13a39ebce8e8598f4adbc864909984313ea1c065ba226465b23ca.json
generated
Normal file
62
.sqlx/query-bbbc24e8d4f13a39ebce8e8598f4adbc864909984313ea1c065ba226465b23ca.json
generated
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n\t\tSELECT DISTINCT ca.cluster_id, ca.hash, ca.cluster_file_name, ca.enabled\n\t\tFROM cluster_artifacts ca\n\t\tJOIN provider_releases pr ON pr.hash = ca.hash\n\t\tWHERE ca.cluster_id = ?\n\t\t\tAND pr.provider = ?\n\t\t\tAND pr.project_id = ?\n\t\t\tAND ca.hash <> ?\n\t\t",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "cluster_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Integer",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "cluster_artifacts",
|
||||||
|
"name": "cluster_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hash",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "cluster_artifacts",
|
||||||
|
"name": "hash"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cluster_file_name",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "cluster_artifacts",
|
||||||
|
"name": "cluster_file_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "enabled",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Integer",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "cluster_artifacts",
|
||||||
|
"name": "enabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "bbbc24e8d4f13a39ebce8e8598f4adbc864909984313ea1c065ba226465b23ca"
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT absolute_path, major, version, vendor, os_arch FROM java_versions \n WHERE absolute_path = ?\n ",
|
"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 ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
|
@ -57,6 +57,17 @@
|
||||||
"name": "os_arch"
|
"name": "os_arch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_jdk: bool",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Integer",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "java_versions",
|
||||||
|
"name": "is_jdk"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|
@ -67,8 +78,9 @@
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "c570e309195b6063cf0fbee866d68972de05107769dab983524fd2e74a7ae31a"
|
"hash": "f5992c9cec6a6d7015cda4c04b463bd5fd9c42ac38b434fa8a0c0fe4f7517cf2"
|
||||||
}
|
}
|
||||||
|
|
@ -30,6 +30,7 @@ fn row_to_runtime(row: JavaVersionRow) -> JavaRuntime {
|
||||||
version: row.version,
|
version: row.version,
|
||||||
vendor: JavaVendor::from_str(&row.vendor).unwrap_or(JavaVendor::Other(row.vendor)),
|
vendor: JavaVendor::from_str(&row.vendor).unwrap_or(JavaVendor::Other(row.vendor)),
|
||||||
os_arch: row.os_arch,
|
os_arch: row.os_arch,
|
||||||
|
is_jdk: row.is_jdk,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +44,7 @@ impl JavaStore for SqlJavaStore {
|
||||||
&runtime.version,
|
&runtime.version,
|
||||||
&runtime.vendor.to_string(),
|
&runtime.vendor.to_string(),
|
||||||
&runtime.os_arch,
|
&runtime.os_arch,
|
||||||
|
runtime.is_jdk,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(StoreError::new)?;
|
.map_err(StoreError::new)?;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- Whether a recorded runtime is a development kit rather than a bare runtime
|
||||||
|
-- image. Probing for it means spawning the java process, so it is remembered
|
||||||
|
-- alongside the version instead. Rows written before this migration default to
|
||||||
|
-- 0 and are corrected the next time they are probed.
|
||||||
|
ALTER TABLE `java_versions` ADD COLUMN `is_jdk` INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
@ -10,24 +10,27 @@ pub async fn insert(
|
||||||
version: &str,
|
version: &str,
|
||||||
vendor: &str,
|
vendor: &str,
|
||||||
os_arch: &str,
|
os_arch: &str,
|
||||||
|
is_jdk: bool,
|
||||||
) -> Result<JavaVersionRow, DbError> {
|
) -> Result<JavaVersionRow, DbError> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
JavaVersionRow,
|
JavaVersionRow,
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO java_versions (absolute_path, major, version, vendor, os_arch)
|
INSERT INTO java_versions (absolute_path, major, version, vendor, os_arch, is_jdk)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT(absolute_path) DO UPDATE SET
|
ON CONFLICT(absolute_path) DO UPDATE SET
|
||||||
major = excluded.major,
|
major = excluded.major,
|
||||||
version = excluded.version,
|
version = excluded.version,
|
||||||
vendor = excluded.vendor,
|
vendor = excluded.vendor,
|
||||||
os_arch = excluded.os_arch
|
os_arch = excluded.os_arch,
|
||||||
RETURNING absolute_path, major, version, vendor, os_arch
|
is_jdk = excluded.is_jdk
|
||||||
|
RETURNING absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool"
|
||||||
"#,
|
"#,
|
||||||
absolute_path,
|
absolute_path,
|
||||||
i64::from(major),
|
i64::from(major),
|
||||||
version,
|
version,
|
||||||
vendor,
|
vendor,
|
||||||
os_arch,
|
os_arch,
|
||||||
|
is_jdk,
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -41,7 +44,8 @@ pub async fn get_by_path(
|
||||||
let row = sqlx::query_as!(
|
let row = sqlx::query_as!(
|
||||||
JavaVersionRow,
|
JavaVersionRow,
|
||||||
r#"
|
r#"
|
||||||
SELECT absolute_path, major, version, vendor, os_arch FROM java_versions
|
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool"
|
||||||
|
FROM java_versions
|
||||||
WHERE absolute_path = ?
|
WHERE absolute_path = ?
|
||||||
"#,
|
"#,
|
||||||
absolute_path,
|
absolute_path,
|
||||||
|
|
@ -59,7 +63,7 @@ pub async fn get_latest_by_major(
|
||||||
let row = sqlx::query_as!(
|
let row = sqlx::query_as!(
|
||||||
JavaVersionRow,
|
JavaVersionRow,
|
||||||
r#"
|
r#"
|
||||||
SELECT absolute_path, major, version, vendor, os_arch
|
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool"
|
||||||
FROM java_versions
|
FROM java_versions
|
||||||
WHERE major = ?
|
WHERE major = ?
|
||||||
ORDER BY version DESC
|
ORDER BY version DESC
|
||||||
|
|
@ -90,7 +94,8 @@ pub async fn list_all(pool: &SqlitePool) -> Result<Vec<JavaVersionRow>, DbError>
|
||||||
let rows = sqlx::query_as!(
|
let rows = sqlx::query_as!(
|
||||||
JavaVersionRow,
|
JavaVersionRow,
|
||||||
r#"
|
r#"
|
||||||
SELECT absolute_path, major, version, vendor, os_arch FROM java_versions
|
SELECT absolute_path, major, version, vendor, os_arch, is_jdk as "is_jdk: bool"
|
||||||
|
FROM java_versions
|
||||||
ORDER BY major DESC, version DESC
|
ORDER BY major DESC, version DESC
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ pub struct JavaVersionRow {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub vendor: String,
|
pub vendor: String,
|
||||||
pub os_arch: String,
|
pub os_arch: String,
|
||||||
|
pub is_jdk: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -14,11 +14,7 @@ public final class JavaInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minecraft needs AWT. Stripped, headless and hand-jlinked images drop
|
|
||||||
// java.desktop and only blow up once the game reaches for it.
|
|
||||||
System.out.println("java.awt=" + hasClass("java.awt.Toolkit"));
|
System.out.println("java.awt=" + hasClass("java.awt.Toolkit"));
|
||||||
// A kit ships a compiler, a runtime image does not. On 8 this finds the
|
|
||||||
// one in tools.jar, on 9+ the jdk.compiler module.
|
|
||||||
System.out.println("java.jdk=" + hasCompiler());
|
System.out.println("java.jdk=" + hasCompiler());
|
||||||
|
|
||||||
System.exit(returnCode);
|
System.exit(returnCode);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use std::{
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
collections::HashMap,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use async_tempfile::TempDir;
|
use async_tempfile::TempDir;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
@ -10,19 +7,6 @@ use crate::error::{JavaError, JavaResult};
|
||||||
|
|
||||||
const JAVA_INFO_CLASS: &[u8] = include_bytes!("../assets/java/JavaInfo.class");
|
const JAVA_INFO_CLASS: &[u8] = include_bytes!("../assets/java/JavaInfo.class");
|
||||||
|
|
||||||
/// Tools that only ever ship with a development kit, never with a runtime image.
|
|
||||||
const JAVAC_BIN: &str = if cfg!(windows) { "javac.exe" } else { "javac" };
|
|
||||||
const JMOD_BIN: &str = if cfg!(windows) { "jmod.exe" } else { "jmod" };
|
|
||||||
|
|
||||||
/// The native library behind `java.awt`. An image linked without `java.desktop`
|
|
||||||
/// simply does not ship it.
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
const AWT_LIBRARY: &str = "awt.dll";
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
const AWT_LIBRARY: &str = "libawt.dylib";
|
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
|
||||||
const AWT_LIBRARY: &str = "libawt.so";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct JavaCheckInfo {
|
pub struct JavaCheckInfo {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
|
@ -84,15 +68,12 @@ pub async fn check_java_runtime(absolute_path: String) -> JavaResult<JavaCheckIn
|
||||||
return Err(JavaError::InvalidJavaPath { path: absolute_path });
|
return Err(JavaError::InvalidJavaPath { path: absolute_path });
|
||||||
};
|
};
|
||||||
|
|
||||||
let home = java_home(&absolute_path, info.get("java.home"));
|
|
||||||
|
|
||||||
// Minecraft draws its window through AWT. A headless or hand-jlinked image
|
// Minecraft draws its window through AWT. A headless or hand-jlinked image
|
||||||
// without `java.desktop` launches happily and then dies mid-game, so it is
|
// without `java.desktop` launches happily and then dies mid-game, so it is
|
||||||
// rejected here rather than recorded as usable.
|
// rejected here rather than recorded as usable.
|
||||||
if !has_awt_support(&info, &home) {
|
if !probe_flag(&info, "java.awt") {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
path = %absolute_path,
|
path = %absolute_path,
|
||||||
home = %home.display(),
|
|
||||||
"java installation has no java.awt support"
|
"java installation has no java.awt support"
|
||||||
);
|
);
|
||||||
return Err(JavaError::MissingAwtSupport { path: absolute_path });
|
return Err(JavaError::MissingAwtSupport { path: absolute_path });
|
||||||
|
|
@ -108,99 +89,14 @@ pub async fn check_java_runtime(absolute_path: String) -> JavaResult<JavaCheckIn
|
||||||
.get("java.vendor")
|
.get("java.vendor")
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| String::from("unknown")),
|
.unwrap_or_else(|| String::from("unknown")),
|
||||||
is_jdk: probe_flag(&info, "java.jdk").unwrap_or(false) || is_jdk_home(&home),
|
is_jdk: probe_flag(&info, "java.jdk"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the installation behind a `java` executable is a development kit.
|
/// Reads a boolean the probe printed; anything else counts as `false`.
|
||||||
///
|
fn probe_flag(info: &HashMap<String, String>, key: &str) -> bool {
|
||||||
/// Nothing is recorded about kit-ness in the store, so callers holding only a
|
info.get(key)
|
||||||
/// path ask the filesystem again instead of the schema growing a column for a
|
.is_some_and(|value| value.eq_ignore_ascii_case("true"))
|
||||||
/// hint this cheap to recompute.
|
|
||||||
pub(crate) fn is_jdk_executable(absolute_path: &str) -> bool {
|
|
||||||
is_jdk_home(&java_home(absolute_path, None))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether `home` is a development kit rather than a runtime image.
|
|
||||||
///
|
|
||||||
/// Directory names lie in both directions (`.../jdk-21/jre`, runtimes unpacked
|
|
||||||
/// into a folder someone called `jdk`), so this only looks for artefacts a
|
|
||||||
/// runtime image never carries: the compiler, `jmod`, the JDK 8 `tools.jar`,
|
|
||||||
/// and the JNI headers.
|
|
||||||
pub(crate) fn is_jdk_home(home: &Path) -> bool {
|
|
||||||
let bin = home.join("bin");
|
|
||||||
|
|
||||||
bin.join(JAVAC_BIN).is_file()
|
|
||||||
|| bin.join(JMOD_BIN).is_file()
|
|
||||||
|| home.join("jmods").is_dir()
|
|
||||||
|| home.join("lib").join("tools.jar").is_file()
|
|
||||||
|| home.join("include").join("jni.h").is_file()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The installation root.
|
|
||||||
///
|
|
||||||
/// `java.home` as the probe reports it is authoritative; when the compiled
|
|
||||||
/// probe class predates that property we fall back to the executable's own
|
|
||||||
/// location, resolving symlinks first so a `/usr/bin/java` shim lands in the
|
|
||||||
/// real home rather than in `/usr`.
|
|
||||||
fn java_home(absolute_path: &str, reported: Option<&String>) -> PathBuf {
|
|
||||||
if let Some(home) = reported.filter(|home| !home.is_empty()) {
|
|
||||||
return PathBuf::from(home);
|
|
||||||
}
|
|
||||||
|
|
||||||
let executable = std::fs::canonicalize(absolute_path)
|
|
||||||
.unwrap_or_else(|_| PathBuf::from(absolute_path));
|
|
||||||
|
|
||||||
executable
|
|
||||||
.parent()
|
|
||||||
.and_then(Path::parent)
|
|
||||||
.map(Path::to_path_buf)
|
|
||||||
.unwrap_or(executable)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether `java.awt` is there: the probe's own answer when it gave one, and
|
|
||||||
/// otherwise what the installation ships on disk.
|
|
||||||
fn has_awt_support(info: &HashMap<String, String>, home: &Path) -> bool {
|
|
||||||
if let Some(reported) = probe_flag(info, "java.awt") {
|
|
||||||
return reported;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Java 9+ images record the modules they were linked with.
|
|
||||||
if let Some(modules) = release_modules(home) {
|
|
||||||
return modules.split_whitespace().any(|module| module == "java.desktop");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Java 8, and anything else without a `release` file: AWT is a native
|
|
||||||
// library sitting next to the runtime.
|
|
||||||
awt_library_exists(home)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `MODULES` line of an image's `release` file, if it has one.
|
|
||||||
fn release_modules(home: &Path) -> Option<String> {
|
|
||||||
let release = std::fs::read_to_string(home.join("release")).ok()?;
|
|
||||||
|
|
||||||
release.lines().find_map(|line| {
|
|
||||||
let modules = line.strip_prefix("MODULES=")?;
|
|
||||||
Some(modules.trim_matches('"').to_string())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn awt_library_exists(home: &Path) -> bool {
|
|
||||||
// Windows keeps native libraries in `bin` and everyone else in `lib`; a
|
|
||||||
// JDK 8 also nests a whole runtime under `jre`.
|
|
||||||
[
|
|
||||||
home.join("lib"),
|
|
||||||
home.join("bin"),
|
|
||||||
home.join("jre").join("lib"),
|
|
||||||
home.join("jre").join("bin"),
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.any(|dir| dir.join(AWT_LIBRARY).exists())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a boolean the probe printed, `None` when it printed no such line.
|
|
||||||
fn probe_flag(info: &HashMap<String, String>, key: &str) -> Option<bool> {
|
|
||||||
info.get(key).map(|value| value.eq_ignore_ascii_case("true"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static TEMP_JAVA_INFO: OnceCell<TempDir> = OnceCell::const_new();
|
static TEMP_JAVA_INFO: OnceCell<TempDir> = OnceCell::const_new();
|
||||||
|
|
@ -232,126 +128,14 @@ mod tests {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn touch(path: &Path) {
|
#[test]
|
||||||
if let Some(parent) = path.parent() {
|
fn a_flag_the_probe_never_printed_is_false() {
|
||||||
std::fs::create_dir_all(parent).expect("create parent");
|
assert!(!probe_flag(&info(&[]), "java.awt"));
|
||||||
}
|
|
||||||
std::fs::write(path, b"").expect("write file");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn a_runtime_image_is_not_mistaken_for_a_kit() {
|
|
||||||
let dir = polyio::tempdir().await.expect("tempdir");
|
|
||||||
let home = dir.dir_path();
|
|
||||||
|
|
||||||
touch(&home.join("bin").join("java"));
|
|
||||||
touch(&home.join("lib").join("modules"));
|
|
||||||
|
|
||||||
assert!(!is_jdk_home(home));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn a_kit_is_recognised_by_its_compiler() {
|
|
||||||
let dir = polyio::tempdir().await.expect("tempdir");
|
|
||||||
let home = dir.dir_path();
|
|
||||||
|
|
||||||
touch(&home.join("bin").join("java"));
|
|
||||||
touch(&home.join("bin").join(JAVAC_BIN));
|
|
||||||
|
|
||||||
assert!(is_jdk_home(home));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn a_kit_is_recognised_by_its_headers_alone() {
|
|
||||||
let dir = polyio::tempdir().await.expect("tempdir");
|
|
||||||
let home = dir.dir_path();
|
|
||||||
|
|
||||||
touch(&home.join("bin").join("java"));
|
|
||||||
touch(&home.join("include").join("jni.h"));
|
|
||||||
|
|
||||||
assert!(is_jdk_home(home));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn a_legacy_kit_is_recognised_by_tools_jar() {
|
|
||||||
let dir = polyio::tempdir().await.expect("tempdir");
|
|
||||||
let home = dir.dir_path();
|
|
||||||
|
|
||||||
touch(&home.join("lib").join("tools.jar"));
|
|
||||||
|
|
||||||
assert!(is_jdk_home(home));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn the_directory_name_alone_proves_nothing() {
|
|
||||||
let dir = polyio::tempdir().await.expect("tempdir");
|
|
||||||
let home = dir.dir_path().join("jdk-21");
|
|
||||||
|
|
||||||
touch(&home.join("bin").join("java"));
|
|
||||||
|
|
||||||
assert!(!is_jdk_home(&home));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_probe_has_the_last_word_on_awt() {
|
fn a_flag_is_read_regardless_of_case() {
|
||||||
let missing = Path::new("/nonexistent-java-home");
|
assert!(probe_flag(&info(&[("java.awt", "TRUE")]), "java.awt"));
|
||||||
|
assert!(!probe_flag(&info(&[("java.awt", "false")]), "java.awt"));
|
||||||
assert!(has_awt_support(&info(&[("java.awt", "true")]), missing));
|
|
||||||
assert!(!has_awt_support(&info(&[("java.awt", "false")]), missing));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn a_module_list_without_java_desktop_fails_the_check() {
|
|
||||||
let dir = polyio::tempdir().await.expect("tempdir");
|
|
||||||
let home = dir.dir_path();
|
|
||||||
|
|
||||||
std::fs::write(
|
|
||||||
home.join("release"),
|
|
||||||
"JAVA_VERSION=\"21.0.3\"\nMODULES=\"java.base java.logging\"\n",
|
|
||||||
)
|
|
||||||
.expect("write release");
|
|
||||||
|
|
||||||
assert!(!has_awt_support(&info(&[]), home));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn a_module_list_with_java_desktop_passes() {
|
|
||||||
let dir = polyio::tempdir().await.expect("tempdir");
|
|
||||||
let home = dir.dir_path();
|
|
||||||
|
|
||||||
std::fs::write(
|
|
||||||
home.join("release"),
|
|
||||||
"JAVA_VERSION=\"21.0.3\"\nMODULES=\"java.base java.desktop java.logging\"\n",
|
|
||||||
)
|
|
||||||
.expect("write release");
|
|
||||||
|
|
||||||
assert!(has_awt_support(&info(&[]), home));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn a_release_less_image_falls_back_to_the_native_library() {
|
|
||||||
let dir = polyio::tempdir().await.expect("tempdir");
|
|
||||||
let home = dir.dir_path();
|
|
||||||
|
|
||||||
assert!(!has_awt_support(&info(&[]), home));
|
|
||||||
|
|
||||||
touch(&home.join("lib").join(AWT_LIBRARY));
|
|
||||||
|
|
||||||
assert!(has_awt_support(&info(&[]), home));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_missing_home_falls_back_to_the_executable_location() {
|
|
||||||
let home = java_home("/opt/jdk-21/bin/java", None);
|
|
||||||
|
|
||||||
assert_eq!(home, PathBuf::from("/opt/jdk-21"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_reported_home_wins_over_the_executable_location() {
|
|
||||||
let reported = String::from("/opt/real-home");
|
|
||||||
let home = java_home("/usr/bin/java", Some(&reported));
|
|
||||||
|
|
||||||
assert_eq!(home, PathBuf::from("/opt/real-home"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@ pub struct JavaRuntime {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub vendor: JavaVendor,
|
pub vendor: JavaVendor,
|
||||||
pub os_arch: String,
|
pub os_arch: String,
|
||||||
|
/// Whether this is a development kit rather than a bare runtime image, as
|
||||||
|
/// the probe reported it when the runtime was recorded (see
|
||||||
|
/// [`crate::JavaCheckInfo::is_jdk`]).
|
||||||
|
pub is_jdk: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ impl JavaService {
|
||||||
let recorded = self.store.latest_by_major(major).await?;
|
let recorded = self.store.latest_by_major(major).await?;
|
||||||
|
|
||||||
if let Some(runtime) = &recorded
|
if let Some(runtime) = &recorded
|
||||||
&& checker::is_jdk_executable(&runtime.absolute_path)
|
&& runtime.is_jdk
|
||||||
{
|
{
|
||||||
return Ok(runtime.clone());
|
return Ok(runtime.clone());
|
||||||
}
|
}
|
||||||
|
|
@ -347,6 +347,7 @@ impl JavaService {
|
||||||
vendor: JavaVendor::from_str(&info.vendor)
|
vendor: JavaVendor::from_str(&info.vendor)
|
||||||
.unwrap_or_else(|_| JavaVendor::Other(info.vendor.clone())),
|
.unwrap_or_else(|_| JavaVendor::Other(info.vendor.clone())),
|
||||||
os_arch: info.os_arch.clone(),
|
os_arch: info.os_arch.clone(),
|
||||||
|
is_jdk: info.is_jdk,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(self.store.upsert(&runtime).await?)
|
Ok(self.store.upsert(&runtime).await?)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue