Files
Unlucky-Launcher/.github/workflows/ci.yml
T
AskaEth ea5f66cf92 feat(ci): M62 —— Linux runtime-smoke(xvfb 真渲染一帧)+ 像素非空白门禁
「编得过」不等于「跑得起来」: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 证明「能出图」,不证明视觉正确。
2026-11-01 16:15:47 +08:00

172 lines
7.0 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: CI
# 主干门禁:fmt / clippy / test / build 双平台矩阵。
# 架构守门(core 零 UI、文件 ≤400 行、依赖许可)见 guards.yml。
# 平台基线 Win10+(标准工具链产物),平台矩阵见 AGENTS.md §7。
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
fmt:
name: fmt (rustfmt --check)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all --check
clippy:
name: clippy (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Linux 图形栈构建依赖(winit / femtovg 编译期需要)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libxkbcommon-dev libfontconfig1-dev libgl1-mesa-dev libegl1-mesa-dev \
libwayland-dev libxcb1-dev
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --workspace --all-targets -- -D warnings
test:
name: test (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Linux 图形栈构建依赖
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libxkbcommon-dev libfontconfig1-dev libgl1-mesa-dev libegl1-mesa-dev \
libwayland-dev libxcb1-dev
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace --all-targets
build:
name: build release (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Linux 图形栈构建依赖
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libxkbcommon-dev libfontconfig1-dev libgl1-mesa-dev libegl1-mesa-dev \
libwayland-dev libxcb1-dev
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo build --workspace --release
# 运行时冒烟(M62):Linux 上**真渲染一帧**,补上「能编译 ≠ 能跑」的空白。
# 上面四个 job 都只证明代码编得过、测得过,从不创建真实图形窗口;本 job 在 Xvfb 虚拟显示里
# 启动 GUI 进程,让 Slint 软件渲染器把当前场景渲进内存像素缓冲并编码成 PNG,再对**像素本身**
# 断言(不是只看退出码:窗口起来了但一帧空白同样是「退出码 0」)。
# 诚实边界:证明「Linux 上真能跑出非空白的一帧」,**不**证明视觉正确——视觉靠人眼看 artifact。
runtime-smoke:
name: runtime smoke (ubuntu x64, xvfb)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: 无头 X 与图形栈依赖(xvfb + 软渲染 + 字体)
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
xvfb xauth \
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
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# 用 debug 而不是 release:本 job 验的是「运行时渲染通路能通」,不是优化后二进制的行为;
# release 的 lto="fat" + codegen-units=1 会把这一步拖到十几分钟却不带来额外证据。
- run: cargo build --bin unlucky-launcher --locked
- name: 在 Xvfb 里跑一帧并截图(退出码非 0 即失败)
run: |
set -o pipefail
mkdir -p "$RUNNER_TEMP/smoke/data"
xvfb-run -a --server-args="-screen 0 1280x1024x24" \
target/debug/unlucky-launcher --software-renderer \
--screenshot "$RUNNER_TEMP/smoke/runtime-smoke.png" \
--data-dir "$RUNNER_TEMP/smoke/data"
- name: 断言 PNG 存在且非空白(尺寸 / 唯一色数 / 亮度σ / 强边缘占比)
run: python3 packaging/check_screenshot.py "$RUNNER_TEMP/smoke/runtime-smoke.png" --json
- name: 上传截图与数据目录(失败也传,便于复盘)
if: always()
uses: actions/upload-artifact@v4
with:
name: runtime-smoke-screenshot
path: ${{ runner.temp }}/smoke/
if-no-files-found: warn
# macOS 槽位探针(M62):**默认不在 PR 上跑**,只有手动 dispatch 才触发。
# 之所以是探针而不是常驻槽位:官方 macOS runner 处于 LaunchDaemon 上下文,没有登录的
# WindowServer 会话,Slint 的 winit backend 创建窗口大概率直接失败;而 worker 禁 push,
# 本机也没有 macOS,这条路径**尚无任何实测证据**——不拿未验证的 job 卡住主干门禁。
# 首跑结论决定去留:绿 → 去掉 if/continue-on-error 升为常驻槽位;红 → 按 D26 归入 iced
# 重写后再规划,并把错误原文贴进 progress 文档(不静默删 job、不吞失败原因)。
runtime-smoke-macos:
name: runtime smoke (macos arm64, probe)
if: github.event_name == 'workflow_dispatch'
runs-on: macos-latest
timeout-minutes: 30
# 探针阶段保留证据但不算流水线结论:log/artifact 里失败原因照常可见,
# 解除条件 = 该槽位在真实 runner 上首跑通过并把本行与上面那条 if 一起删掉。
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo build --bin unlucky-launcher --locked
- name: 尝试截图(macOS 没有 X 服务器,直接跑;失败即为结论)
run: |
set -o pipefail
mkdir -p "$RUNNER_TEMP/smoke/data"
target/debug/unlucky-launcher --software-renderer \
--screenshot "$RUNNER_TEMP/smoke/runtime-smoke.png" \
--data-dir "$RUNNER_TEMP/smoke/data"
- name: 断言 PNG 存在且非空白
run: python3 packaging/check_screenshot.py "$RUNNER_TEMP/smoke/runtime-smoke.png" --json
- name: 上传截图与数据目录(失败也传,便于复盘)
if: always()
uses: actions/upload-artifact@v4
with:
name: runtime-smoke-screenshot-macos
path: ${{ runner.temp }}/smoke/
if-no-files-found: warn