fix(core): launch --dry-run 不再落盘(natives 只预览,零建目录/零写文件)
`ull launch --dry-run` 在「环境准备」段走的是真解压:即使只打印命令行,也会在 `versions/<id>/natives/` 下建目录、解 dll、写戳记,把别人的版本树改脏(父目录 mtime 也变)。失败路径(缺 jar 报 missing_components)照样留下这些副作用。 - natives 新增 `preview()`:与 `extract()` 共用条目筛选与戳记口径,但零落盘 (无 create_dir_all / clear_natives / write_stamp),只清点「将写下几个原生库」; jar 打不开照样报错,预览不藏坏包。 - `NativesReport` 增 `planned` / `dir_exists`:预览报告自报家门,人读行与 JSON 说清「未落盘」「目录尚不存在」。 - `Launcher::dry_run` 改走 `preview_environment`,`spawn` 仍走 `prepare_environment`。 - 报告类型拆到 `natives/report.rs`(mod.rs 曾破 400 行红线)。 - 回归测试(natives_dry_run_cases.rs):有 jar / 无 jar / 空 jar / 已解压过四条 零落盘判据,用「节点类型 + 纳秒 mtime」快照逐字比较版本树,并带一条判据自检 (瞬时建删目录必须被快照看见,防假绿)。 - help:`--dry-run` 的承诺补成「不写盘、不解压 natives、不建目录」。 证据(外部版本树 versions/1.16.5 + 真 natives jar): 修复前 dry-run 后 versions/1.16.5 mtime 1794342830.6861426 → 1794342832.4544542, 新增 natives/ + lwjgl.dll + OpenAL.dll + .ull-natives.json;修复后同一场景快照逐字 不变(mtime 1794343730.6618167),JSON natives.planned=true / dir_exists=false。
This commit is contained in:
1 parent
774a547aa0
commit
1fa30c4885
11 files changed
+583
-239
No files matched your search
@@ -2,44 +2,12 @@
|
||||
//!
|
||||
//! 假 jar 用 `zip` 的 Stored 模式现造(不依赖真下载),真机验收另有人工步骤。
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use zip::write::SimpleFileOptions;
|
||||
use zip::{CompressionMethod, ZipWriter};
|
||||
use std::path::Path;
|
||||
|
||||
use unlucky_core::launch::natives::{self, NativeJar, NativesEra, NativesPlan, STAMP_FILE};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// 造一个 natives jar:`entries` 是(条目名, 内容)。
|
||||
fn make_jar(path: &Path, entries: &[(&str, &[u8])]) {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).expect("建 jar 目录");
|
||||
}
|
||||
let file = File::create(path).expect("建 jar");
|
||||
let mut writer = ZipWriter::new(file);
|
||||
let options = SimpleFileOptions::default().compression_method(CompressionMethod::Stored);
|
||||
for (name, bytes) in entries {
|
||||
writer.start_file(*name, options).expect("写条目");
|
||||
writer.write_all(bytes).expect("写条目内容");
|
||||
}
|
||||
writer.finish().expect("收尾 jar");
|
||||
}
|
||||
|
||||
/// 一份 1.16.5 风格的 natives jar 的条目集(原生库 + 该被排除的噪音)。
|
||||
fn lwjgl_jar_entries() -> Vec<(&'static str, &'static [u8])> {
|
||||
vec![
|
||||
("lwjgl.dll", b"DLL-BYTES".as_slice()),
|
||||
("OpenAL.dll", b"OPENAL-BYTES".as_slice()),
|
||||
("META-INF/MANIFEST.MF", b"manifest".as_slice()),
|
||||
("META-INF/LWJGL.SF", b"signature".as_slice()),
|
||||
("lwjgl.dll.sha1", b"deadbeef".as_slice()),
|
||||
("README.txt", b"noise".as_slice()),
|
||||
]
|
||||
}
|
||||
|
||||
fn plan(jars: Vec<NativeJar>) -> NativesPlan {
|
||||
NativesPlan::new(jars)
|
||||
}
|
||||
@@ -208,74 +176,3 @@ fn natives_extracted_event_carries_report_fields() {
|
||||
.expect("dir 是字符串")
|
||||
.ends_with("natives"));
|
||||
}
|
||||
|
||||
/// `--dry-run` 也做环境准备:第二次跑要看到 up_to_date(真机验收的判定依据)。
|
||||
#[test]
|
||||
fn dry_run_prepares_natives_and_is_idempotent() {
|
||||
let root = temp_dir("natives-dry-run");
|
||||
let jar = root.join("natives-windows.jar");
|
||||
make_jar(&jar, &[("lwjgl.dll", b"DLL".as_slice())]);
|
||||
let natives_dir: PathBuf = root.join("versions/1.16.5/natives");
|
||||
|
||||
let (mut request, _) = legacy_forge_request();
|
||||
request.dirs.natives_dir = natives_dir.clone();
|
||||
request.natives = NativesPlan::new(vec![NativeJar::new(&jar, "lwjgl")]);
|
||||
request.manifest.legacy_arguments = Some("--username ${auth_player_name}".to_string());
|
||||
|
||||
let sessions = temp_sessions_root("natives-dry-run-sessions");
|
||||
let launcher = Launcher::new(LauncherConfig::new(&sessions));
|
||||
|
||||
let first = launcher.dry_run(&request).expect("首次 dry-run");
|
||||
let report = first.natives.expect("应有 natives 报告");
|
||||
assert_eq!(report.files, 1, "{report:?}");
|
||||
assert!(!report.up_to_date);
|
||||
assert!(natives_dir.join("lwjgl.dll").is_file());
|
||||
|
||||
let second = launcher.dry_run(&request).expect("二次 dry-run");
|
||||
let report = second.natives.expect("应有 natives 报告");
|
||||
assert!(report.up_to_date, "第二次应命中戳记:{report:?}");
|
||||
assert_eq!(report.skipped, 1);
|
||||
}
|
||||
|
||||
/// 缺 natives jar:适配层当成组件缺失(组装前显式失败),而不是等到 UnsatisfiedLinkError。
|
||||
#[test]
|
||||
fn missing_native_jar_becomes_missing_component() {
|
||||
let (mut request, ctx) = legacy_forge_request();
|
||||
request.natives = NativesPlan::new(vec![NativeJar::new(
|
||||
"C:/mc/libraries/missing-natives-windows.jar",
|
||||
"org.lwjgl:lwjgl:3.2.2:natives-windows",
|
||||
)]);
|
||||
// 直接验证适配层:给一个不存在的 natives jar,严格模式应产出 MissingNativeJar 告警。
|
||||
let libraries: Vec<LibraryInput> = request
|
||||
.classpath
|
||||
.iter()
|
||||
.map(|path| LibraryInput::new(path.clone(), "x"))
|
||||
.collect();
|
||||
let natives = request.natives.jars.clone();
|
||||
let snapshot = SnapshotInput {
|
||||
version_id: "1.16.5",
|
||||
main_class: Some("net.minecraft.client.main.Main"),
|
||||
libraries: &libraries,
|
||||
natives: &natives,
|
||||
..SnapshotInput::minimal("1.16.5", "net.minecraft.client.main.Main")
|
||||
};
|
||||
let options = AdapterOptions {
|
||||
java: JavaSelection::new("C:/java/bin/java.exe", 17, 64),
|
||||
..AdapterOptions::strict()
|
||||
};
|
||||
let output = build_request(&snapshot, options);
|
||||
assert!(
|
||||
output
|
||||
.warnings
|
||||
.iter()
|
||||
.any(|warning| matches!(warning, AdapterWarning::MissingNativeJar { .. })),
|
||||
"应有 MissingNativeJar 告警:{:?}",
|
||||
output.warnings
|
||||
);
|
||||
assert_eq!(output.request.natives.era, NativesEra::Legacy);
|
||||
// 组装按既有语义直接失败(missing_components 非空)。
|
||||
assert!(matches!(
|
||||
assemble(&output.request, &ctx).err(),
|
||||
Some(LaunchError::Assembly(AssemblyError::MissingComponents(_)))
|
||||
));
|
||||
}
|
||||
Reference in new issue
Block a user