feat(java): --version 指定 JDK 版本(Temurin/Oracle 能,MS 只认快照,Zulu 报错)+ --dry-run(M280)
用户原话:「我们内置了那么多镜像源 甚至用户不能手动选择指定版本下载。」这条之前
`InstallRequest` 根本没有版本字段,四家全是「该 major 最新 GA」。
core:
- `InstallRequest.version: Option<String>` + `with_version`;`None` 时仍走 `source::fetch`
的老路径(`latest_url` / `parse_latest` / `fetch_latest` 一个字节未动)。
- `source::fetch` 在唯一分派点按 `Option<&str>` 分两路:`latest` 与 `pinned`,后者绝不换家。
- Temurin(新 `api/version.rs`):先 `assets/feature_releases/{major}/ga` 枚举(`page_size`
上限 20),把用户给的公共版本号翻成 release 名,再打 `assets/release_name/eclipse/{name}`
取归档——不走 `latest`。实测 `/v3/assets/version/{v}` 在线上回 404,故不用它(见报告)。
- Oracle:`versions_for` 接进选择路径(`select_version`),`None` 仍是 `pick_version`。
- Microsoft:只认写死快照里那一个版本号,别的版本明确报错并说清「这家不能自选版本」。
- Zulu / Termux:上游只有 latest / 版本由系统包定 → 明确报错,不回落、不换家。
- `catalog.rs` 收策略:`matches_version`(唯一相等判据,不做前缀近似)、
`version_pin_error`(四家文案互不相同且各自说清原因 + 下一步)、`version_mismatch`
(兜底闸:装到的版本必须真是用户要的那一版)。
- `plan()` 改成接 `&InstallRequest`,`--dry-run` 与真装走同一条选择路径。
cli:
- `java install <major> [--version V] [--dry-run] [--vendor X]`;`--json` 增 `requested_version`。
- 全局 `--version` 只在子命令之前成立,否则 `java install 21 --version 21.0.13+9` 会被
当成「打印启动器版本」吞掉。
拆分(守 400 行硬线,均为纯搬移 + 逐字节核对):
- `api.rs` → `api/version.rs`、`oracle.rs` → `oracle/listing.rs`(纯的那一半)、
`cli/parse.rs` → `cli/parse/java.rs`、`commands/java.rs` → `commands/java_install.rs`。
门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-280):core --lib / --test game / --test launch、
cli --all-targets、fmt --check、clippy -D warnings 全部 EXIT=0。
This commit is contained in:
1 parent
c8f820caec
commit
9fded45d9e
16 files changed
+1352
-346
No files matched your search
@@ -16,11 +16,9 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use unlucky_core::download::blocking::block_on;
|
||||
use unlucky_core::download::{DownloadEngine, RegionProfile};
|
||||
use unlucky_core::game::java::{
|
||||
self, location, vendor, InstallRequest, JavaOrigin, JavaRuntime, JdkVendor, LocationMode,
|
||||
DEFAULT_MAJOR,
|
||||
self, location, vendor, JavaOrigin, JavaRuntime, JdkVendor, LocationMode, DEFAULT_MAJOR,
|
||||
};
|
||||
use unlucky_core::launch::request::JavaSelection;
|
||||
use unlucky_core::platform::paths::ProcessEnv;
|
||||
@@ -31,12 +29,10 @@ use crate::context::Ctx;
|
||||
use crate::error::CliError;
|
||||
use crate::output::Report;
|
||||
|
||||
pub use super::java_install::install;
|
||||
pub use super::java_launch::{resolve_for_launch, LaunchJava};
|
||||
pub use super::java_vendors::vendors;
|
||||
|
||||
/// 可自动安装的最老大版本(再老的 Temurin 没有 GA 产物)。
|
||||
const MIN_INSTALLABLE_MAJOR: u32 = 8;
|
||||
|
||||
/// 装配用的 Java 选择(`--java` > `JAVA_HOME` > PATH 上的 `java`)。
|
||||
///
|
||||
/// `snapshot::prepare` 调它拿 [`JavaSelection`];真正的来源优先级在
|
||||
@@ -131,83 +127,6 @@ pub fn list(ctx: &Ctx) -> Result<Report, CliError> {
|
||||
Ok(Report::new(data, human))
|
||||
}
|
||||
|
||||
/// `ull java install <major> [--vendor X]`:回退链查版本 → 下载 → 解压 → `java -version` 验证。
|
||||
///
|
||||
/// 幂等:同一个归档装过就直接复用(不联网、不解压)。下载走 `core::download` 引擎,
|
||||
/// 国内环境按 D7 的地区策略优先院校/国内镜像。
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 大版本不合法、平台无产物、所有发行版都失败(网络/校验/解压/跑不起来)时返回用户错误。
|
||||
pub fn install(ctx: &Ctx, major: u32, vendor: Option<JdkVendor>) -> Result<Report, CliError> {
|
||||
if !(MIN_INSTALLABLE_MAJOR..=99).contains(&major) {
|
||||
return Err(CliError::usage(format!(
|
||||
"大版本号看起来不对:{major}(可用 8 / 11 / 17 / 21 / 25 这类 GA 大版本)"
|
||||
)));
|
||||
}
|
||||
let settings = JavaSettings::load(ctx);
|
||||
let engine = build_engine(ctx);
|
||||
let install_root = settings.location(ctx)?;
|
||||
let request = match vendor {
|
||||
Some(vendor) => {
|
||||
InstallRequest::with_vendor(major, vendor).with_priority(settings.priority.clone())
|
||||
}
|
||||
None => InstallRequest::new(major).with_priority(settings.priority.clone()),
|
||||
};
|
||||
let installed =
|
||||
block_on(java::install(&engine, &install_root, request)).map_err(map_java_error)?;
|
||||
|
||||
let data = json!({
|
||||
"major": installed.major,
|
||||
"vendor": installed.vendor.id(),
|
||||
"release": installed.release.release_name,
|
||||
"version": installed.release.version,
|
||||
"semver": installed.release.semver,
|
||||
"archive": installed.release.archive.name,
|
||||
"sha256": installed.release.archive.sha256,
|
||||
"size": installed.release.archive.size,
|
||||
"root": installed.root.display().to_string(),
|
||||
"install_mode": installed.mode.as_str(),
|
||||
"binary": installed.binary().display().to_string(),
|
||||
"reused": installed.reused(),
|
||||
"source": installed.downloaded,
|
||||
"bytes": installed.bytes,
|
||||
"files": installed.files,
|
||||
"extracted": installed.extracted,
|
||||
"chain": installed.chain.to_json(),
|
||||
"runtime": candidate_json(&installed.runtime),
|
||||
});
|
||||
|
||||
let mut human = Vec::new();
|
||||
if installed.reused() {
|
||||
human.push(format!(
|
||||
"Java {}({})已就绪({}),未走网络",
|
||||
installed.major,
|
||||
installed.vendor.id(),
|
||||
installed.root.display()
|
||||
));
|
||||
} else {
|
||||
human.push(format!(
|
||||
"已安装 Java {}({},来源 {},{} 字节 / {} 个文件)",
|
||||
installed.major,
|
||||
installed.release.summary(),
|
||||
installed.downloaded.as_deref().unwrap_or("?"),
|
||||
installed.bytes,
|
||||
installed.files
|
||||
));
|
||||
}
|
||||
human.push(format!("落点:{}", installed.location_line()));
|
||||
human.push(format!("可执行文件:{}", installed.binary().display()));
|
||||
if let Some(version) = &installed.runtime.version {
|
||||
human.push(format!("探测:{version}"));
|
||||
}
|
||||
for note in installed.chain.degradations() {
|
||||
human.push(format!("降级:{note}"));
|
||||
}
|
||||
human.push("`%prog% launch` 会自动优先用它".to_string());
|
||||
Ok(Report::new(data, human))
|
||||
}
|
||||
|
||||
/// 设置里与 JDK 有关的两项(`java.installRoot` / `java.vendorPriority`)。
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub(crate) struct JavaSettings {
|
||||
@@ -255,7 +174,7 @@ impl JavaSettings {
|
||||
}
|
||||
|
||||
/// 真正要装到哪(解析失败 → 用户错误,带可照着做的提示)。
|
||||
fn location(&self, ctx: &Ctx) -> Result<java::JavaInstallRoot, CliError> {
|
||||
pub(super) fn location(&self, ctx: &Ctx) -> Result<java::JavaInstallRoot, CliError> {
|
||||
java::JavaInstallRoot::resolve(
|
||||
ctx.data.mode,
|
||||
&ctx.layout(),
|
||||
@@ -268,7 +187,7 @@ impl JavaSettings {
|
||||
}
|
||||
|
||||
/// 落点模式 → 人读一行(含「没设覆盖键时默认落在哪」的解释)。
|
||||
fn describe_mode(mode: LocationMode, override_root: Option<&Path>) -> String {
|
||||
pub(super) fn describe_mode(mode: LocationMode, override_root: Option<&Path>) -> String {
|
||||
match mode {
|
||||
LocationMode::Custom => format!(
|
||||
"custom(java.installRoot = {})",
|
||||
@@ -282,7 +201,7 @@ fn describe_mode(mode: LocationMode, override_root: Option<&Path>) -> String {
|
||||
}
|
||||
|
||||
/// 一个运行时的 `--json` 形状。
|
||||
fn candidate_json(runtime: &JavaRuntime) -> Value {
|
||||
pub(super) fn candidate_json(runtime: &JavaRuntime) -> Value {
|
||||
json!({
|
||||
"binary": runtime.binary.display().to_string(),
|
||||
"source": runtime.origin.as_str(),
|
||||
@@ -314,7 +233,7 @@ pub(crate) fn build_engine(ctx: &Ctx) -> Arc<DownloadEngine> {
|
||||
}
|
||||
|
||||
/// JDK 下载/解压错误 → CLI 错误(保留稳定码与提示)。
|
||||
fn map_java_error(error: java::JavaError) -> CliError {
|
||||
pub(super) fn map_java_error(error: java::JavaError) -> CliError {
|
||||
let cli = CliError::user(error.code(), error.message().to_string());
|
||||
match error.hint() {
|
||||
Some(hint) => cli.with_hint(hint.to_string()),
|
||||
|
||||
Reference in new issue
Block a user