From e90486524d42839f3609e272fa1409d23bbf68d1 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Thu, 1 Oct 2026 10:19:38 +0800 Subject: [PATCH] =?UTF-8?q?CI=EF=BC=9AGitea=20Actions=20=E8=90=BD=E5=9C=B0?= =?UTF-8?q?=20L0+L0.5=EF=BC=88=E8=A1=A5=20T-6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 .gitea/workflows/ci.yml:push/PR 触发两个 Windows job—— l0-gates 跑 pre-commit --all-files(无 Keil .map 时自适应跳过 mem-numbers), l05-host-unit-tests 遍历 test/host/**/run_unit_test.py(缺 MSVC 则告警降级跳过)。 刻意不跑 @build(需 Keil 授权);L1/L2/L3 需真机+ST-LINK,不上 CI。 新增 scripts/ci_local.py:本地跑与 CI 等价的门禁(自动选用装了 pre-commit 的解释器;.map 自适应;退出码 0/1/2 与 CI 对齐),供提交前自查与 runner 就绪前过渡。 文档同步:测试开发标准流程 §9(CI 章节)/§1/§4.1、待处理内容 T-6 销项、 项目部署环境要求 §2.11(runner 前置)、pre-commit 使用说明 §1、scripts/README.md、AGENTS.md。 验证:scripts/ci_local.py rc=0(L0 14 钩子全过 + L0.5 39/39)。 --- .gitea/workflows/ci.yml | 186 +++++++++++++++++++++++++ AGENTS.md | 1 + docs/代码规范/pre-commit使用说明.md | 4 + docs/待处理内容.md | 11 +- docs/测试说明/测试开发标准流程.md | 67 +++++++++ docs/环境与部署/项目部署环境要求.md | 21 +++ scripts/README.md | 11 ++ scripts/ci_local.py | 207 ++++++++++++++++++++++++++++ 8 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/ci.yml create mode 100644 scripts/ci_local.py diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 00000000..5c69c2f0 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,186 @@ +# Gitea Actions CI —— STM32F4-Base 持续集成 +# +# 覆盖范围(**只做不依赖硬件的层**,见 docs/测试说明/测试开发标准流程.md §1/§9): +# L0 静态门禁 = pre-commit 全部钩子(等价本地 `pre-commit run --all-files`) +# L0.5 主机侧纯逻辑单测 = test/host/**(MSVC 离线编译,零硬件) +# +# ⛔ L1 板端套件(test/board/,需真机 + ST-LINK)/ L2 @test 闭环 / L3 人工目检 +# **不上 CI** —— 需要开发板与烧录器,只能在本地跑。 +# +# 明确不做的两件事(都是刻意为之): +# 1. `@build`(Keil UV4)—— 需本机 Keil 授权,runner 上不具备;且 L0 的 cppcheck + +# 代码规范已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(AGENTS.md)。 +# 2. `mem-numbers` 钩子 —— 它的事实源是 `MDK-ARM/**/*.map`(Keil 构建产物、已 gitignore), +# 无 `@build` 即无 `.map`。故本 workflow 在**检测不到 .map 时自动跳过该钩子** +# (见下方 "决定跳过哪些钩子" 步骤),有 .map 的 runner(如自建带 Keil 的机器)则照常跑。 +# +# 触发:push(除纯文档路径外)与 pull_request。 +# +# ⚠️ 前置(部署到 Gitea 时需确认): +# - 该 Gitea 实例已启用 Actions:`[actions] ENABLED = true`(site admin 配置) +# - 已注册一个 **Windows** runner(labels 含 `windows`),且装有: +# Python 3.x、cppcheck、pre-commit、MSVC(VS2019/2022 的 C++ 工具链 + Windows SDK) +# - 缺 MSVC 时 L0.5 步骤会**降级为提示并跳过**(不硬失败),但会打印醒目 WARNING +# +# 本地等价命令(与 CI 同一套判定,见 scripts/ci_local.py): +# & '.venv\Scripts\python.exe' scripts/ci_local.py + +name: CI + +on: + push: + branches: ['**'] + paths-ignore: + - 'docs/**' + - '**/*.md' + - '.workbuddy-ai/**' + pull_request: + paths-ignore: + - 'docs/**' + - '**/*.md' + - '.workbuddy-ai/**' + workflow_dispatch: + +# 同一分支的旧运行自动取消,省 runner +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + # ------------------------------------------------------------------ + # L0:静态门禁(pre-commit 钩子全集) + # ------------------------------------------------------------------ + l0-gates: + name: L0 静态门禁 (pre-commit) + runs-on: windows + steps: + - uses: actions/checkout@v4 + with: + # 钩子要对比基线与历史(doc-links 等读工作区即可,但 gitlint/cubemx 需完整历史) + fetch-depth: 0 + + - name: 环境自检(Python / cppcheck / pre-commit) + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $missing = @() + foreach ($item in @( + @{ name = 'Python'; cmd = 'python' }, + @{ name = 'cppcheck'; cmd = 'cppcheck' } + )) { + if (-not (Get-Command $item.cmd -ErrorAction SilentlyContinue)) { + $missing += $item.name + } + } + if ($missing.Count -gt 0) { + Write-Host "::error::runner 缺少:$($missing -join ', ') —— 请安装后重跑" + exit 1 + } + python --version + cppcheck --version + + # pre-commit:优先用 `python -m pre_commit`(不依赖 PATH 里的 console script) + python -m pre_commit --version + if ($LASTEXITCODE -ne 0) { + Write-Host "::error::runner 缺少 pre-commit(pip install pre-commit / uv tool install pre-commit)" + exit 1 + } + Write-Host "runner 环境自检通过" + + - name: 安装 pre-commit 钩子到本仓库 + shell: pwsh + run: python -m pre_commit install --install-hooks + + - name: 决定跳过哪些钩子(Keil 依赖) + id: skip + shell: pwsh + run: | + # mem-numbers 的事实源是 Keil 构建产物 .map(已 gitignore)。 + # 无 .map => 本 runner 不可能跑 L0 的该钩子 => 跳过(不是"通过",是"不适用")。 + $map = Get-ChildItem -Path 'MDK-ARM' -Recurse -Filter '*.map' -ErrorAction SilentlyContinue + if ($map) { + Write-Host "找到 .map:$($map[0].FullName) —— mem-numbers 照常运行" + "value=" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } else { + Write-Host "::notice::未找到 .map(未做 Keil 构建)—— 跳过 mem-numbers 钩子" + "value=mem-numbers" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } + + - name: 运行 L0 门禁(--all-files) + shell: pwsh + env: + SKIP: ${{ steps.skip.outputs.value }} + run: | + if ($env:SKIP) { Write-Host "SKIP=$env:SKIP" } + python -m pre_commit run --all-files --show-diff-on-failure + if ($LASTEXITCODE -ne 0) { + Write-Host "::error::L0 静态门禁未通过(详见上方输出)" + exit $LASTEXITCODE + } + + # ------------------------------------------------------------------ + # L0.5:主机侧纯逻辑单测(零硬件;缺 MSVC 则跳过) + # ------------------------------------------------------------------ + l05-host-unit-tests: + name: L0.5 主机侧单测 (MSVC) + runs-on: windows + steps: + - uses: actions/checkout@v4 + + - name: 探测 MSVC(VS BuildTools / Community 等) + id: msvc + shell: pwsh + run: | + # 与 test/host/common/unit_test_harness.py 的 VS_ROOTS 保持一致 + $roots = @( + 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools', + 'C:\Program Files\Microsoft Visual Studio\2022\BuildTools', + 'C:\Program Files\Microsoft Visual Studio\2022\Community', + 'C:\Program Files\Microsoft Visual Studio\2022\Professional' + ) + $found = $false + foreach ($r in $roots) { + if (Test-Path $r) { + $cl = Get-ChildItem -Path $r -Recurse -Filter 'cl.exe' -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like '*Hostx64\x64*' } | Select-Object -First 1 + if ($cl) { $found = $true; Write-Host "找到 cl.exe: $($cl.FullName)"; break } + } + } + if ($found) { + "value=yes" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } else { + Write-Host "::warning::runner 未安装 MSVC(VS C++ 工具链)—— 跳过 L0.5 单测。请安装 VS BuildTools 后重跑。" + "value=no" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + } + + - name: 运行主机侧单测 + if: steps.msvc.outputs.value == 'yes' + shell: pwsh + run: | + # 遍历 test/host/*/run_unit_test.py(各模块自带运行器,统一走 common harness) + $runners = Get-ChildItem -Path 'test/host' -Recurse -Filter 'run_unit_test.py' | + Sort-Object FullName + if (-not $runners) { + Write-Host "::warning::未找到 test/host/**/run_unit_test.py —— 跳过" + exit 0 + } + $fail = 0 + foreach ($r in $runners) { + Write-Host "================================" + Write-Host "运行:$($r.FullName)" + Write-Host "================================" + python $r.FullName + if ($LASTEXITCODE -ne 0) { + Write-Host "::error::单测失败:$($r.FullName)(退出码 $LASTEXITCODE)" + $fail = 1 + } + } + exit $fail + + - name: L0.5 跳过说明 + if: steps.msvc.outputs.value == 'no' + shell: pwsh + run: | + Write-Host "L0.5 未执行:runner 缺 MSVC。" + Write-Host "本地等价命令:& '.venv\Scripts\python.exe' scripts/ci_local.py" + exit 0 diff --git a/AGENTS.md b/AGENTS.md index d5cad583..d5634a94 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v - 提交信息:**单行中文标题 <100 字符**(gitlint),UTF-8 无 BOM + `git commit -F <文件>`(防终端转码破坏) - 代码规范 V1.0:小写下划线命名(`g_`/`s_`/`p_`/`a_` 前缀)、头文件保护宏 `XXX_H`、常量在右、文件头模块注释块 → `docs/代码规范/` - 全库扫描 `pre-commit run --all-files`;详见 `docs/代码规范/pre-commit使用说明.md` +- **CI(Gitea Actions,2026-10-01 T-6)**:`.gitea/workflows/ci.yml` 在 push/PR 跑 **L0 静态门禁 + L0.5 主机侧单测**(Windows runner);**不含 L1/L2/L3**(需真机+ST-LINK)、**刻意不跑 `@build`**(需 Keil 授权)、无 `.map` 时自适应跳过 `mem-numbers`。本地等价自查:`& '.venv\Scripts\python.exe' scripts/ci_local.py`(**runner 未就绪时这是 L0 的唯一保障**,不能因"已配 CI"放宽本地门禁)。详见 `docs/测试说明/测试开发标准流程.md` §9 ## 平台约束 diff --git a/docs/代码规范/pre-commit使用说明.md b/docs/代码规范/pre-commit使用说明.md index f4c577d5..d21c23f1 100644 --- a/docs/代码规范/pre-commit使用说明.md +++ b/docs/代码规范/pre-commit使用说明.md @@ -30,6 +30,10 @@ git commit **两阶段钩子**:`pre-commit` 阶段拦截代码问题,`commit-msg` 阶段拦截格式问题。任一失败 → commit 中止。 +> **CI 侧的同一套门禁(2026-10-01 T-6)**:Gitea Actions(`.gitea/workflows/ci.yml`)在 push / PR 时 +> 以 `pre-commit run --all-files` 复跑**全部上述钩子**(`mem-numbers` 在无 Keil `.map` 的 runner 上自适应跳过)。 +> 本地提交前想跑同一套判定:`& '.venv\Scripts\python.exe' scripts/ci_local.py`。详见 `../测试说明/测试开发标准流程.md` §9。 + ### 依赖清单 | 依赖 组件 | 安装方式 | 用途 | diff --git a/docs/待处理内容.md b/docs/待处理内容.md index 3ad1688d..fdedf79d 100644 --- a/docs/待处理内容.md +++ b/docs/待处理内容.md @@ -53,7 +53,16 @@ > 看门狗运行器 170 行 → 55 行声明式。 > - **T-4(P2)**:`test/board/test_common.h` 集中断言内核,4 套件绑定自身统计变量。 > - **T-5(P2)**:`test/run_all_suites.py` 多套件编排(`try/finally` 无条件还原开关)。 +> - **T-6(P3)**:**Gitea Actions CI 落地(补办,2026-10-01)**。远程为 Gitea 1.25.4(`gitea.bitnasdaq.vip/wandering1/STM32F4-Base.git`),已启用 Actions → +> 新增 `.gitea/workflows/ci.yml`:**两个 Windows job**,`l0-gates`(`pre-commit run --all-files`,检测不到 Keil `.map` 时自适应跳过 `mem-numbers`)+ +> `l05-host-unit-tests`(遍历 `test/host/**/run_unit_test.py`,缺 MSVC 则打 WARNING 降级跳过)。**刻意不跑 `@build`**(需 Keil 授权)。 +> 另新增 **`scripts/ci_local.py`**(本地跑与 CI 等价的门禁,自动选用装了 pre-commit 的解释器;退出码 0/1/2 与 CI 对齐)。 +> 登记依据见 [`测试说明/测试开发标准流程.md`](测试说明/测试开发标准流程.md) §9;覆盖边界与"刻意不含项"见该节与 `../.gitea/workflows/ci.yml` 文件头。 + +> ⚠️ **部署前置(未完成,需人工确认)**:CI 仅在 runner 就绪后才真正生效—— +> ① Gitea 实例 `[actions] ENABLED = true`;② 注册一个 **Windows runner(labels 含 `windows`)**,装 Python 3.x / cppcheck / pre-commit / MSVC(VS C++ 工具链 + Windows SDK)。 +> runner 未就绪前,**提交前仍须在本地跑 `scripts/ci_local.py` 或 `pre-commit run --all-files` 自查**(这是 L0 的唯一保障,不能因"已配 CI"而放宽)。 | # | 事项 | 优先级 | 问题与证据 | 处置方向 | 出处 | |---|---|---|---|---|---| -| T-6 | **无 CI——L0/L0.5 已具备上 CI 条件** | P3 | L0(pre-commit 门禁,纯 Python + cppcheck)与 L0.5(MSVC 单测,纯标准库运行器)**均不依赖硬件**,可直接在 CI runner 跑;L1/L2 需真机与 ST-LINK,只能本地。当前全靠开发者本地 `pre-commit install`,新机器漏装即裸奔 | 待有 CI 平台时:挂 L0(`pre-commit run --all-files`)+ L0.5;L1/L2 保留本地。前置:CI runner 需装 cppcheck + VS BuildTools(或自建带工具链镜像) | 2026-10-01 评审;`测试说明/测试开发标准流程.md` §1 | +| (暂无待办) | — | — | — | — | — | diff --git a/docs/测试说明/测试开发标准流程.md b/docs/测试说明/测试开发标准流程.md index a3116f72..6e47d256 100644 --- a/docs/测试说明/测试开发标准流程.md +++ b/docs/测试说明/测试开发标准流程.md @@ -34,6 +34,9 @@ > (无窗口、无硬件、秒级),专测"判定逻辑"。目前仅看门狗模块使用,见 > [`看门狗单元测试说明.md`](看门狗单元测试说明.md)。适格对象:算法 / 状态机 / 协议编解码 / > 参数校验等**无副作用**的模块。 +> +> **CI**:**L0 + L0.5 已上 CI**(Gitea Actions,`.gitea/workflows/ci.yml`,2026-10-01 T-6); +> L1/L2/L3 需硬件,只在本地。本地等价命令 `scripts/ci_local.py`。详见 §9。 ### 1.1 门禁决策表(改动类型 → 必跑门禁) @@ -235,6 +238,7 @@ static void phaseN_run(void) { | `test/host/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL)+ 可选套件汇总行校验(T-2) | | **`test/host/common/unit_test_harness.py`** | `test/host/common/` | **L0.5 通用单测 harness**:工具链探测 / `INCLUDE`-`LIB` 拼接 / 编译运行 / 退出码契约。各模块运行器只做声明式配置(T-3) | | **`test/host/watchdog/run_unit_test.py`** | `test/host/watchdog/` | **L0.5 主机侧单测**:声明 `TestBuild` 后调 harness,穷举看门狗判定状态机(零硬件、秒级)。**需本机装有 VS(`cl.exe`)**,见 §4.2 环境要求 | +| **`scripts/ci_local.py`** | `scripts/` | **本地跑与 CI 等价的门禁**(L0 + L0.5):自动选用装了 pre-commit 的解释器;`.map` 自适应跳过 `mem-numbers`;退出码 0/1/2 与 CI 对齐。见 §9.3 | | `.opencode/serial_config.json` | 仓库 | **串口端口单一配置源**(当前 COM6 / 115200);换端口只改这个文件 | | `test/host/ftp_*.py`(4 个)/ `ch395f_socket_test.py` / `time_sync_server.py` | `test/host/` | PC 端交互测试工具(FTP 客户端 / socket 回显 / 对时推送) | | `simulator/tests/*.py`(4 个) | `simulator/tests/` | LVGL 模拟器回归脚本(见 §5) | @@ -432,6 +436,68 @@ flowchart LR --- +## 9. CI 持续集成(Gitea Actions,2026-10-01 加 T-6) + +远程仓库为 **Gitea 1.25.4**(`gitea.bitnasdaq.vip/wandering1/STM32F4-Base.git`),支持 Gitea Actions +(兼容 GitHub Actions 语法,workflow 置于 `.gitea/workflows/`)。落地文件 **`.gitea/workflows/ci.yml`**。 + +### 9.1 覆盖范围:只做"不依赖硬件"的层 + +| 层 | 上 CI? | 载体 | 理由 | +|---|---|---|---| +| **L0** 静态门禁 | ✅ | `pre-commit run --all-files`(全部钩子) | 纯 Python + cppcheck,零硬件 | +| **L0.5** 主机侧纯逻辑单测 | ✅ | `test/host/**/run_unit_test.py`(MSVC 离线编译) | 纯标准库 + `cl.exe`,零硬件 | +| L1 板端套件 | ⛔ | `test/board/` | 需真机 + 串口 + ST-LINK | +| L2 `@test` 闭环 | ⛔ | 烧录 + 串口采集 | 同上 | +| L3 人工目检 | ⛔ | 上机屏幕 | 人的判断 | + +**明确不做的两件事(都是刻意为之)**: + +1. **不跑 `@build`(Keil UV4)**——需本机 Keil 授权,runner 上不具备。L0 的 cppcheck + 代码规范 + 已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(`AGENTS.md`)。 +2. **强制跑 `mem-numbers` 钩子**——它的事实源是 `MDK-ARM/**/*.map`(Keil 构建产物,已 `.gitignore`), + 无 `@build` 即无 `.map`。故 workflow 在**检测不到 `.map` 时自动跳过该钩子** + (`SKIP=mem-numbers`,钩子显示 `Skipped` 而非 `Passed`——是"不适用",不是"通过"); + 有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。 + +### 9.2 两个 job + +| job | `runs-on` | 步骤要点 | +|---|---|---| +| `l0-gates` | `windows` | checkout(`fetch-depth: 0`,gitlint/cubemx 需完整历史)→ 环境自检(Python / cppcheck / `python -m pre_commit`,缺则 `::error::` + exit 1)→ `pre_commit install --install-hooks` → **决定跳过哪些钩子**(探测 `.map`,写入 `GITHUB_OUTPUT`)→ `pre-commit run --all-files --show-diff-on-failure`(`env: SKIP`) | +| `l05-host-unit-tests` | `windows` | checkout → **探测 MSVC**(按 VS_ROOTS 找 `Hostx64\x64\cl.exe`)→ 遍历 `test/host/**/run_unit_test.py` 逐个跑(失败 `::error::` 并置退出码);**未探测到 MSVC 时打印醒目 WARNING 并降级跳过**(不硬失败,避免"没装 VS 的 runner 全红") | + +触发:`push`(`paths-ignore: docs/** / **/*.md / .workbuddy-ai/**`)与 `pull_request`,另留 `workflow_dispatch` 手动触发。 +`concurrency: group: ci-${{ github.ref }}` + `cancel-in-progress: true`(同分支旧运行自动取消,省 runner)。 + +### 9.3 本地等价:`scripts/ci_local.py` + +CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runner 还没配好时**,用同一套判定在本地跑: + +```powershell +& '.venv\Scripts\python.exe' scripts/ci_local.py # L0 + L0.5(与 CI 等价) +& '.venv\Scripts\python.exe' scripts/ci_local.py --l0-only # 只跑 L0 +& '.venv\Scripts\python.exe' scripts/ci_local.py --l05-only # 只跑 L0.5 +``` + +- **自动选用装了 pre-commit 的解释器**(本机 `pre-commit` 只装在 anaconda3,`.venv` 没有); + 仍不可用时退出码 2 并给出修复提示。 +- **`.map` 自适应与 CI 完全一致**:本地有 `.map` 就照常跑 `mem-numbers`,没有就跳过。 +- 退出码:`0` 通过 / `1` 门禁或单测失败 / `2` 环境错误。 + +> ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`@build`、L1/L2 台架项都只存在于本地。 +> 提交前跑一遍 `ci_local.py`(或 `pre-commit run --all-files`)仍是**强制**动作,见 §7.1。 + +### 9.4 部署前置(需人工确认) + +| 项 | 要求 | +|---|---| +| Gitea Actions | 实例开启 `[actions] ENABLED = true`(site admin 配置) | +| Windows runner | 已注册且 **labels 含 `windows`**;装有 Python 3.x、cppcheck、pre-commit、MSVC(VS2019/2022 C++ 工具链 + Windows SDK) | +| 缺 MSVC 时 | L0.5 降级为 WARNING 跳过(不硬失败),但**等于该层裸奔**,须尽快补装 | + +--- + ## 附录 A:test/ 文件清单(现状) | 文件 | 作用 | @@ -464,6 +530,7 @@ flowchart LR | 日期 | 变更 | |---|---| +| 2026-10-01 | 新增 **§9 CI 持续集成(Gitea Actions,T-6)**:`.gitea/workflows/ci.yml`(`l0-gates` + `l05-host-unit-tests`,Windows runner);**L0 + L0.5 上 CI、L1/L2/L3 留本地**;刻意不跑 `@build`、`.map` 缺失时自适应跳过 `mem-numbers`;新增 `scripts/ci_local.py` 本地等价命令(§4.1 登记);§1 补 CI 说明;`docs/待处理内容.md` T-6 销项 | | 2026-10-01 | §3.6 新增 **`test/run_all_suites.py`** 多套件回归编排(`try/finally` 无条件还原套件开关;T-5);`test/README.md` 同步 | | 2026-10-01 | §3.2 断言宏改为**集中内核**:新增 `test/board/test_common.h`(`test_stats_t` + `TEST_CHECK_IMPL`/`TEST_REPORT_IMPL`),4 套件各自用一行宏绑定自身统计变量(T-4;零行为变化、零 RAM 增量、`@build` 0/0,四套件均 0/0 验证)。附 `test_common.h` 不 include `dbg_log.h` 的原因(`DBG_TAG` 冻结 → `#47-D`) | | 2026-10-01 | 新增 §7.1 钩子 **`test-suite-switch`**(测试套件开关=产品构建,防测试固件静默提交)+ §3.1 铁律(跑完套件回归务必改回产品构建);依据 `docs/待处理内容.md` T-1(测试架构评审 P0)。同步更新 `../代码规范/pre-commit使用说明.md` §2.13 | diff --git a/docs/环境与部署/项目部署环境要求.md b/docs/环境与部署/项目部署环境要求.md index 7aed5065..03e10b15 100644 --- a/docs/环境与部署/项目部署环境要求.md +++ b/docs/环境与部署/项目部署环境要求.md @@ -376,6 +376,26 @@ pre-commit run --all-files > 获取渠道优先级:① `winget`(自动校验、免手动)→ ② 上表官方页 → ③ 网络受限时自行选可信镜像; > Keil / ST 官网访问受限时可走代理。 +### 2.11 CI runner 环境(Gitea Actions,2026-10-01 T-6) + +CI 跑 **L0 静态门禁 + L0.5 主机侧单测**(**不含 L1/L2/L3**——需真机 + ST-LINK,只在本地)。 +workflow 见 `.gitea/workflows/ci.yml`;覆盖边界与判定细节见 [`../测试说明/测试开发标准流程.md`](../测试说明/测试开发标准流程.md) §9。 + +| 项 | 要求 | 说明 | +|---|---|---| +| Gitea 版本 | ≥ 1.21(实测远程 **1.25.4**) | workflow 置于 `.gitea/workflows/`,兼容 GitHub Actions 语法 | +| Actions 开关 | 实例 `[actions] ENABLED = true` | site admin 配置;未开启则 push 不触发任何 job | +| runner OS | **Windows**(`runs-on: windows`) | labels 须含 `windows`;与开发机同平台,避免脚本语法分叉 | +| runner 软件 | Python 3.x、cppcheck、pre-commit、**MSVC**(VS C++ 工具链 + Windows SDK) | 前三个缺 → `l0-gates` **硬失败**(`::error::`);MSVC 缺 → `l05-host-unit-tests` **降级跳过并告警**(不硬失败,但等于 L0.5 裸奔) | +| Keil | **不需要** | 刻意不跑 `@build`(需授权);`.map` 缺失时 `mem-numbers` 钩子自动跳过 | +| `mem-numbers` | 有 `.map` 就跑,无则 `SKIP` | Keil 构建产物 `.map` 已 gitignore;自建带 Keil 的 runner 会照常跑该钩子 | + +**runner 就绪前的过渡**:CI 只是"多一道网",**不能替代本地门禁**。提交前仍须在本地跑: + +```powershell +& '.venv\Scripts\python.exe' scripts/ci_local.py # 与 CI 同一套判定(L0 + L0.5) +``` + --- ## 三、目标板环境(DTU 硬件) @@ -552,6 +572,7 @@ pre-commit run --all-files | 日期 | 变更 | |---|---| +| 2026-10-01 | **新增 §2.11 CI runner 环境(Gitea Actions,T-6)**:Gitea ≥1.21(实测 1.25.4)+ `[actions] ENABLED` + Windows runner(装 Python/cppcheck/pre-commit/MSVC);**不需要 Keil**(刻意不跑 `@build`,无 `.map` 时 `mem-numbers` 自动跳过);补"CI 不能替代本地门禁"与 `scripts/ci_local.py` 过渡命令 | | 2026-10-01 | **补主机侧单元测试(L0.5)环境要求**:§2.3 由"PC 模拟器环境"扩为"PC 侧编译环境(模拟器 + 主机侧单元测试)"——明确**同一套 VS 的 `cl.exe` 同时服务模拟器与单测**,MSVC 从"仅模拟器需要"升级为"提交门禁需要";§2.7 pre-commit 表补 `watchdog-unit-test` 钩子及"未装 VS 会被拦截";第 3 步 / 第 10 步冒烟 / 搭建清单 / §2.10 下载表(补 VS2022/2026 行)/ 速查表(补 `cl.exe` 缺失与 `C1083 stdio.h` 两条)/ 相关文档同步;总览拓扑图加 `MSVC cl.exe` 节点 | | 2026-09-21 | 更正 SDL2 描述:`simulator/SDL2/` 并不入库(gitignore,fresh clone 缺失),原"**随仓库提供**/不用装"表述有误;第 3 步补全官方预编译包下载(直链 + 内网中转)、解压、`include/SDL2/` 头文件复制、5 文件校验步骤;§2.3 表格与速查表同步修正 | | 2026-09-21 | 模拟器构建改为自适应:新增 `simulator/build_sim.bat/.ps1`(vswhere 自动探测本机 VS 大版本 → 生成器 16=2019 / 17=2022 / 18=2026;`build/` 生成器不符自动删除重建,删除带重试抗文件锁);VS 版本不再固定,2019/2022/2026 兼容:总览图、§2.3、第 3 步、第 10 步冒烟、搭建清单、速查表同步更新 | diff --git a/scripts/README.md b/scripts/README.md index af9f16fb..64080ab5 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -23,12 +23,23 @@ | `ram_report.py` | 从 `.map` 生成**静态 RAM/Flash 占用**报告(→ `docs/内存管理/data/`) | | `stack_report.py` | 从 Keil 静态调用图 + 串口 `[STACK]` 水位生成**每任务最坏链**报告 | +### CI 与本地等价(2026-10-01 T-6) + +| 脚本 / 文件 | 作用 | +|---|---| +| `ci_local.py` | **本地跑与 CI 等价的门禁**(L0 pre-commit + L0.5 主机侧单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers`;退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,说明见 `docs/测试说明/测试开发标准流程.md` §9 | + ## 使用方式 ```bash # 全库跑门禁 & 'C:\ProgramData\anaconda3\python.exe' -m pre_commit run --all-files +# 本地跑与 CI 等价的门禁(L0 + L0.5) +& '.venv\Scripts\python.exe' scripts/ci_local.py +& '.venv\Scripts\python.exe' scripts/ci_local.py --l0-only # 只跑 L0 +& '.venv\Scripts\python.exe' scripts/ci_local.py --l05-only # 只跑 L0.5 + # 单个脚本 & '.venv\Scripts\python.exe' scripts/check_doc_links.py --all & '.venv\Scripts\python.exe' scripts/gen_task_table.py --check # 或 --write / --diff diff --git a/scripts/ci_local.py b/scripts/ci_local.py new file mode 100644 index 00000000..3381f0e5 --- /dev/null +++ b/scripts/ci_local.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""ci_local.py - 本地一键跑"与 CI 等价"的门禁(纯标准库)。 + +为什么需要它(2026-10-01 T-6): + CI(`.gitea/workflows/ci.yml`)只在 push / PR 时跑,且需要 Gitea Actions + + Windows runner 就绪。**提交前想自查、或 runner 还没配好时**,用本脚本在本地 + 跑同一套判定,命令与退出码与 CI 一一对应。 + +覆盖范围(与 CI 完全一致): + L0 静态门禁 = `pre-commit run --all-files` + L0.5 主机侧纯逻辑单测 = `test/host/**/run_unit_test.py` + + 刻意**不含**(与 CI 同理由,见 workflow 文件头): + - `@build`(需 Keil 授权;CI 也不跑,真编译是开发者本地红线) + - `mem-numbers` 钩子(事实源 `.map` 是 Keil 产物——**本地若存在 .map 就照常跑**, + 与 CI 的自适应逻辑一致) + +退出码: + 0 = 全部通过(或不适用项已按规则跳过) + 1 = 有门禁/单测失败 + 2 = 环境错误(缺 pre-commit / python 模块等) + +用法(仓库根目录): + & '.venv\\Scripts\\python.exe' scripts/ci_local.py + & '.venv\\Scripts\\python.exe' scripts/ci_local.py --l0-only + & '.venv\\Scripts\\python.exe' scripts/ci_local.py --l05-only + & '.venv\\Scripts\\python.exe' scripts/ci_local.py --keep-going # 失败也继续跑完 + +设计依据:`.gitea/workflows/ci.yml`;docs/测试说明/测试开发标准流程.md §9 +创建日期:2026-10-01 +""" + +import argparse +import os +import subprocess +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent + + +def _resolve_python(): + """挑一个真正装了 pre-commit 的解释器。 + + 本机可能有多个 python(.venv / anaconda3 / 系统),而 **pre-commit 只装在其中一个**。 + 按优先级探测:当前解释器 → `.venv` → anaconda3 → PATH 上的 `pre-commit` 所在环境。 + 找到即返回 (python_path, note);都找不到则返回 (当前解释器, None) 交由调用方报错。 + """ + candidates = [sys.executable] + venv = REPO_ROOT / ".venv" / "Scripts" / "python.exe" + if venv.is_file(): + candidates.append(str(venv)) + candidates.append(r"C:\ProgramData\anaconda3\python.exe") + + seen = set() + for py in candidates: + if not py or py in seen or not os.path.isfile(py): + continue + seen.add(py) + try: + cp = subprocess.run([py, "-m", "pre_commit", "--version"], + capture_output=True, timeout=60) + except (OSError, subprocess.TimeoutExpired): + continue + if cp.returncode == 0: + note = None if py == sys.executable else "(自动选用,本解释器无 pre-commit)" + return py, note + return sys.executable, None + + +PY, PY_NOTE = _resolve_python() + + +def _find_map(): + """返回 Keil 构建产物 .map 的路径(不存在则 None)。""" + hits = sorted((REPO_ROOT / "MDK-ARM").rglob("*.map")) + return hits[0] if hits else None + + +def _find_unit_runners(): + """test/host 下所有模块运行器,按路径排序。""" + return sorted((REPO_ROOT / "test" / "host").rglob("run_unit_test.py")) + + +def _run(cmd, env=None): + """跑子进程,输出直通终端。返回退出码。""" + print("$ " + " ".join(str(c) for c in cmd)) + cp = subprocess.run(cmd, cwd=str(REPO_ROOT), env=env) + return cp.returncode + + +def run_l0(): + """L0:pre-commit 全部钩子。返回退出码(0/1/2)。""" + print("") + print("=" * 64) + print(" L0 静态门禁(pre-commit --all-files)") + print("=" * 64) + + env = dict(os.environ) + # 与 CI 相同的自适应:无 .map(未做 Keil 构建)=> mem-numbers 不适用 => 跳过 + map_path = _find_map() + if map_path: + print("[INFO] 找到 .map:{}".format(map_path.relative_to(REPO_ROOT))) + print("[INFO] mem-numbers 照常运行") + else: + print("[INFO] 未找到 .map(未做 Keil 构建)-> 跳过 mem-numbers(与 CI 一致)") + skip = env.get("SKIP", "") + env["SKIP"] = (skip + "," if skip else "") + "mem-numbers" + + return _run([PY, "-m", "pre_commit", "run", "--all-files"], env=env) + + +def run_l05(args): + """L0.5:主机侧单测。返回退出码(0/1/2)。""" + print("") + print("=" * 64) + print(" L0.5 主机侧纯逻辑单测(MSVC,零硬件)") + print("=" * 64) + + runners = _find_unit_runners() + if not runners: + print("[WARN] 未找到 test/host/**/run_unit_test.py —— 跳过") + return 0 + + rc_all = 0 + for r in runners: + rel = r.relative_to(REPO_ROOT) + print("") + print("--- 运行:{} ---".format(rel)) + rc = _run([PY, str(r)]) + if rc != 0: + print("[FAIL] {}(退出码 {})".format(rel, rc)) + if rc == 2: + print(" (退出码 2 = 环境错误:多半是本机未装 MSVC,见 " + "docs/测试说明/测试开发标准流程.md §4.2)") + rc_all = 1 + if not args.keep_going: + break + else: + print("[OK] {}".format(rel)) + return rc_all + + +def main(argv=None): + for stream in (sys.stdout, sys.stderr): + try: + stream.reconfigure(encoding="utf-8", errors="replace") + except Exception: + pass + + ap = argparse.ArgumentParser( + description="本地跑与 CI 等价的门禁(L0 + L0.5,纯标准库)") + g = ap.add_mutually_exclusive_group() + g.add_argument("--l0-only", action="store_true", help="只跑 L0 静态门禁") + g.add_argument("--l05-only", action="store_true", help="只跑 L0.5 主机侧单测") + ap.add_argument("--keep-going", action="store_true", + help="某步失败也继续跑后续步骤(默认首错即停)") + args = ap.parse_args(argv) + + print("=== 本地 CI(等价 .gitea/workflows/ci.yml)===") + print("仓库:{}".format(REPO_ROOT)) + print("Python:{}".format(PY)) + if PY_NOTE: + print(" {}".format(PY_NOTE)) + + # 环境预检:pre-commit 必须可用(CI 里由"环境自检"步骤拦) + try: + probe = subprocess.run([PY, "-m", "pre_commit", "--version"], + capture_output=True, timeout=60) + except (OSError, subprocess.TimeoutExpired) as e: + print("[ERROR] 无法调用 pre-commit:{}".format(e)) + return 2 + if probe.returncode != 0: + print("[ERROR] 该解释器没有 pre-commit 模块。请任选其一:") + print(" - 用装了 pre-commit 的解释器跑本脚本(如 " + r"& 'C:\ProgramData\anaconda3\python.exe' scripts/ci_local.py)") + print(" - 或在当前环境安装:pip install pre-commit") + return 2 + + rc = 0 + try: + if not args.l05_only: + rc = run_l0() + if rc != 0 and not args.keep_going: + print("") + print("[FAIL] L0 未通过 —— 中止(--keep-going 可继续跑 L0.5)") + return 1 + if not args.l0_only: + rc05 = run_l05(args) + rc = rc or rc05 + except FileNotFoundError as e: + print("[ERROR] 环境错误:{}".format(e)) + return 2 + + print("") + print("=" * 64) + if rc == 0: + print(" [OK] 本地 CI 全部通过(L0 + L0.5)") + else: + print(" [FAIL] 本地 CI 有未通过项(见上方输出)") + print("=" * 64) + return rc + + +if __name__ == "__main__": + sys.exit(main())