验收要的是「一次运行、多角度覆盖、输出能直接判读」,而不是把朋友当测试机反复问 7 个问题。 这一轮把产品进程能**自己观测**的那部分日志补齐,并把人工问题压缩到 3 条。 新增 `renderer/acceptance/`(三块,全部 ≤400 行): - `mod.rs`:`[backend]`(plan = 判据,final = **结果**:`result=started|failed` + 具名错误 + `via=respawn|refuse|none`)、`[perf]`(boot 三段实测 / session / scene 帧率只算首帧之后的间隔)、 `[theme]`(来源 = 哪个参数决定,系统那条写 `pending` 等异步真值)、`[window]`(**请求值**, 字段名自证 `requested` / `scale_requested`);开关 `ULL_ACCEPTANCE=1`、尺寸覆盖 `ULL_WINDOW_SIZE=1520x900` 与它的解析/夹取(含 `size_source=` 四态)。 - `locale.rs`:`[env]`(真适配器枚举 + 硬件判定;显示器写 `needs-window` 不猜)、`[i18n]` (四级链每一级的原始值 + 结论; macOS 走 `defaults read -g AppleLanguages`)。 - `visual.rs`:`[font] chain`(令牌声明的回退链,与已有的实际落点 `[font] platform=` 互补)、 `[caps]`(`bundle` / `menu_bar_name` 从可执行文件路径**读**出来;`controls=self-drawn` 由 `decorations=false` 推出)。 接线:`app/boot.rs` 在正确的时刻把真值喂进去(探测产物 / 窗口设置 / 实测 `Instant`), `pages/harness.rs` 同一套前缀(含 `[perf] scene`),`main.rs` 用法里写出两个开关。 反模式的防线(M97 那类「在需要结果的地方打印请求」):每一格都有能改坏的断言 (`acceptance/tests.rs` + `locale_tests.rs`,共 20 条新增),失败路径的 `error="…"` 会把 wgpu 自带的双引号换成单引号,保证单行可解析。 本机(Windows)实测:auto → `[backend] final=wgpu result=started` + `Selected: AdapterInfo`; cpu → `tiny-skia`;`WGPU_BACKEND=metal` 负例 → exit 1 + 无图 + `via=refuse`;宽屏覆盖生效。 macOS 分支(Metal / AppleLanguages / 菜单栏名)需真机复核,期望字符串见 `renderer/mod.rs` 文档。 验收脚本不入库(用户要求):落在 gitignore 的 `DevDocs/ops/macos-acceptance.sh`, 由 tower 拷到分发目录。
104 lines
4.6 KiB
Rust
104 lines
4.6 KiB
Rust
//! 入口。所有实际逻辑在 lib(`unlucky_app_iced`)里:
|
||
//!
|
||
//! 一个二进制两种用法(M82),分发在进 UI 之前完成:
|
||
//!
|
||
//! - 被子命令命中(`instances` / `install` / `launch` / `accounts` / `sessions` / `logs` /
|
||
//! `diag` / `config` / `java` / `agent`,或 `--help` / `--version`)→ 直接转给
|
||
//! `unlucky_cli::run`,**不初始化任何 UI**(不 probe 后端、不扫字体、不碰图形库)。
|
||
//! 子命令表不在这里抄一份:唯一真相源是 `crates/cli` 的解析器
|
||
//! (`unlucky_cli::cli::looks_like_cli`,本文件只调用)。
|
||
//! - 其余参数(`--software-renderer` / `--renderer` / `--theme` / `--screenshot*` /
|
||
//! `--diff` …):开窗跑骨架,`--screenshot` 出图后退出;`--diff <a> <b>` 只读两张 PNG
|
||
//! 做逐像素对比(D30 的 GPU/CPU 几何一致性检查),不开窗。
|
||
//!
|
||
//! 零 UI 依赖的 `ull` 仍然保留(D18):它是唯一连 fontconfig 都不需要的产物,
|
||
//! 给服务器 / CI / Termux 这类没有图形库的机器用。
|
||
//!
|
||
//! 退出码:`0` 成功 / `1` 截图有差异(diff 模式)或 CLI 用户错误 / `2` 参数或运行错误。
|
||
|
||
use std::process::ExitCode;
|
||
|
||
use unlucky_app_iced::shot;
|
||
use unlucky_app_iced::Options;
|
||
|
||
fn main() -> ExitCode {
|
||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||
if let Some(code) = run_cli_if_requested(&args) {
|
||
return code;
|
||
}
|
||
|
||
// M97:把 iced_wgpu / wgpu 的 `log` 输出接到 stderr。`Selected: AdapterInfo{…}` 那行
|
||
// 是「GPU 真生效」的实证——没有它,日志只能证明我们**请求过** GPU。
|
||
// CLI 子命令在上面就返回了;`--diff` 虽然也装,但它不碰 wgpu,不会多出任何一行。
|
||
unlucky_app_iced::renderer::log_bridge::install();
|
||
|
||
match run_gui() {
|
||
Ok(code) => code,
|
||
Err(message) => {
|
||
eprintln!("[cli] {message}");
|
||
eprintln!(
|
||
"用法:unlucky-launcher-iced [--renderer=auto|gpu|cpu] [--software-renderer] \
|
||
[--theme=light|dark] [--screenshot <png>] [--screenshot-theme=light|dark] \
|
||
[--scale-factor <f32>] [--diff <a.png> <b.png>]"
|
||
);
|
||
// M98:验收开关写在用法里,不然「日志怎么变详细了」只能靠翻文档。
|
||
eprintln!(
|
||
"验收诊断:ULL_ACCEPTANCE=1([env]/[backend]/[window]/[font]/[theme]/[perf]/[i18n]/[caps])\
|
||
,ULL_WINDOW_SIZE=1520x900 覆盖窗口尺寸"
|
||
);
|
||
ExitCode::from(2)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 子命令命中 CLI 就跑 CLI 并以它的退出码收尾;返回 `None` 表示「这不是 CLI 调用,去开窗」。
|
||
///
|
||
/// 这条路径上**不构造窗口、不探后端、不扫字体、不依赖任何图形库**(这些库缺失时同一条命令照样
|
||
/// 跑完,实测见 packaging/README.md §7.4)。`unlucky-launcher-iced launch …` 的耗时因此与首帧无关——
|
||
/// 这句是结构性判断,**没有重量**(原因见 §10-8)。
|
||
///
|
||
/// 「这条命令行算不算 CLI 调用」的判据不在这里重复:`unlucky_cli::cli::looks_like_cli` 是唯一
|
||
/// 一处(M82 复审去重),两个 GUI 二进制都只调它,改语义不会漂。
|
||
fn run_cli_if_requested(args: &[String]) -> Option<ExitCode> {
|
||
if !unlucky_cli::cli::looks_like_cli(args) {
|
||
return None;
|
||
}
|
||
let mut stdout = std::io::stdout();
|
||
let mut stderr = std::io::stderr();
|
||
let code = unlucky_cli::run(args, &mut stdout, &mut stderr);
|
||
let _ = std::io::Write::flush(&mut stdout);
|
||
let _ = std::io::Write::flush(&mut stderr);
|
||
Some(ExitCode::from(u8::try_from(code).unwrap_or(2)))
|
||
}
|
||
|
||
/// 开窗路径:解析 GUI 参数 → (`--diff` 走比对)→ 起 UI。
|
||
fn run_gui() -> Result<ExitCode, String> {
|
||
let options = Options::from_env()?;
|
||
|
||
if let Some((left, right)) = options.diff.clone() {
|
||
let scan = i32::try_from(options.diff_scan).unwrap_or(i32::MAX);
|
||
return match shot::diff_files_with_scan(&left, &right, scan) {
|
||
Ok(diff) => {
|
||
eprintln!("{}", diff.describe());
|
||
if diff.is_identical() {
|
||
Ok(ExitCode::SUCCESS)
|
||
} else {
|
||
Ok(ExitCode::from(1))
|
||
}
|
||
}
|
||
Err(message) => {
|
||
eprintln!("[diff] {message}");
|
||
Ok(ExitCode::from(2))
|
||
}
|
||
};
|
||
}
|
||
|
||
match unlucky_app_iced::run(options) {
|
||
Ok(()) => Ok(ExitCode::SUCCESS),
|
||
Err(error) => {
|
||
eprintln!("[run] {error}");
|
||
Ok(ExitCode::FAILURE)
|
||
}
|
||
}
|
||
}
|