From 4dbeb07220428829b6a2431959b56a3a9b59b115 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sat, 7 Nov 2026 09:19:18 +0800 Subject: [PATCH] =?UTF-8?q?refactor(core):=20=E6=8B=86=E5=88=86=E8=B6=8A?= =?UTF-8?q?=20400=20=E8=A1=8C=E7=BA=A2=E7=BA=BF=E7=9A=84=20install.rs=20?= =?UTF-8?q?=E4=B8=8E=20install/libraries.rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 合并 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 行,红线内。 --- crates/core/src/game/install/libraries.rs | 53 +----- .../game/install/libraries/arm64_natives.rs | 72 ++++++++ crates/core/src/game/java/install.rs | 174 +++--------------- crates/core/src/game/java/install/one.rs | 100 ++++++++++ .../src/game/java/install/termux_install.rs | 105 +++++++++++ 5 files changed, 302 insertions(+), 202 deletions(-) create mode 100644 crates/core/src/game/install/libraries/arm64_natives.rs create mode 100644 crates/core/src/game/java/install/one.rs create mode 100644 crates/core/src/game/java/install/termux_install.rs diff --git a/crates/core/src/game/install/libraries.rs b/crates/core/src/game/install/libraries.rs index 1fa0ccb..ea92fce 100644 --- a/crates/core/src/game/install/libraries.rs +++ b/crates/core/src/game/install/libraries.rs @@ -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:::natives-linux` 改写成 arm64。 -fn linux_arm64_native_entry(library: &Library, env: &Environment) -> Option { - 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 { diff --git a/crates/core/src/game/install/libraries/arm64_natives.rs b/crates/core/src/game/install/libraries/arm64_natives.rs new file mode 100644 index 0000000..fc0c7f9 --- /dev/null +++ b/crates/core/src/game/install/libraries/arm64_natives.rs @@ -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:::natives-linux` 改写成 arm64。 +pub(super) fn linux_arm64_native_entry( + library: &Library, + env: &Environment, +) -> Option { + 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('\\', "/") + ) +} diff --git a/crates/core/src/game/java/install.rs b/crates/core/src/game/java/install.rs index 9201d85..358b22c 100644 --- a/crates/core/src/game/java/install.rs +++ b/crates/core/src/game/java/install.rs @@ -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 { let domestic = engine.region().is_domestic(); let priority = request.order(domestic); - let mut chain = ChainLog::new(); let mut reasons: Vec = 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, - bytes: u64, - files: usize, - extracted: bool, -} - -/// 装某一家的那一份(下载 → 解压 → 探测)。 -async fn install_one( - engine: &DownloadEngine, - location: &JavaInstallRoot, - release: &JdkRelease, -) -> Result { - 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::>() .join("/") } - -fn note(message: &str) { - if cfg!(debug_assertions) { - eprintln!("[ull::game::java] {message}"); - } else { - let _ = message; - } -} diff --git a/crates/core/src/game/java/install/one.rs b/crates/core/src/game/java/install/one.rs new file mode 100644 index 0000000..e213e22 --- /dev/null +++ b/crates/core/src/game/java/install/one.rs @@ -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, + 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 { + 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; + } +} diff --git a/crates/core/src/game/java/install/termux_install.rs b/crates/core/src/game/java/install/termux_install.rs new file mode 100644 index 0000000..62d8ef6 --- /dev/null +++ b/crates/core/src/game/java/install/termux_install.rs @@ -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), + /// 不该走这条分支,或者走了但失败(原因已进 `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, + major: u32, + location: &JavaInstallRoot, + chain: ChainLog, + reasons: &mut Vec, +) -> Result { + 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)) + } + } +}