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