CI:补产物存档与结果汇总,明示体积门禁盲区(S3+S14)
This commit is contained in:
+130
-8
@@ -14,6 +14,17 @@
|
||||
# 无 `@build` 即无 `.map`。故本 workflow 在**检测不到 .map 时自动跳过该钩子**
|
||||
# (见下方 "决定跳过哪些钩子" 步骤),有 .map 的 runner(如自建带 Keil 的机器)则照常跑。
|
||||
#
|
||||
# 🔴 门禁盲区声明(S14,2026-10-02):跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**。
|
||||
# 体积变更(新增任务/缓冲/代码)不会在 CI 被拦下;必须靠**本地 `@build` 后**
|
||||
# `python scripts/check_mem_numbers.py --scan` 兜底。故该跳过以 `::warning::` 明示
|
||||
# (不是弱化的 `::notice::`),并写入 job step-summary 的醒目行。
|
||||
#
|
||||
# 产物存档(S3,2026-10-02):两个 job 把关键输出 `Tee-Object` 到 `ci-artifacts/*.log`,
|
||||
# 并写 $GITHUB_STEP_SUMMARY(Gitea UI 直接可见)+ `::notice::` 汇总行。
|
||||
# **刻意不用 `actions/upload-artifact`**——内网可能拉不到外层 action(见 CI配置指南 §4.3);
|
||||
# 零依赖方案下日志留在 runner 工作区,UI 通过 step-summary 即可排障。
|
||||
# ⚠️ `ci-artifacts/` 已 gitignore(运行时产物)。
|
||||
#
|
||||
# 触发:push(除纯文档路径外)与 pull_request。
|
||||
#
|
||||
# 🔴 shell 用 `powershell` 而非 `pwsh`(2026-10-02 实测修正):
|
||||
@@ -156,13 +167,27 @@ jobs:
|
||||
run: |
|
||||
# mem-numbers fact source is the Keil build artifact .map (gitignored).
|
||||
# No .map => this hook cannot run => skip (not "pass", but "not applicable").
|
||||
#
|
||||
# S14 (2026-10-02): make this GAP loud. mem-numbers is the ONLY gate that
|
||||
# checks docs/memory numbers against the real .map (Flash/RAM size). When
|
||||
# it is skipped, CI effectively does NOT guard the size budget -- a silent
|
||||
# ::notice:: was too easy to miss. So:
|
||||
# - emit ::warning:: (not ::notice::) so it shows in the run summary
|
||||
# - write a machine-readable marker file for the summary step
|
||||
# - append a bold line to the job step-summary
|
||||
$map = Get-ChildItem -Path 'MDK-ARM' -Recurse -Filter '*.map' -ErrorAction SilentlyContinue
|
||||
if ($map) {
|
||||
Write-Host "found .map: $($map[0].FullName) -- mem-numbers runs normally"
|
||||
"value=" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"sizegate=on" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"> Volume gate (mem-numbers): RAN (.map present)" |
|
||||
Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
|
||||
} else {
|
||||
Write-Host "::notice::no .map found (no Keil build) -- skipping mem-numbers hook"
|
||||
Write-Host "::warning::no .map found (no Keil build) -- SKIPPING mem-numbers, so the Flash/RAM size gate does NOT run in CI. Size changes are NOT caught here; verify locally with 'python scripts/check_mem_numbers.py --scan' after a Keil build."
|
||||
"value=mem-numbers" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"sizegate=off" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"> :warning: Volume gate (mem-numbers): **NOT RUN** -- no `.map` (no Keil build)." |
|
||||
Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
|
||||
}
|
||||
|
||||
- name: 运行 L0 门禁(--all-files)
|
||||
@@ -170,13 +195,55 @@ jobs:
|
||||
env:
|
||||
SKIP: ${{ steps.skip.outputs.value }}
|
||||
run: |
|
||||
# Pure ASCII only (see file header). Capture output to an artifact file
|
||||
# AND to the console (Tee-Object), so a failed run is debuggable from the
|
||||
# Gitea UI without inspecting runner disk.
|
||||
#
|
||||
# CAUTION: the runner injects $ErrorActionPreference='Stop'. With `2>&1 |`,
|
||||
# a native command's stderr becomes an ErrorRecord -> under 'Stop' that
|
||||
# would ABORT the pipeline. Relax the preference around the pipeline and
|
||||
# restore it afterwards (same pattern as the env-check step above).
|
||||
New-Item -ItemType Directory -Force -Path 'ci-artifacts' | Out-Null
|
||||
$log = 'ci-artifacts\l0-pre-commit.log'
|
||||
if ($env:SKIP) { Write-Host "SKIP=$env:SKIP" }
|
||||
& $env:PYTHON -s -m pre_commit run --all-files --show-diff-on-failure
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "::error::L0 static gates failed (see output above)"
|
||||
exit $LASTEXITCODE
|
||||
$saved = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $env:PYTHON -s -m pre_commit run --all-files --show-diff-on-failure 2>&1 |
|
||||
Tee-Object -FilePath $log
|
||||
$rc = $LASTEXITCODE
|
||||
$ErrorActionPreference = $saved
|
||||
|
||||
# Summarize hook results: count Passed / Failed / Skipped lines.
|
||||
$lines = Get-Content $log -ErrorAction SilentlyContinue
|
||||
$passed = @($lines | Select-String -Pattern 'Passed$').Count
|
||||
$failed = @($lines | Select-String -Pattern 'Failed$').Count
|
||||
$skipd = @($lines | Select-String -Pattern 'Skipped$').Count
|
||||
$summary = "L0: pre-commit hooks -- passed=$passed failed=$failed skipped=$skipd (log: $log)"
|
||||
Write-Host "::notice::$summary"
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### L0 static gates" -Encoding utf8
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $summary -Encoding utf8
|
||||
|
||||
if ($rc -ne 0) {
|
||||
Write-Host "::error::L0 static gates failed (see output above and $log)"
|
||||
exit $rc
|
||||
}
|
||||
|
||||
- name: 归档 L0 日志(供下载/排障)
|
||||
if: always()
|
||||
shell: powershell
|
||||
run: |
|
||||
# S3: zero-dependency artifact archival. We do NOT use
|
||||
# actions/upload-artifact (inner networks may not fetch outer actions);
|
||||
# instead we keep the log on the runner AND surface its path + tail in
|
||||
# the step summary, so the UI is self-sufficient for triage.
|
||||
if (Test-Path 'ci-artifacts\l0-pre-commit.log') {
|
||||
$size = (Get-Item 'ci-artifacts\l0-pre-commit.log').Length
|
||||
Write-Host "archived: ci-artifacts/l0-pre-commit.log ($size bytes)"
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "" -Encoding utf8
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Archived log: ``ci-artifacts/l0-pre-commit.log`` ($size bytes on runner)" -Encoding utf8
|
||||
}
|
||||
Write-Host "::notice::L0 artifacts kept on runner at: ci-artifacts\l0-pre-commit.log"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# L0.5:主机侧纯逻辑单测(零硬件;缺 MSVC 则跳过)
|
||||
# ------------------------------------------------------------------
|
||||
@@ -213,6 +280,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: 运行主机侧单测
|
||||
id: l05
|
||||
if: steps.msvc.outputs.value == 'yes'
|
||||
shell: powershell
|
||||
run: |
|
||||
@@ -229,6 +297,8 @@ jobs:
|
||||
if (-not $py) { Write-Host "::error::no python found"; exit 1 }
|
||||
Write-Host "using python: $py"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path 'ci-artifacts' | Out-Null
|
||||
|
||||
# Iterate test/host/*/run_unit_test.py (each module ships its own runner via common harness)
|
||||
$runners = Get-ChildItem -Path 'test/host' -Recurse -Filter 'run_unit_test.py' |
|
||||
Sort-Object FullName
|
||||
@@ -237,22 +307,74 @@ jobs:
|
||||
exit 0
|
||||
}
|
||||
$fail = 0
|
||||
$runnerCount = 0
|
||||
$sumPass = 0
|
||||
$sumTotal = 0
|
||||
# Relax preference around each `2>&1 | Tee-Object` pipeline (native stderr
|
||||
# becomes ErrorRecord -> 'Stop' would abort). See L0 step for details.
|
||||
$savedPref = $ErrorActionPreference
|
||||
foreach ($r in $runners) {
|
||||
$runnerCount++
|
||||
$name = $r.Directory.Name
|
||||
$log = "ci-artifacts\l05-$name.log"
|
||||
Write-Host "================================"
|
||||
Write-Host "run: $($r.FullName)"
|
||||
Write-Host "================================"
|
||||
& $py $r.FullName
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "::error::unit test failed: $($r.FullName) (exit $LASTEXITCODE)"
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $py $r.FullName 2>&1 | Tee-Object -FilePath $log
|
||||
$rc = $LASTEXITCODE
|
||||
$ErrorActionPreference = $savedPref
|
||||
|
||||
# Parse the module's own summary line, e.g.
|
||||
# "=== System Clock Tests: 100/100 PASSED (failed=0) ==="
|
||||
$m = Select-String -Path $log -Pattern ': (\d+)/(\d+) PASSED \(failed=(\d+)\)' -ErrorAction SilentlyContinue |
|
||||
Select-Object -Last 1
|
||||
if ($m) {
|
||||
$sumPass += [int]$m.Matches[0].Groups[1].Value
|
||||
$sumTotal += [int]$m.Matches[0].Groups[2].Value
|
||||
Write-Host ("::notice::{0}: {1}" -f $name, $m.Matches[0].Value.Trim())
|
||||
} else {
|
||||
Write-Host "::warning::$name produced no '=== ... N/M PASSED ===' summary line"
|
||||
}
|
||||
if ($rc -ne 0) {
|
||||
Write-Host "::error::unit test failed: $($r.FullName) (exit $rc)"
|
||||
$fail = 1
|
||||
}
|
||||
}
|
||||
|
||||
$agg = "L0.5: $runnerCount runner(s), $sumPass/$sumTotal assertions passed, fail=$fail"
|
||||
Write-Host "::notice::$agg"
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### L0.5 host-side unit tests" -Encoding utf8
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $agg -Encoding utf8
|
||||
"count=$runnerCount" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"agg=$agg" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
exit $fail
|
||||
|
||||
- name: 归档 L0.5 日志(供下载/排障)
|
||||
if: always()
|
||||
shell: powershell
|
||||
run: |
|
||||
# S3: same zero-dependency archival as L0.
|
||||
if (Test-Path 'ci-artifacts') {
|
||||
$logs = Get-ChildItem -Path 'ci-artifacts' -Filter '*.log' -ErrorAction SilentlyContinue
|
||||
foreach ($f in $logs) {
|
||||
Write-Host "archived: $($f.FullName) ($($f.Length) bytes)"
|
||||
}
|
||||
$names = ($logs | ForEach-Object { '``' + $_.Name + '``' }) -join ', '
|
||||
if ($names) {
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "" -Encoding utf8
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Archived logs on runner: $names" -Encoding utf8
|
||||
}
|
||||
} else {
|
||||
Write-Host "no ci-artifacts directory (L0.5 skipped or failed early)"
|
||||
}
|
||||
|
||||
- name: L0.5 跳过说明
|
||||
if: steps.msvc.outputs.value == 'no'
|
||||
shell: powershell
|
||||
run: |
|
||||
Write-Host "L0.5 skipped: runner has no MSVC."
|
||||
Write-Host "Local equivalent: & '.venv\Scripts\python.exe' scripts/ci_local.py"
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### L0.5 host-side unit tests" -Encoding utf8
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value ":warning: **NOT RUN** -- runner has no MSVC (VS C++ toolchain)." -Encoding utf8
|
||||
exit 0
|
||||
|
||||
@@ -130,3 +130,7 @@ docs/images/*.png
|
||||
|
||||
# Serial assistant (tools/serial_tool) runtime state (daemon log/meta, local runtime artifacts)
|
||||
tools/serial_tool/state/
|
||||
|
||||
# CI 运行时产物(.gitea/workflows/ci.yml 的 S3 步骤在 runner 工作区落盘的日志;
|
||||
# 零依赖存档方案——落在工作区目录而非上传 artifact,故必须忽略以免误提交)
|
||||
ci-artifacts/
|
||||
|
||||
+19
-6
@@ -457,17 +457,29 @@ flowchart LR
|
||||
|
||||
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 的机器)则照常跑。
|
||||
2. **`mem-numbers` 钩子按 `.map` 有无自适应跳过**——它的事实源是 `MDK-ARM/**/*.map`
|
||||
(Keil 构建产物,已 `.gitignore`),无 `@build` 即无 `.map`。故 workflow 在**检测不到
|
||||
`.map` 时自动跳过该钩子**(`SKIP=mem-numbers`,钩子显示 `Skipped` 而非 `Passed`——
|
||||
是"不适用",不是"通过");有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。
|
||||
|
||||
> 🔴 **门禁盲区(S14,2026-10-02)**:跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**——
|
||||
> 新增任务/栈/缓冲/代码导致的体积增长**不会在 CI 被拦**。故该跳过以 `::warning::` 明示
|
||||
> (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `@build`(0/0)
|
||||
> → `python scripts/check_mem_numbers.py --scan`。详见
|
||||
> [`../环境与部署/CI配置指南.md`](../环境与部署/CI配置指南.md) §0.0。
|
||||
|
||||
### 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 全红") |
|
||||
| `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`;无 `.map` 时 `::warning::` 声明体积门禁未跑)→ `pre-commit run --all-files --show-diff-on-failure`(`env: SKIP`,输出 `Tee` 到 `ci-artifacts/l0-pre-commit.log`)→ 打印 `::notice::L0: passed/failed/skipped` 汇总行 → 归档日志说明 |
|
||||
| `l05-host-unit-tests` | `windows` | checkout → **探测 MSVC**(按 VS_ROOTS 找 `Hostx64\x64\cl.exe`)→ 遍历 `test/host/**/run_unit_test.py` 逐个跑(输出 `Tee` 到 `ci-artifacts/l05-<模块>.log`;解析各模块 `N/M PASSED` 汇总行 → 聚合 `::notice::L0.5: K runner(s), A/B assertions passed`;失败 `::error::` 并置退出码);**未探测到 MSVC 时打印醒目 WARNING 并降级跳过**(不硬失败,避免"没装 VS 的 runner 全红") |
|
||||
|
||||
**结果可见性(S3,2026-10-02)**:两个 job 均写 `$GITHUB_STEP_SUMMARY`(job 运行页的
|
||||
**Summary** 区块直接可见)+ `::notice::` 汇总行;日志落 `ci-artifacts/*.log`(runner 工作区,
|
||||
已 gitignore)。**刻意不用 `actions/upload-artifact`**(内网可能拉不到外层 action,见
|
||||
`CI配置指南.md` §4.3/§4.4)——零依赖方案下,"不查 MySQL / 不翻磁盘即可排障"的目标由
|
||||
step-summary 达成。
|
||||
|
||||
触发:`push`(`paths-ignore: docs/** / **/*.md / .workbuddy-ai/**`)与 `pull_request`,另留 `workflow_dispatch` 手动触发。
|
||||
`concurrency: group: ci-${{ github.ref }}` + `cancel-in-progress: true`(同分支旧运行自动取消,省 runner)。
|
||||
@@ -537,6 +549,7 @@ CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runn
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-02 | **S3 + S14**:§9.1 第 2 条改正(原文"强制跑 `mem-numbers`"自相矛盾 → 改为"按 `.map` 有无自适应跳过")并加 🔴 门禁盲区声明;§9.2 补 S3 结果可见性(`Tee-Object` 落盘 + `$GITHUB_STEP_SUMMARY` + `::notice::` 汇总行,不用 `actions/upload-artifact`)。另:§4.1 登记 `test/host/sys_clock/` 新单测(S1-a) |
|
||||
| 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`) |
|
||||
|
||||
+57
-4
@@ -7,6 +7,25 @@
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 0.0 CI 不覆盖的体积门禁(必读,S14)
|
||||
|
||||
> **CI 不守 Flash/RAM 体积门禁。** `mem-numbers` 钩子的事实源是 Keil 构建产物
|
||||
> `MDK-ARM/**/*.map`(已 gitignore),而 CI 刻意不跑 `@build` → **无 `.map` → 该钩子被跳过**。
|
||||
>
|
||||
> 后果:**新增任务 / 栈 / 缓冲 / 代码导致的体积增长(或文档内存数字失真)不会在 CI 被拦下。**
|
||||
>
|
||||
> 兜底(每次改代码后必做):
|
||||
> ```powershell
|
||||
> & "MDK-ARM\build.bat" # 0/0 且生成 .map
|
||||
> & '.venv\Scripts\python.exe' scripts/check_mem_numbers.py --scan
|
||||
> ```
|
||||
> 若 CI 允许在带 Keil 的 runner 上跑,则该钩子会在有 `.map` 时**照常执行**(workflow 自适应)。
|
||||
>
|
||||
> CI 运行时该跳过以 **`::warning::`** 明示(不是弱化的 notice),并写入 job 的
|
||||
> **step-summary** 醒目行,避免被忽略。
|
||||
|
||||
---
|
||||
|
||||
## 0. 全景:CI 需要三样东西就位
|
||||
|
||||
```
|
||||
@@ -258,6 +277,35 @@ github.com 会在 checkout 步骤卡住/失败。三种处置:
|
||||
> 方案 3 无需任何外部 action 与任何 `DEFAULT_ACTIONS_URL` 配置;代价是失去 checkout 的缓存优化。
|
||||
> 参考:Gitea FAQ「Where will the runner download scripts when using actions such as `actions/checkout@v4`?」
|
||||
|
||||
### 4.4 产物存档与结果汇总(S3,2026-10-02)
|
||||
|
||||
**目标**:CI 失败时**不查 MySQL / 不翻 runner 磁盘**,在 Gitea 页面即可看到完整输出与摘要。
|
||||
|
||||
**零依赖方案**(内网首选,**不用 `actions/upload-artifact`**):
|
||||
|
||||
| 手段 | 说明 |
|
||||
|---|---|
|
||||
| `Tee-Object` 落盘 | 两个 job 把关键输出 `Tee` 到 `ci-artifacts/l0-pre-commit.log`、`ci-artifacts/l05-<模块>.log`(**留在 runner 工作区**,已 gitignore) |
|
||||
| `$GITHUB_STEP_SUMMARY` | 写入 job 的 **step-summary**(Gitea UI 运行页直接可见,无需下钻日志) |
|
||||
| `::notice::` 汇总行 | 控制台醒目行,便于日志检索 |
|
||||
|
||||
**汇总行格式**:
|
||||
|
||||
| 层 | 汇总行 |
|
||||
|---|---|
|
||||
| L0 | `L0: pre-commit hooks -- passed=N failed=M skipped=K (log: ci-artifacts\l0-pre-commit.log)` |
|
||||
| L0.5 | `L0.5: <K> runner(s), <A>/<B> assertions passed, fail=<F>`(逐模块另有 `::notice::<模块>: N/M PASSED (failed=0)`) |
|
||||
|
||||
> ⚠️ **`ci-artifacts/` 已加入 `.gitignore`**:它是 runner 工作区的运行时产物,不要提交。
|
||||
>
|
||||
> ⚠️ **若后续改为 `actions/upload-artifact`**(需确认实例能拉该 action):把 `Tee` 落盘
|
||||
> 保留即可,上传步骤叠加在其后;本方案的落盘文件即上传源。
|
||||
|
||||
**PowerShell 陷阱(已在 workflow 内处理)**:runner 注入 `$ErrorActionPreference='Stop'`,
|
||||
而 `& native 2>&1 | Tee-Object` 会把 native 的 stderr 变成 ErrorRecord → 在 `Stop` 下**整段中止**。
|
||||
故 pipeline 前后需 `Save/Restore` 该偏好为 `Continue`(与"环境自检"步骤同法)。
|
||||
`$LASTEXITCODE` 在 `Tee-Object` 之后**仍保留** native 退出码(实测),可直接用于判定。
|
||||
|
||||
---
|
||||
|
||||
## 5. 端到端验证清单
|
||||
@@ -270,9 +318,10 @@ github.com 会在 checkout 步骤卡住/失败。三种处置:
|
||||
| 2 | Settings → Actions → Runners 里 runner 为 **Idle** | runner 注册成功 |
|
||||
| 3 | runner 上 `python --version` / `cppcheck --version` / `python -m pre_commit --version` | 三条全通 |
|
||||
| 4 | `git push` 一次(或页面 Run workflow) | Actions 页出现运行中任务 |
|
||||
| 5 | `l0-gates` 日志 | `pre-commit ... Passed`;无 `.map` 时 `mem-numbers` 显示 `Skipped` |
|
||||
| 6 | `l05-host-unit-tests` 日志 | `=== Task Watchdog Tests: 39/39 PASSED (failed=0) ===` |
|
||||
| 7 | 提交记录旁出现 ✅ | CI 全绿 |
|
||||
| 5 | `l0-gates` 日志 | `pre-commit ... Passed`;`::notice::L0: pre-commit hooks -- passed=.. failed=0 skipped=..`;无 `.map` 时见 `::warning::` 体积门禁未跑 |
|
||||
| 6 | `l05-host-unit-tests` 日志 | `::notice::L0.5: 2 runner(s), 139/139 assertions passed, fail=0` |
|
||||
| 7 | job 运行页的 **Summary** 区块 | 出现 "L0 static gates" / "L0.5 host-side unit tests" 小标题与汇总行 |
|
||||
| 8 | 提交记录旁出现 ✅ | CI 全绿 |
|
||||
|
||||
**本地先自查**(与 CI 同一套判定,避免推上去才发现问题):
|
||||
|
||||
@@ -291,7 +340,10 @@ github.com 会在 checkout 步骤卡住/失败。三种处置:
|
||||
| `l0-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) |
|
||||
| `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §4.3 |
|
||||
| `l05` 日志出现 `::warning::runner 未安装 MSVC` | VS 装在非默认路径或未装 | 见 §3.2(默认路径 / 同步改两处根目录) |
|
||||
| `mem-numbers` 显示 `Skipped` | 正常(CI 无 Keil,无 `.map`) | 无需处理;是"不适用"不是"失败" |
|
||||
| `mem-numbers` 显示 `Skipped` | 正常(CI 无 Keil,无 `.map`) | 无需处理;但注意 **CI 未守体积门禁**(见 §0.0),改代码后本地 `@build` + `check_mem_numbers.py --scan` 兜底 |
|
||||
| L0.5 显示 `2 runner(s), X/Y ...` 但 X<Y | 有单测用例未通过 | 看该模块的 `::error::unit test failed` 行与其 `ci-artifacts/l05-<模块>.log` |
|
||||
| L0.5 报 `produced no '=== ... N/M PASSED ===' summary line` | 该模块运行器未输出汇总行(改了 harness 或运行器崩溃) | 核对 `test/host/common/unit_test_harness.py` 的 `summary_marker` 与该模块 `TestBuild` 是否一致 |
|
||||
| `Tee-Object` 步骤报 `TerminatorExpectedAtEndOfString` / 字符串缺少终止符 | `run:` 块混入了非 ASCII(GBK 解码错位) | 见文件头 🔴🔴;`run:` 内必须纯 ASCII |
|
||||
| `l05` 报 `fatal error C1083: 无法打开包括文件 "stdio.h"` | Windows SDK 缺失 | 装 VS 时勾"使用 C++ 的桌面开发"+ SDK |
|
||||
| 单测退出码 2 | 环境错误(多为无 MSVC) | 同上 |
|
||||
| `pre-commit` 首次运行久 | 首次要下载各钩子的环境 | 正常,runner 缓存后会快 |
|
||||
@@ -313,5 +365,6 @@ github.com 会在 checkout 步骤卡住/失败。三种处置:
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-02 | **S3 + S14**:新增 §0.0「CI 不覆盖的体积门禁」醒目声明;新增 §4.4「产物存档与结果汇总」(零依赖 `Tee-Object` + `$GITHUB_STEP_SUMMARY` + `::notice::` 汇总行,不用 `actions/upload-artifact`;记录 `$ErrorActionPreference='Stop'` 与 `2>&1 \| Tee-Object` 的陷阱及处置)。§5 验证清单(7→8 条)与 §6 排障表同步更新 |
|
||||
| 2026-10-01 | **修正 `DEFAULT_ACTIONS_URL` 说明**(原 §1.2 示例误导):新增 §1.2.1 明确它控制的是 **action 下载源**(默认 `github.com`;`self` = 从自己实例拉且需先镜像 action),**不是填自己的 Gitea 地址**;§4.3 同步修正(原文误写"gitea.com 代理"),并补零依赖写法为内网首选。依据:Gitea 官方 FAQ + config-cheat-sheet |
|
||||
| 2026-10-01 | 初版:Gitea Actions 配置全流程(服务端开关 / Windows runner 注册 / runner 软件环境 / workflow 位置与触发 / 内网 action 拉取 / 端到端验证清单 / 故障排查 / 日常维护) |
|
||||
|
||||
+58
-2
@@ -21,6 +21,10 @@
|
||||
1 = 有门禁/单测失败
|
||||
2 = 环境错误(缺 pre-commit / python 模块等)
|
||||
|
||||
汇总行(S3,2026-10-02,对齐 CI 的 ::notice::):
|
||||
[SUMMARY] L0: pre-commit hooks -- passed=N failed=M skipped=K
|
||||
[SUMMARY] L0.5: K runner(s), A/B assertions passed, fail=F
|
||||
|
||||
用法(仓库根目录):
|
||||
& '.venv\\Scripts\\python.exe' scripts/ci_local.py
|
||||
& '.venv\\Scripts\\python.exe' scripts/ci_local.py --l0-only
|
||||
@@ -33,6 +37,7 @@
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -90,6 +95,27 @@ def _run(cmd, env=None):
|
||||
return cp.returncode
|
||||
|
||||
|
||||
def _run_capture(cmd, env=None):
|
||||
"""跑子进程,输出**既直通终端又捕获**(对齐 CI 的 Tee-Object)。
|
||||
|
||||
返回 (退出码, 全部输出文本)。用于 L0.5 解析各模块汇总行(S3 汇总)。
|
||||
"""
|
||||
print("$ " + " ".join(str(c) for c in cmd))
|
||||
cp = subprocess.run(cmd, cwd=str(REPO_ROOT), env=env,
|
||||
capture_output=True)
|
||||
try:
|
||||
text = cp.stdout.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
text = cp.stdout.decode("utf-8", errors="replace")
|
||||
sys.stdout.write(text)
|
||||
if cp.stderr:
|
||||
try:
|
||||
sys.stderr.write(cp.stderr.decode("utf-8"))
|
||||
except UnicodeDecodeError:
|
||||
sys.stderr.write(cp.stderr.decode("utf-8", errors="replace"))
|
||||
return cp.returncode, text
|
||||
|
||||
|
||||
def run_l0():
|
||||
"""L0:pre-commit 全部钩子。返回退出码(0/1/2)。"""
|
||||
print("")
|
||||
@@ -105,10 +131,20 @@ def run_l0():
|
||||
print("[INFO] mem-numbers 照常运行")
|
||||
else:
|
||||
print("[INFO] 未找到 .map(未做 Keil 构建)-> 跳过 mem-numbers(与 CI 一致)")
|
||||
print("[INFO] :warning: 体积门禁未执行——改代码后请本地 @build + "
|
||||
"check_mem_numbers.py --scan 兜底")
|
||||
skip = env.get("SKIP", "")
|
||||
env["SKIP"] = (skip + "," if skip else "") + "mem-numbers"
|
||||
|
||||
return _run([PY, "-m", "pre_commit", "run", "--all-files"], env=env)
|
||||
rc, text = _run_capture([PY, "-m", "pre_commit", "run", "--all-files"], env=env)
|
||||
# 汇总行(对齐 CI 的 ::notice::):统计钩子 Passed/Failed/Skipped
|
||||
lines = text.splitlines()
|
||||
passed = sum(1 for l in lines if l.rstrip().endswith("Passed"))
|
||||
failed = sum(1 for l in lines if l.rstrip().endswith("Failed"))
|
||||
skipd = sum(1 for l in lines if l.rstrip().endswith("Skipped"))
|
||||
print("[SUMMARY] L0: pre-commit hooks -- passed={} failed={} skipped={}".format(
|
||||
passed, failed, skipd))
|
||||
return rc
|
||||
|
||||
|
||||
def run_l05(args):
|
||||
@@ -124,11 +160,28 @@ def run_l05(args):
|
||||
return 0
|
||||
|
||||
rc_all = 0
|
||||
runner_count = 0
|
||||
sum_pass = 0
|
||||
sum_total = 0
|
||||
for r in runners:
|
||||
runner_count += 1
|
||||
rel = r.relative_to(REPO_ROOT)
|
||||
print("")
|
||||
print("--- 运行:{} ---".format(rel))
|
||||
rc = _run([PY, str(r)])
|
||||
rc, text = _run_capture([PY, str(r)])
|
||||
# 解析模块自身汇总行,如 "=== System Clock Tests: 100/100 PASSED (failed=0) ==="
|
||||
m = None
|
||||
for line in text.splitlines():
|
||||
mm = re.search(r":\s*(\d+)/(\d+)\s+PASSED\s+\(failed=(\d+)\)", line)
|
||||
if mm:
|
||||
m = mm
|
||||
if m:
|
||||
sum_pass += int(m.group(1))
|
||||
sum_total += int(m.group(2))
|
||||
print("[SUMMARY] {}: {}/{} PASSED (failed={})".format(
|
||||
r.parent.name, m.group(1), m.group(2), m.group(3)))
|
||||
else:
|
||||
print("[WARN] {} 未输出 '=== ... N/M PASSED ===' 汇总行".format(rel))
|
||||
if rc != 0:
|
||||
print("[FAIL] {}(退出码 {})".format(rel, rc))
|
||||
if rc == 2:
|
||||
@@ -139,6 +192,9 @@ def run_l05(args):
|
||||
break
|
||||
else:
|
||||
print("[OK] {}".format(rel))
|
||||
|
||||
print("[SUMMARY] L0.5: {} runner(s), {}/{} assertions passed, fail={}".format(
|
||||
runner_count, sum_pass, sum_total, rc_all))
|
||||
return rc_all
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user