refactor(core): 拆分越 400 行红线的 install.rs 与 install/libraries.rs

合并 termux-glibc 后两个文件顶破了「单文件 ≤400 行」(guards.yml 的 file-size):
`game/java/install.rs` 379 → 456、`game/install/libraries.rs` 258 → 409。

按步骤拆,纯搬运、零行为变化:

- `install/termux_install.rs`(新):Termux 系统包那条路。它与归档链不共用任何一步
  (不查版本元数据、不下载、不解压、不校验 sha256),原来只能是一段嵌在 install() 中间的
  if。返回 `Outcome::Done`/`Skipped` 并用 Box 装 `InstalledJdk`(否则 large_enum_variant:
  392 vs 24 字节)。
- `install/one.rs`(新):回退链里「一家」的机械步骤——下载 → 解压 → 探测 + 幂等复用,
  连同 `Prepared` 与 `note()`。
- `install/libraries/arm64_natives.rs`(新):Linux arm64 的 natives 改写与 Maven 兜底
  (两个常量 + 改写/拼址两个函数)。用例仍留在 libraries.rs 的 tests 里,它们从 `plan()`
  的出口断言,不碰私有步骤。

拆分后:install.rs 319、one.rs 100、termux_install.rs 104、libraries.rs 348、
arm64_natives.rs 72;全仓受检文件(*.rs/*.md/*.toml/*.yml/*.json/*.py/*.sh)复算
最大 400 行,红线内。
This commit is contained in:
AskaEth committed 2026-11-07 09:33:24 +08:00
1 parent c82e39d0eb
commit 4dbeb07220
5 files changed
+302 -202

No files matched your search

+4 -49
View File
@@ -18,6 +18,10 @@ use super::super::library::Library;
use super::plan::{FileRole, PlanFile};
use crate::download::ResourceKind;
use arm64_natives::{arm64_native_url, linux_arm64_native_entry, LINUX_ARM64_CLASSIFIER};
mod arm64_natives;
/// 没写 `url` 的库兜底用的 Maven 基址(Mojang 官方库仓)。
pub const DEFAULT_MAVEN_BASE: &str = "https://libraries.minecraft.net/";
@@ -150,39 +154,6 @@ fn library_dest(
}
}
/// 现代清单里 LWJGL3 natives 条目的改写结果。
struct Arm64Native {
name: String,
relative: PathBuf,
url: String,
}
/// Linux arm64:把 `org.lwjgl:<module>:<version>:natives-linux` 改写成 arm64。
fn linux_arm64_native_entry(library: &Library, env: &Environment) -> Option<Arm64Native> {
let name = library.linux_arm64_native_name(env)?;
let base = name.strip_suffix(":natives-linux-arm64")?;
let mut parts = base.split(':');
let group = parts.next()?;
let artifact = parts.next()?;
let version = parts.next()?;
if parts.next().is_some() {
return None;
}
let relative = PathBuf::from(format!(
"{}/{artifact}/{version}/{artifact}-{version}-natives-linux-arm64.jar",
group.replace('.', "/")
));
let url = format!(
"{LINUX_ARM64_NATIVES_BASE}{}",
relative.to_string_lossy().replace('\\', "/")
);
Some(Arm64Native {
name,
relative,
url,
})
}
/// 按库声明(或默认)的 Maven 基址拼下载地址。
fn maven_url(library: &Library, relative: &Path) -> String {
let base = library
@@ -199,22 +170,6 @@ fn maven_url(library: &Library, relative: &Path) -> String {
}
}
/// Linux arm64 的 natives 兜底仓库:Mojang 的 `libraries.minecraft.net` 对
/// `natives-linux-arm64` 一律 404(实测 3.3.2 的 lwjgl/glfw/opengl/stb/tinyfd/openal),
/// 同一坐标在 Maven Central / 阿里云镜像有。
pub const LINUX_ARM64_NATIVES_BASE: &str = "https://maven.aliyun.com/repository/public/";
/// Linux arm64 的 natives 分类器(见 `Library::native_classifier`)。
pub const LINUX_ARM64_CLASSIFIER: &str = "natives-linux-arm64";
/// arm64 natives 的兜底 URL(按相对路径拼镜像基址)。
fn arm64_native_url(relative: &Path) -> String {
format!(
"{LINUX_ARM64_NATIVES_BASE}{}",
relative.to_string_lossy().replace('\\', "/")
)
}
/// 供上层判错用:库展开不产生硬失败,缺地址一律降级成告警。
#[cfg(test)]
mod tests {
@@ -0,0 +1,72 @@
//! Linux arm64 的 natives 改写与下载兜底。
//!
//! 两件事实拼出来的特例:
//! 1. Mojang 清单里 LWJGL3 的 natives 只写 `natives-linux`(x86_64 的 `.so`);
//! 直接用它,在 aarch64 JVM 里加载必失败。
//! 2. 同一个 Maven 坐标确实有 `natives-linux-arm64` 分类器,但 `libraries.minecraft.net`
//! 对它一律 404(实测 3.3.2 的 lwjgl/glfw/opengl/stb/tinyfd/openal),Maven Central 与
//! 阿里云镜像有。
//!
//! 所以 Linux arm64 上:分类器改写(`Library::native_classifier` /
//! `Library::linux_arm64_native_name`)+ 地址兜底到镜像。**只在 linux + arm64 生效**,
//! x86_64 / Windows / macOS 的地址一个字节不变。用例仍在 `libraries.rs` 的 tests 里
//! (它们从 `plan()` 的出口断言,不直接调这里的私有步骤)。
//!
//! 从 `libraries.rs` 拆出来是为了守住 400 行红线(`guards.yml` 的 `file-size`)。
use std::path::{Path, PathBuf};
use crate::game::arguments::Environment;
use crate::game::library::Library;
/// Linux arm64 的 natives 兜底仓库:Mojang 的 `libraries.minecraft.net` 对
/// `natives-linux-arm64` 一律 404(实测 3.3.2 的 lwjgl/glfw/opengl/stb/tinyfd/openal),
/// 同一坐标在 Maven Central / 阿里云镜像有。
pub const LINUX_ARM64_NATIVES_BASE: &str = "https://maven.aliyun.com/repository/public/";
/// Linux arm64 的 natives 分类器(见 `Library::native_classifier`)。
pub const LINUX_ARM64_CLASSIFIER: &str = "natives-linux-arm64";
/// 现代清单里 LWJGL3 natives 条目的改写结果。
pub(super) struct Arm64Native {
pub(super) name: String,
pub(super) relative: PathBuf,
pub(super) url: String,
}
/// Linux arm64:把 `org.lwjgl:<module>:<version>:natives-linux` 改写成 arm64。
pub(super) fn linux_arm64_native_entry(
library: &Library,
env: &Environment,
) -> Option<Arm64Native> {
let name = library.linux_arm64_native_name(env)?;
let base = name.strip_suffix(":natives-linux-arm64")?;
let mut parts = base.split(':');
let group = parts.next()?;
let artifact = parts.next()?;
let version = parts.next()?;
if parts.next().is_some() {
return None;
}
let relative = PathBuf::from(format!(
"{}/{artifact}/{version}/{artifact}-{version}-natives-linux-arm64.jar",
group.replace('.', "/")
));
let url = format!(
"{LINUX_ARM64_NATIVES_BASE}{}",
relative.to_string_lossy().replace('\\', "/")
);
Some(Arm64Native {
name,
relative,
url,
})
}
/// arm64 natives 的兜底 URL(按相对路径拼镜像基址)。
pub(super) fn arm64_native_url(relative: &Path) -> String {
format!(
"{LINUX_ARM64_NATIVES_BASE}{}",
relative.to_string_lossy().replace('\\', "/")
)
}
+21 -153
View File
@@ -19,16 +19,21 @@
use std::path::{Path, PathBuf};
use super::error::{io_error, JavaError, JavaErrorKind};
use super::error::{JavaError, JavaErrorKind};
use super::extract;
use super::location::{JavaInstallRoot, LocationMode};
use super::probe::{self, JavaOrigin, JavaRuntime};
use super::release::{JdkArchive, JdkRelease};
use super::probe::{self, JavaRuntime};
use super::release::JdkRelease;
use super::source::{self, ChainLog};
use super::target::AdoptiumTarget;
use super::vendor::{self, JdkVendor};
use crate::download::{DownloadEngine, DownloadRequest, ResourceKind};
use one::install_one;
mod one;
mod termux_install;
/// 归档缓存目录名(在安装根下;托管目录扫描跳过它)。
pub use super::location::CACHE_DIR;
@@ -186,75 +191,24 @@ pub async fn install(
) -> Result<InstalledJdk, JavaError> {
let domestic = engine.region().is_domestic();
let priority = request.order(domestic);
let mut chain = ChainLog::new();
let mut reasons: Vec<String> = Vec::new();
// 清掉上次中断留下的 .staging-* 与空壳(只碰我们自己的形状,见 location 模块)。
let _ = super::location::cleanup_orphans(&location.root);
// Termux:JDK 由系统包管理器装进 `$PREFIX`,不是可下载的归档。
// 默认(没钉发行版)时优先走它;`--vendor termux` 时只走它。
let want_termux = match request.vendor {
Some(JdkVendor::Termux) => true,
Some(_) => false,
None => super::termux::available(),
// Termux 那一条路(系统包)单独成模块:它与归档链不共用任何一步,见 `termux_install`。
// 它把 chain 还回来(没走成时),归档链接着往上记。
let mut chain = match termux_install::attempt(
request.vendor,
request.major,
location,
ChainLog::new(),
&mut reasons,
)
.await?
{
termux_install::Outcome::Done(installed) => return Ok(*installed),
termux_install::Outcome::Skipped(chain) => chain,
};
if want_termux {
if !super::termux::available() {
return Err(
JavaError::new(JavaErrorKind::Unsupported, "termux 源只在 Termux 里可用")
.with_hint("普通 Linux/Windows 用 --vendor temurin|oracle|microsoft|zulu"),
);
}
match super::termux::install(request.major).await {
Ok((home, reused)) => {
let mut runtime = probe::probe(&extract::java_binary(&home), JavaOrigin::Managed);
runtime.vendor = Some(JdkVendor::Termux.id().to_string());
let version = runtime
.version
.clone()
.unwrap_or_else(|| request.major.to_string());
let release = JdkRelease {
vendor: JdkVendor::Termux,
major: request.major,
version,
semver: None,
release_name: format!("Termux openjdk-{}", request.major),
archive: JdkArchive {
name: format!("openjdk-{}.deb", request.major),
url: String::new(),
sha256: String::new(),
size: 0,
},
};
chain.hit(JdkVendor::Termux, format!("命中 {}", release.summary()));
return Ok(InstalledJdk {
major: release.major,
vendor: JdkVendor::Termux,
release,
root: home,
mode: location.mode,
archive: PathBuf::new(),
runtime,
downloaded: if reused {
None
} else {
Some("termux".to_string())
},
bytes: 0,
files: 0,
extracted: false,
chain,
});
}
Err(error) => {
if request.vendor == Some(JdkVendor::Termux) {
return Err(error);
}
chain.failed(JdkVendor::Termux, error.message().to_string());
reasons.push(format!("termux:{}", error.message()));
}
}
}
// 归档发行版需要 Adoptium 的平台参数;Termux/Android 上这里会 Unsupported,
// 不提前 `?` 退出——让每一家都记一条降级原因,错误里能看见全貌。
@@ -327,84 +281,6 @@ pub async fn install(
.with_hint("`ull java vendors` 看各发行版的可用性;或手动装 JDK 后设 JAVA_HOME"))
}
/// 某一家的中间结果(把七元组元组换成具名结构,读起来才不费劲)。
struct Prepared {
root: PathBuf,
archive: PathBuf,
runtime: JavaRuntime,
downloaded: Option<String>,
bytes: u64,
files: usize,
extracted: bool,
}
/// 装某一家的那一份(下载 → 解压 → 探测)。
async fn install_one(
engine: &DownloadEngine,
location: &JavaInstallRoot,
release: &JdkRelease,
) -> Result<Prepared, JavaError> {
let root = location.jdk_dir(release.major, release.vendor, &release.version);
let binary = extract::java_binary(&root);
let archive = archive_path(&location.root, release);
// 幂等第一层:戳记里的 sha256 与本次一致,且 bin/java 在 → 一个字节都不动。
if extract::is_ready(&root, release.sha256()) {
let runtime = probe::probe(&binary, JavaOrigin::Managed);
if runtime.major == Some(release.major) {
let mut runtime = runtime;
runtime.vendor = Some(release.vendor.id().to_string());
return Ok(Prepared {
root,
archive,
runtime,
downloaded: None,
bytes: 0,
files: 0,
extracted: false,
});
}
note(&format!(
"托管目录 {} 里的 java 跑不起来,重装一份",
root.display()
));
}
if let Some(parent) = archive.parent() {
std::fs::create_dir_all(parent)
.map_err(|error| io_error("创建归档缓存目录", parent.display(), &error))?;
}
let outcome = engine
.download(archive_request(release, archive.clone()))
.await?;
let report = extract::extract_archive(&archive, release.sha256(), release.major, &root)?;
let mut runtime = probe::probe(&binary, JavaOrigin::Managed);
runtime.vendor = Some(release.vendor.id().to_string());
if runtime.major != Some(release.major) {
return Err(JavaError::new(
JavaErrorKind::Probe,
format!(
"装好的 JDK 探测结果是 Java {},与请求的 {} 不符({})",
runtime
.major
.map_or_else(|| "未知".to_string(), |major| major.to_string()),
release.major,
binary.display()
),
)
.with_hint("删掉该托管目录后重跑 `ull java install`;或手动设 JAVA_HOME"));
}
Ok(Prepared {
root,
archive,
runtime,
downloaded: Some(outcome.source),
bytes: outcome.bytes,
files: report.files,
extracted: report.wrote(),
})
}
/// 只做规划不落盘:返回将要安装的版本、目标目录与「是否已经装好」。
///
/// # Errors
@@ -446,11 +322,3 @@ fn majors_text(vendor: JdkVendor) -> String {
.collect::<Vec<_>>()
.join("/")
}
fn note(message: &str) {
if cfg!(debug_assertions) {
eprintln!("[ull::game::java] {message}");
} else {
let _ = message;
}
}
+100
View File
@@ -0,0 +1,100 @@
//! 回退链里的「一家」怎么装:下载 → 解压 → 探测,外加幂等复用。
//!
//! 从 `install.rs` 拆出来是为了守住 400 行红线(`guards.yml` 的 `file-size`):
//! `install.rs` 只保留「按优先级链挑一家 / 记降级原因」的编排,这里放单家的机械步骤。
use std::path::PathBuf;
use super::super::error::{io_error, JavaError, JavaErrorKind};
use super::super::extract;
use super::super::location::JavaInstallRoot;
use super::super::probe::{self, JavaOrigin, JavaRuntime};
use super::super::release::JdkRelease;
use super::{archive_path, archive_request};
use crate::download::DownloadEngine;
/// 某一家的中间结果(把七元组元组换成具名结构,读起来才不费劲)。
pub(super) struct Prepared {
pub(super) root: PathBuf,
pub(super) archive: PathBuf,
pub(super) runtime: JavaRuntime,
pub(super) downloaded: Option<String>,
pub(super) bytes: u64,
pub(super) files: usize,
pub(super) extracted: bool,
}
/// 装某一家的那一份(下载 → 解压 → 探测)。
pub(super) async fn install_one(
engine: &DownloadEngine,
location: &JavaInstallRoot,
release: &JdkRelease,
) -> Result<Prepared, JavaError> {
let root = location.jdk_dir(release.major, release.vendor, &release.version);
let binary = extract::java_binary(&root);
let archive = archive_path(&location.root, release);
// 幂等第一层:戳记里的 sha256 与本次一致,且 bin/java 在 → 一个字节都不动。
if extract::is_ready(&root, release.sha256()) {
let runtime = probe::probe(&binary, JavaOrigin::Managed);
if runtime.major == Some(release.major) {
let mut runtime = runtime;
runtime.vendor = Some(release.vendor.id().to_string());
return Ok(Prepared {
root,
archive,
runtime,
downloaded: None,
bytes: 0,
files: 0,
extracted: false,
});
}
note(&format!(
"托管目录 {} 里的 java 跑不起来,重装一份",
root.display()
));
}
if let Some(parent) = archive.parent() {
std::fs::create_dir_all(parent)
.map_err(|error| io_error("创建归档缓存目录", parent.display(), &error))?;
}
let outcome = engine
.download(archive_request(release, archive.clone()))
.await?;
let report = extract::extract_archive(&archive, release.sha256(), release.major, &root)?;
let mut runtime = probe::probe(&binary, JavaOrigin::Managed);
runtime.vendor = Some(release.vendor.id().to_string());
if runtime.major != Some(release.major) {
return Err(JavaError::new(
JavaErrorKind::Probe,
format!(
"装好的 JDK 探测结果是 Java {},与请求的 {} 不符({})",
runtime
.major
.map_or_else(|| "未知".to_string(), |major| major.to_string()),
release.major,
binary.display()
),
)
.with_hint("删掉该托管目录后重跑 `ull java install`;或手动设 JAVA_HOME"));
}
Ok(Prepared {
root,
archive,
runtime,
downloaded: Some(outcome.source),
bytes: outcome.bytes,
files: report.files,
extracted: report.wrote(),
})
}
fn note(message: &str) {
if cfg!(debug_assertions) {
eprintln!("[ull::game::java] {message}");
} else {
let _ = message;
}
}
@@ -0,0 +1,105 @@
//! `install` 的 Termux 分支:JDK 由系统包管理器装进 `$PREFIX`,不是可下载的归档。
//!
//! 单独成模块有两个理由:`.deb` 这条路与归档链**没有共用步骤**(不查版本元数据、不下载、
//! 不解压、不校验 sha256),留在 `install()` 里只能是一段嵌在中间的 if;而 `install.rs`
//! 贴着 400 行红线(拆分前 456 行,见 `guards.yml` 的 `file-size`)。
use super::super::error::{JavaError, JavaErrorKind};
use super::super::extract;
use super::super::location::JavaInstallRoot;
use super::super::probe::{self, JavaOrigin};
use super::super::release::{JdkArchive, JdkRelease};
use super::super::source::ChainLog;
use super::super::vendor::JdkVendor;
use super::InstalledJdk;
/// Termux 分支的结果。
pub(super) enum Outcome {
/// 系统包装成了,`install` 直接返回它(装箱只为让枚举别撑到 392 字节,
/// 这条路一次安装才走一次)。
Done(Box<InstalledJdk>),
/// 不该走这条分支,或者走了但失败(原因已进 `chain` 与 `reasons`)——把 chain 还给调用方,
/// 由它继续跑归档回退链。
Skipped(ChainLog),
}
/// 尝试走 Termux 系统包。
///
/// 语义(D23「`--vendor` 钉死一家、不自动降级」):
/// - `pinned = Some(Termux)`:只走这条,失败直接冒泡;
/// - `pinned = None`:Termux 上先试系统包,失败记一条降级原因后继续归档链;
/// - 其它平台:`Ok(Skipped(chain))`,一个字节都不动。
///
/// # Errors
///
/// 只有「钉死 termux」且失败时才返回错误;其余失败一律降级成 `reasons` 里的一行。
pub(super) async fn attempt(
pinned: Option<JdkVendor>,
major: u32,
location: &JavaInstallRoot,
chain: ChainLog,
reasons: &mut Vec<String>,
) -> Result<Outcome, JavaError> {
let wanted = match pinned {
Some(JdkVendor::Termux) => true,
Some(_) => false,
None => super::super::termux::available(),
};
if !wanted {
return Ok(Outcome::Skipped(chain));
}
if !super::super::termux::available() {
return Err(
JavaError::new(JavaErrorKind::Unsupported, "termux 源只在 Termux 里可用")
.with_hint("普通 Linux/Windows 用 --vendor temurin|oracle|microsoft|zulu"),
);
}
let mut chain = chain;
match super::super::termux::install(major).await {
Ok((home, reused)) => {
let mut runtime = probe::probe(&extract::java_binary(&home), JavaOrigin::Managed);
runtime.vendor = Some(JdkVendor::Termux.id().to_string());
let version = runtime.version.clone().unwrap_or_else(|| major.to_string());
let release = JdkRelease {
vendor: JdkVendor::Termux,
major,
version,
semver: None,
release_name: format!("Termux openjdk-{major}"),
archive: JdkArchive {
name: format!("openjdk-{major}.deb"),
url: String::new(),
sha256: String::new(),
size: 0,
},
};
chain.hit(JdkVendor::Termux, format!("命中 {}", release.summary()));
Ok(Outcome::Done(Box::new(InstalledJdk {
major: release.major,
vendor: JdkVendor::Termux,
release,
root: home,
mode: location.mode,
archive: std::path::PathBuf::new(),
runtime,
downloaded: if reused {
None
} else {
Some("termux".to_string())
},
bytes: 0,
files: 0,
extracted: false,
chain,
})))
}
Err(error) => {
if pinned == Some(JdkVendor::Termux) {
return Err(error);
}
chain.failed(JdkVendor::Termux, error.message().to_string());
reasons.push(format!("termux:{}", error.message()));
Ok(Outcome::Skipped(chain))
}
}
}