feat(platform): 识别 Termux 宿主并把 $PREFIX/lib/jvm 纳入 JDK 扫描
- 新增 platform::termux:用 TERMUX_VERSION / 默认 PREFIX 识别 Termux, 推导 $PREFIX 与 $PREFIX/lib/jvm,不把路径写死进调用方 - java::discover 的 standard_roots 在 Unix 分支追加 Termux 的 JVM 根, 环境可注入(standard_roots_in),补上「Termux 装了 openjdk 但没设 JAVA_HOME/PATH 就发现不了」的盲点 - 单测覆盖识别、负例与扫描结果
This commit is contained in:
1 parent
ac2e163eb8
commit
e090c67297
3 files changed
+136
-1
No files matched your search
@@ -8,6 +8,7 @@
|
||||
//! | --- | --- |
|
||||
//! | Windows | `%ProgramFiles%\{Java, Eclipse Adoptium, Microsoft, Zulu, Amazon Corretto, BellSoft, Semeru}\*`(含 `ProgramFiles(x86)` / `ProgramW6432` 变体,WOW64 才看得到 32 位安装)、`%APPDATA%\.minecraft\runtime\*`(**官方启动器装的运行时**,跨产品兼容)、`HKLM\SOFTWARE\JavaSoft\JDK`(老 Oracle 写的,新版发行版通常没有→容错跳过) |
|
||||
//! | Linux | `/usr/lib/jvm/*`、`/usr/java/*` |
|
||||
//! | Termux | `$PREFIX/lib/jvm/*`(`java-17-openjdk` 这类;`$PREFIX` 由 [`crate::platform::termux`] 识别) |
|
||||
//! | macOS | `/Library/Java/JavaVirtualMachines/*.jdk/Contents/Home` |
|
||||
//!
|
||||
//! 三个纪律:
|
||||
@@ -22,6 +23,9 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::platform::paths::{EnvSource, ProcessEnv};
|
||||
use crate::platform::termux;
|
||||
|
||||
use super::extract::java_binary_name;
|
||||
use super::probe::{self, discover_managed_roots, system_candidates, JavaOrigin, JavaRuntime};
|
||||
|
||||
@@ -73,6 +77,12 @@ pub fn scan_java_binaries() -> Vec<PathBuf> {
|
||||
/// 候选 JDK 根目录(`bin/java` 的父目录的父目录)。
|
||||
#[must_use]
|
||||
pub fn standard_roots() -> Vec<PathBuf> {
|
||||
standard_roots_in(&ProcessEnv)
|
||||
}
|
||||
|
||||
/// 同 [`standard_roots`],但环境变量可注入(测试 / 便携化封装用)。
|
||||
#[must_use]
|
||||
pub fn standard_roots_in(env: &dyn EnvSource) -> Vec<PathBuf> {
|
||||
let mut roots = Vec::new();
|
||||
if cfg!(windows) {
|
||||
for variable in ["ProgramW6432", "ProgramFiles", "ProgramFiles(x86)"] {
|
||||
@@ -99,6 +109,10 @@ pub fn standard_roots() -> Vec<PathBuf> {
|
||||
for base in ["/usr/lib/jvm", "/usr/java"] {
|
||||
roots.extend(child_dirs(Path::new(base)));
|
||||
}
|
||||
// Termux 的 JDK 落在 `$PREFIX/lib/jvm`(如 `java-17-openjdk`),不在 `/usr` 下。
|
||||
if let Some(root) = termux::jvm_root(env) {
|
||||
roots.extend(child_dirs(&root));
|
||||
}
|
||||
}
|
||||
roots.sort();
|
||||
roots.dedup();
|
||||
@@ -211,8 +225,9 @@ pub fn path_key(path: &Path) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{dedupe, parse_java_home, path_key, standard_roots};
|
||||
use super::{dedupe, parse_java_home, path_key, standard_roots, standard_roots_in};
|
||||
use crate::game::java::probe::{JavaOrigin, JavaRuntime};
|
||||
use crate::platform::paths::MapEnv;
|
||||
|
||||
fn runtime(path: &str, origin: JavaOrigin) -> JavaRuntime {
|
||||
JavaRuntime {
|
||||
@@ -323,4 +338,41 @@ mod tests {
|
||||
let roots = standard_roots();
|
||||
assert!(roots.iter().all(|root| !root.as_os_str().is_empty()));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn termux_jvm_root_is_scanned() {
|
||||
let base = std::env::temp_dir().join(format!("ull-termux-jvm-{}", std::process::id()));
|
||||
let jdk = base.join("lib").join("jvm").join("java-17-openjdk");
|
||||
std::fs::create_dir_all(jdk.join("bin")).unwrap();
|
||||
std::fs::write(jdk.join("bin").join("java"), b"").unwrap();
|
||||
|
||||
let env = MapEnv::new()
|
||||
.with("TERMUX_VERSION", "0.118.3")
|
||||
.with("PREFIX", base.to_str().unwrap());
|
||||
let roots = standard_roots_in(&env);
|
||||
assert!(
|
||||
roots.contains(&jdk),
|
||||
"Termux 的 $PREFIX/lib/jvm 必须进扫描表:{roots:?}"
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&base).ok();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn non_termux_env_does_not_scan_prefix() {
|
||||
let base = std::env::temp_dir().join(format!("ull-not-termux-{}", std::process::id()));
|
||||
let jdk = base.join("lib").join("jvm").join("java-17-openjdk");
|
||||
std::fs::create_dir_all(&jdk).unwrap();
|
||||
|
||||
let env = MapEnv::new().with("PREFIX", base.to_str().unwrap());
|
||||
let roots = standard_roots_in(&env);
|
||||
assert!(
|
||||
!roots.contains(&jdk),
|
||||
"非 Termux 不该扫 $PREFIX/lib/jvm:{roots:?}"
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&base).ok();
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ pub mod os_info;
|
||||
pub mod paths;
|
||||
pub mod receipt;
|
||||
pub mod redirect;
|
||||
pub mod termux;
|
||||
pub mod theme_probe;
|
||||
pub mod vk_probe;
|
||||
pub mod wallpaper;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
//! Termux(Android 上的用户空间)宿主识别。
|
||||
//!
|
||||
//! Termux 的软件前缀不在 `/usr`,而是 `$PREFIX`(典型值
|
||||
//! `/data/data/com.termux/files/usr`);JDK 由 `pkg install openjdk-17` 落到
|
||||
//! `$PREFIX/lib/jvm/java-17-openjdk`。这里只做**识别与路径推导**,不碰包管理,
|
||||
//! 也不把 `$PREFIX` 当成编译期常量写死。
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::paths::EnvSource;
|
||||
|
||||
/// Termux 的默认前缀(`PREFIX` 缺失时的兜底)。
|
||||
pub const DEFAULT_PREFIX: &str = "/data/data/com.termux/files/usr";
|
||||
|
||||
/// 当前环境是否运行在 Termux 里。
|
||||
///
|
||||
/// 判据取 Termux 自己导出的两个变量:`TERMUX_VERSION`(版本)或 `PREFIX`
|
||||
/// 指向默认前缀。两者都没有时按普通 Linux 处理——不猜。
|
||||
#[must_use]
|
||||
pub fn is_termux(env: &dyn EnvSource) -> bool {
|
||||
env.var("TERMUX_VERSION").is_some()
|
||||
|| env
|
||||
.var("PREFIX")
|
||||
.is_some_and(|prefix| prefix.trim_end_matches('/') == DEFAULT_PREFIX)
|
||||
}
|
||||
|
||||
/// Termux 的软件前缀(仅当识别为 Termux 时)。
|
||||
#[must_use]
|
||||
pub fn prefix(env: &dyn EnvSource) -> Option<PathBuf> {
|
||||
if !is_termux(env) {
|
||||
return None;
|
||||
}
|
||||
Some(env.var("PREFIX").map_or_else(
|
||||
|| PathBuf::from(DEFAULT_PREFIX),
|
||||
PathBuf::from,
|
||||
))
|
||||
}
|
||||
|
||||
/// Termux 的 JDK 安装根(`$PREFIX/lib/jvm`)。
|
||||
#[must_use]
|
||||
pub fn jvm_root(env: &dyn EnvSource) -> Option<PathBuf> {
|
||||
prefix(env).map(|prefix| prefix.join("lib").join("jvm"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_termux, jvm_root, prefix, DEFAULT_PREFIX};
|
||||
use crate::platform::paths::MapEnv;
|
||||
|
||||
#[test]
|
||||
fn plain_linux_is_not_termux() {
|
||||
let env = MapEnv::new().with("HOME", "/home/u");
|
||||
assert!(!is_termux(&env));
|
||||
assert_eq!(prefix(&env), None);
|
||||
assert_eq!(jvm_root(&env), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_prefix_is_recognised() {
|
||||
let env = MapEnv::new().with("PREFIX", DEFAULT_PREFIX);
|
||||
assert!(is_termux(&env));
|
||||
assert_eq!(
|
||||
jvm_root(&env).unwrap(),
|
||||
std::path::PathBuf::from(DEFAULT_PREFIX).join("lib/jvm")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_variable_alone_is_enough() {
|
||||
let env = MapEnv::new()
|
||||
.with("TERMUX_VERSION", "0.118.3")
|
||||
.with("PREFIX", "/data/data/com.termux/files/usr");
|
||||
assert!(is_termux(&env));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_prefix_is_not_termux() {
|
||||
// `PREFIX` 被搬到别的目录不在识别范围内——只有版本变量或默认前缀算数。
|
||||
let env = MapEnv::new().with("PREFIX", "/opt/termux");
|
||||
assert!(!is_termux(&env));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user