「编得过」不等于「跑得起来」:CI 里 Linux/macOS 此前只 build + test,从未创建过真实图形窗口。 本 commit 补第一份 Linux 真实图形环境运行证据,并把它做成不可伪造的门禁。 - ci.yml 新增 runtime-smoke(ubuntu-latest):装 xvfb + 软渲染/字体依赖,用 xvfb-run 起 GUI 进程, 走既有 `--software-renderer --screenshot` 通道渲出一帧 PNG,再断言 ①退出码 0 ②PNG 宽高 > 0 ③非空白(唯一色数 / 亮度σ / 强边缘占比三项过阈值),并把截图作为 artifact `runtime-smoke-screenshot` 上传。 既有 fmt/clippy/test/build 四个 job 与触发器语义零改动(纯追加)。 - ci.yml 新增 runtime-smoke-macos 探针:只在 workflow_dispatch 触发且 continue-on-error。官方 macOS runner 处于 LaunchDaemon 上下文、无 WindowServer 会话,大概率建不了窗口;本机无 macOS 且 worker 禁 push,这条路径尚无实测证据,故不拿它卡 PR 门禁(首跑结论决定升常驻或按 D26 归入 iced 重写)。 - packaging/check_screenshot.py:零第三方依赖的 PNG 解码(zlib + 反过滤 0~4)与三项指标判定, `--selftest` 打印阈值依据(人为纯色帧 1/0.0/0.0 必红,真实帧 342/19.45/0.00761 全过线)。 - packaging/linux-smoke-docker.sh:在 Docker 里复刻同一 job(TUNA apt/rustup 源),便于无 Linux 桌面的本地复跑。 - packaging/README.md §11:覆盖平台、阈值依据数值、本地复跑、已知跳过项、迁 iced 后需重新验证。 本机实测(Debian 12 容器 + xvfb + 软渲染):退出码 0,PNG 1180x760,唯一色 342 / σ 19.45 / 强边缘 0.00761。 CI(Ubuntu 24.04 runner)首跑仍未验证——本 job 证明「能出图」,不证明视觉正确。
91 lines
4.2 KiB
Bash
91 lines
4.2 KiB
Bash
#!/usr/bin/env bash
|
||
# 本地复跑 `.github/workflows/ci.yml` 的 `runtime-smoke` job:Linux + Xvfb + 软渲染截图。
|
||
#
|
||
# 为什么要有这条脚本:CI 的 runtime-smoke 在 GitHub 的 ubuntu-latest 上跑,
|
||
# 但「改动这个 job 的人」需要在没有 Linux 桌面的开发机上先自证一遍——否则改的就是没人验过的 YAML。
|
||
# 本脚本在 Docker 里复刻 job 的步骤(同样的 apt 依赖、同样的构建、同样的 xvfb-run 命令),
|
||
# 并把截图与判定结果留在宿主目录,供人眼与 `check_screenshot.py` 复核。
|
||
#
|
||
# 用法:
|
||
# bash packaging/linux-smoke-docker.sh [仓库根目录] [输出目录]
|
||
# ULL_SMOKE_SKIP_BUILD=1 bash packaging/linux-smoke-docker.sh # 复用缓存的二进制
|
||
#
|
||
# 说明(诚实边界):
|
||
# - 基础镜像 `rust:1-bookworm` 是 **Debian 12(glibc 2.36)**,官方 runner 是 **Ubuntu 24.04(glibc 2.39)**。
|
||
# 两者图形栈(X11/xvfb/Mesa 软渲染)来源不同,因此本脚本证明的是「Linux 上这条通道能真跑出图」,
|
||
# 不能代替官方 runner 的首跑验证。
|
||
# - 国内网络慢时可换 TUNA 镜像:脚本默认就用 TUNA 的 Debian 源(`ULL_APT_MIRROR` 可覆盖)。
|
||
|
||
set -euo pipefail
|
||
|
||
repo_root=$(cd "${1:-.}" && pwd)
|
||
out_dir=${2:-/tmp/ull-smoke}
|
||
image=${ULL_SMOKE_IMAGE:-ull-linux-smoke:bookworm}
|
||
cargo_volume=${ULL_CARGO_VOLUME:-ull-cargo-reg}
|
||
target_volume=${ULL_CARGO_TARGET_VOLUME:-ull-linux-target}
|
||
apt_mirror=${ULL_APT_MIRROR:-https://mirrors.tuna.tsinghua.edu.cn}
|
||
rustup_mirror=${ULL_RUSTUP_MIRROR:-https://mirrors.tuna.tsinghua.edu.cn/rustup}
|
||
|
||
mkdir -p "$out_dir"
|
||
out_dir=$(cd "$out_dir" && pwd)
|
||
echo "== 仓库: $repo_root"
|
||
echo "== 输出: $out_dir"
|
||
|
||
# 1) 构建镜像:rust 工具链 + xvfb + 图形栈编译依赖 + 字体(软渲染器要从系统字体库取字形)
|
||
if ! docker image inspect "$image" >/dev/null 2>&1 || [ "${ULL_SMOKE_REBUILD_IMAGE:-0}" = "1" ]; then
|
||
context=$(mktemp -d)
|
||
trap 'rm -rf "$context"' EXIT
|
||
cat >"$context/Dockerfile" <<EOF
|
||
FROM rust:1-bookworm
|
||
# 工具链组件(rustfmt/clippy)在 rust-toolchain.toml 里是必备项,rustup 会去补齐;
|
||
# 默认 dist 服务器在国内会挂成「rustc 0.6% CPU 睡 12 分钟」的假死(M62 实测),所以走 TUNA。
|
||
ENV RUSTUP_DIST_SERVER=$rustup_mirror \\
|
||
RUSTUP_UPDATE_ROOT=$rustup_mirror/rustup
|
||
RUN sed -i 's|http://deb.debian.org|$apt_mirror|g' /etc/apt/sources.list.d/debian.sources \\
|
||
&& apt-get update \\
|
||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \\
|
||
xvfb xauth python3 \\
|
||
libxkbcommon-dev libxkbcommon-x11-0 libfontconfig1-dev \\
|
||
libgl1-mesa-dev libegl1-mesa-dev libwayland-dev libxcb1-dev \\
|
||
libx11-6 libxcursor1 libxrandr2 libxi6 \\
|
||
fonts-dejavu-core fonts-liberation2 \\
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
RUN rustup component add rustfmt clippy && rustup toolchain list
|
||
WORKDIR /src
|
||
EOF
|
||
docker build -t "$image" "$context"
|
||
fi
|
||
|
||
# 2) 跑容器:构建 → xvfb 截图 → 像素判定
|
||
# 注意缓存卷要挂到 `registry` 子目录:直接挂 /usr/local/cargo 会把镜像里的 `bin/`(cargo 本体)盖掉,
|
||
# 变成 `cargo: command not found`(M62 实测踩到)。
|
||
MSYS_NO_PATHCONV=1 docker run --rm \
|
||
-v "$repo_root:/src" \
|
||
-v "$cargo_volume:/usr/local/cargo/registry" \
|
||
-v "$target_volume:/target" \
|
||
-v "$out_dir:/out" \
|
||
-w /src \
|
||
-e CARGO_TARGET_DIR=/target \
|
||
-e ULL_SMOKE_SKIP_BUILD="${ULL_SMOKE_SKIP_BUILD:-0}" \
|
||
-e CARGO_TERM_COLOR=never \
|
||
"$image" bash -euo pipefail -c '
|
||
rustc --version && cargo --version && cat /etc/debian_version
|
||
if [ "$ULL_SMOKE_SKIP_BUILD" != "1" ]; then
|
||
# 共享热缓存卷可能喂陈旧产物(踩坑记录 N7):先清掉受检 crate 再编
|
||
cargo clean -p unlucky-app || true
|
||
cargo build --bin unlucky-launcher --locked -j 6
|
||
fi
|
||
mkdir -p /out/data
|
||
set +e
|
||
xvfb-run -a --server-args="-screen 0 1280x1024x24" \
|
||
/target/debug/unlucky-launcher --software-renderer \
|
||
--screenshot /out/runtime-smoke.png --data-dir /out/data
|
||
code=$?
|
||
set -e
|
||
echo "exit=$code"
|
||
python3 packaging/check_screenshot.py /out/runtime-smoke.png
|
||
exit $code
|
||
'
|
||
|
||
echo "== 截图与数据目录已留在 $out_dir"
|