Files
Unlucky-Launcher/.github/workflows/guards.yml
T
AskaEth 56bc52756b ci(guards): 新增 presenter 零 UI 与 design 零第三方依赖两道门禁(M89)
- presenter-no-ui:照 core-no-ui 的「依赖表 + cargo tree」双查,名单一致
  (slint/egui/eframe/iced/winit/gtk/javafx/tauri/fltk/druid/relm)。
  依赖表检查按表头切出依赖表再查键名——presenter 的 description 合法地写着
  「Slint 版与 iced 版共用」,core-no-ui 那种整文件 grep 在这里会误报。
- design-zero-dep:cargo tree -p unlucky-design --edges normal 只允许出现自身一行,
  把 README 明写的「零第三方依赖」卖点从人肉守变成 CI 守。
- 两条都 fail-closed:cargo tree 自身失败(crate 改名/解析异常)同样红;
  失败信息给出命中清单与放哪里的建议。
- 未改任何已有 job 的语义(core-no-ui / file-size / licenses 原样)。
2026-11-02 11:19:14 +08:00

163 lines
8.3 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: Guards
# 架构与合规守门(与 fmt/clippy 同级,不允许 continue-on-error):
# 1. core-no-ui —— crates/core 零 UI 依赖(D3 铁律),依赖表 + cargo tree 双查
# 2. presenter-no-ui —— crates/presenter 零 UI 依赖(D26 / M85):Slint 版与 iced 版共用的宿主层
# 3. design-zero-dep —— crates/design 零第三方依赖(M61 / M87):依赖树只允许出现自身
# 4. file-size —— 全仓代码/文档单文件 ≤400 行(用户硬约束)
# 5. licenses —— cargo-deny:许可证白名单 + GPL 依赖拦截 + 供应链来源校验
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
core-no-ui:
name: core 零 UI 守门
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: 依赖表直查(crates/core/Cargo.toml 不得出现 UI 框架)
run: |
set -uo pipefail
forbidden='slint|egui|eframe|iced|winit|gtk|javafx|tauri|fltk|druid|relm'
# 只看非注释行:manifest 里说明「不许出现 slint」的注释本身不该触发守门
if grep -vE '^[[:space:]]*#' crates/core/Cargo.toml | grep -nEi "$forbidden"; then
echo "::error file=crates/core/Cargo.toml::core 不得依赖 UI 框架(见 AGENTS.md 硬约束)"
exit 1
fi
echo "OK: crates/core/Cargo.toml 无 UI 依赖"
- name: 依赖树泄漏检查(cargo tree -p unlucky-core,排除自身行)
run: |
set -uo pipefail
forbidden='slint|egui|eframe|iced|winit|gtk|javafx|tauri|fltk|druid|relm'
tree=$(cargo tree -p unlucky-core --edges normal --prefix none)
echo "$tree"
if echo "$tree" | grep -vE '^unlucky-core v' | grep -qiE "$forbidden"; then
echo "::error::core 的依赖树出现 UI 框架 —— 传递依赖也不允许"
exit 1
fi
echo "OK: unlucky-core 依赖树零 UI"
# presenter 是 Slint 版(crates/app)与 iced 版(crates/app-iced)共用的唯一业务层(D26)。
# 一旦 slint/iced/winit 被拉进来,分层就废了,而编译与单测仍会全绿 —— 所以必须由 CI 守。
# 与 core-no-ui 同样是「依赖表 + cargo tree」双查,名单保持一致;脚本刻意复制而非抽公共脚本,
# 理由是 CI 守门脚本自身挂掉的排查成本远高于这几行重复。
presenter-no-ui:
name: presenter 零 UI 守门
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: 依赖表直查(crates/presenter/Cargo.toml 的依赖表不得出现 UI 框架)
run: |
set -uo pipefail
forbidden='slint|egui|eframe|iced|winit|gtk|javafx|tauri|fltk|druid|relm'
# 与 core-no-ui 的整文件 grep 不同:本 crate 的 description 合法地写着
# 「Slint 版与 iced 版共用」,整文件 grep 会把它当越线。所以先按表头切出依赖表
# (含 target.*.dependencies 与 dev/build 表),再去掉注释行,只查键名。
awk '
/^[[:space:]]*\[/ {
in_deps = ($0 ~ /^[[:space:]]*\[(target\..*\.)?(dependencies|dev-dependencies|build-dependencies)\][[:space:]]*$/)
next
}
in_deps && !/^[[:space:]]*#/ && !/^[[:space:]]*$/ { printf "%d:%s\n", NR, $0 }
' crates/presenter/Cargo.toml > /tmp/presenter-deps.txt
cat /tmp/presenter-deps.txt
if grep -iE "$forbidden" /tmp/presenter-deps.txt; then
echo "::error file=crates/presenter/Cargo.toml::presenter 不得依赖 UI 框架(D26)。窗口创建 / 渲染 / 图片解码属于 app 与 app-iced 侧;需要跨前端共用的类型请放进 presenter 的 dto。"
exit 1
fi
echo "OK: crates/presenter/Cargo.toml 依赖表无 UI 依赖"
- name: 依赖树泄漏检查(cargo tree -p unlucky-presenter,排除自身行)
run: |
set -uo pipefail
forbidden='slint|egui|eframe|iced|winit|gtk|javafx|tauri|fltk|druid|relm'
# 注意:cargo tree 命中 UI 框架时**退出码仍是 0**,所以只能抓输出再判断。
if ! tree=$(cargo tree -p unlucky-presenter --edges normal --prefix none); then
echo "::error::cargo tree -p unlucky-presenter 执行失败 —— crate 是否仍叫 unlucky-presenter?"
exit 1
fi
echo "$tree"
hits=$(printf '%s\n' "$tree" | grep -vE '^unlucky-presenter v' | grep -iE "$forbidden")
if [ -n "$hits" ]; then
echo "::error::presenter 的依赖树出现 UI 框架(传递依赖也不允许),本次命中:"
printf '%s\n' "$hits" | sed 's/^/ - /'
echo "::error::presenter 是 Slint 版与 iced 版共用的宿主层:把窗口 / 渲染 / 图片解码留在 app 侧,presenter 只持有与框架无关的 DTO、状态 reducer 与 host 装配。"
exit 1
fi
echo "OK: unlucky-presenter 依赖树零 UI"
# unlucky-design 是「纯数据 + 纯函数、零第三方依赖」的设计令牌层,这是 README 明写的卖点
# (连 serde 都不加),但目前只靠人肉守。零依赖的判据比 UI 名单更硬:依赖树只允许有自己一行。
design-zero-dep:
name: design 零第三方依赖守门
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: 依赖树只允许出现自身(cargo tree -p unlucky-design --edges normal)
run: |
set -uo pipefail
if ! tree=$(cargo tree -p unlucky-design --edges normal --prefix none); then
echo "::error::cargo tree -p unlucky-design 执行失败 —— crate 是否仍叫 unlucky-design?"
exit 1
fi
echo "$tree"
# 空行不算,剩下的每一行都必须是自己:正常时整个输出只有一行
# `unlucky-design v0.1.0 (/path/to/crates/design)`。
lines=$(printf '%s\n' "$tree" | grep -cE '[^[:space:]]' || true)
others=$(printf '%s\n' "$tree" | grep -vE '^[[:space:]]*$' | grep -vE '^unlucky-design v' || true)
if [ -n "$others" ] || [ "$lines" -ne 1 ]; then
echo "::error file=crates/design/Cargo.toml::unlucky-design 必须零第三方依赖,本应只有自身一行,实际 $lines 行;越线的包:"
printf '%s\n' "$others" | sed 's/^/ - /'
echo "::error::design 是纯数据 + 纯函数的设计令牌:色彩 / 排版 / 形状 / 高程 / 动效请不要引入 serde、rand、once_cell 等任何 crate。序列化与运行时能力放到消费方(app / app-iced / presenter)。"
exit 1
fi
echo "OK: unlucky-design 依赖树只有自身,零第三方依赖"
file-size:
name: 文件行数 ≤400 守门
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 检查单文件行数
run: |
set -uo pipefail
LIMIT=400
failed=0
while IFS= read -r f; do
case "$f" in
LICENSE|*/LICENSE|LICENSE-*|*/LICENSE-*) continue ;;
CHANGELOG*|*/CHANGELOG*) continue ;;
esac
n=$(wc -l < "$f")
if [ "$n" -gt "$LIMIT" ]; then
echo "::error file=$f::$f 有 $n 行,超过 $LIMIT 行上限,请按模块拆分"
failed=$((failed + 1))
fi
# 覆盖范围含脚本(*.py/*.sh):packaging/ 下的构建辅助脚本同样受 400 行约束;
# Cargo.lock 等机器生成文件刻意不在列(否则一生成就超限)
done < <(git ls-files '*.rs' '*.md' '*.slint' '*.yml' '*.yaml' '*.toml' '*.json' '*.py' '*.sh')
if [ "$failed" -ne 0 ]; then
echo "共 $failed 个文件超限"
exit 1
fi
echo "OK: 全部受检文件 ≤$LIMIT 行"
licenses:
name: 依赖许可与来源(cargo-deny)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
with:
# 只跑许可/禁用/来源三类,advisories 由后续安全轮次单独接管
command: check licenses bans sources