Files
STM32F4-Base/.gitea/workflows/ci.yml
T
wandering1 f3befaed08
CI / L0 静态门禁 (pre-commit) (push) Successful in 58s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 19s
CI:补产物存档与结果汇总,明示体积门禁盲区(S3+S14)
2026-10-02 11:55:31 +08:00

381 lines
19 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.
# 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 的机器)则照常跑。
#
# 🔴 门禁盲区声明(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 实测修正):
# `pwsh` = PowerShell 7,**不是 Windows 自带**;本机 runner 只有
# Windows PowerShell 5.1(`C:\Windows\System32\WindowsPowerShell\v1.0\`),
# 用 `shell: pwsh` 会直接报 `Cannot find: pwsh in PATH` → job 失败(跑不到任何业务逻辑)。
# `shell: powershell` 在任意 Windows 上都存在。
#
# 🔴🔴 `run:` 脚本内容必须【纯 ASCII】(2026-10-02 第二次实测修正):
# runner 把每个 `run:` 块写入临时 `.ps1`(`...\act\workflow\N.ps1`)再交给
# Windows PowerShell 5.1 执行,而该临时文件**无 BOM** → PS 5.1 按 **GBK/CP936** 解码。
# 脚本里的 UTF-8 中文字节会与相邻引号错位 → 报
# `TerminatorExpectedAtEndOfString` / `字符串缺少终止符: "` / `缺少右"}"`
# (报错行号正是含中文的那一行,极易误判为逻辑错误)。
# **故本文件所有 `run:` 块内零非 ASCII**(中文仅出现在 YAML 的 name:/注释里——
# 那些由 Gitea 按 UTF-8 读,不喂给 PowerShell,安全)。
# ⚠️ 与 AGENTS.md「字符串字面量必须纯 ASCII」同源;改脚本时务必守住。
#
# ⚠️ 前置(部署到 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: powershell
run: |
# NOTE: keep every run: block pure ASCII. The runner writes this block
# to a BOM-less .ps1 executed by Windows PowerShell 5.1, which decodes
# it as GBK -- non-ASCII bytes there break string quoting.
$repoRoot = (Get-Location).Path
# --- pick a Python that actually has pre-commit -------------------
# The runner runs as SYSTEM, whose PATH is NOT the dev user's PATH.
# On this box SYSTEM's `python` is C:\msys64\...\python.exe (3.14.5,
# no pre-commit). Probe the same order as scripts/ci_local.py:
# PATH python -> .venv -> anaconda3
#
# CAUTION 1: a failing native command writes to stderr; with
# $ErrorActionPreference='Stop' that would abort the whole script
# (NativeCommandError) before the next candidate is tried. So relax
# the preference around each probe, and restore it afterwards.
#
# CAUTION 2: pre-commit may be installed in the USER site of some
# account (e.g. %APPDATA%\Roaming\Python\...), which SYSTEM cannot
# see at all -- no python.exe path can help then. Probe with `-s`
# (no user-site) so we only accept an interpreter that works for
# SYSTEM, and say so clearly when none is found.
$candidates = @(
(Get-Command python -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source),
(Join-Path $repoRoot '.venv\Scripts\python.exe'),
'C:\ProgramData\anaconda3\python.exe'
)
$py = $null
$saved = $ErrorActionPreference
foreach ($c in $candidates) {
if (-not $c) { continue }
if (-not (Test-Path $c)) { continue }
$ErrorActionPreference = 'Continue'
& $c -s -m pre_commit --version 2>&1 | Out-Null
$rc = $LASTEXITCODE
$ErrorActionPreference = $saved
if ($rc -eq 0) { $py = $c; break }
}
$ErrorActionPreference = 'Stop'
if (-not $py) {
Write-Host "::error::no Python with a SYSTEM-visible pre-commit. Tried: $($candidates -join ' | ')"
Write-Host "::error::pre-commit is probably installed only in a USER site (invisible to SYSTEM)."
Write-Host "::error::fix (elevated): powershell -ExecutionPolicy Bypass -File D:\Docker\act_runner\install_precommit_global.ps1"
exit 1
}
Write-Host "using python: $py"
& $py --version
& $py -s -m pre_commit --version
"PYTHON=$py" | Out-File -FilePath $env:GITHUB_ENV -Append
# --- cppcheck -----------------------------------------------------
# cppcheck is often installed with a USER-scope PATH entry, which
# SYSTEM cannot see. Fall back to its default install dir.
$cpp = Get-Command cppcheck -ErrorAction SilentlyContinue
if ($cpp) {
Write-Host "found cppcheck: $($cpp.Source)"
} elseif (Test-Path 'C:\Program Files\Cppcheck\cppcheck.exe') {
$dir = 'C:\Program Files\Cppcheck'
$env:Path = "$env:Path;$dir"
$dir | Out-File -FilePath $env:GITHUB_PATH -Append
Write-Host "cppcheck not on PATH; added $dir (User-scope PATH is invisible to SYSTEM)"
} else {
Write-Host "::error::runner missing cppcheck -- install it, then re-run"
exit 1
}
cppcheck --version
Write-Host "runner environment check passed"
- name: 安装 pre-commit 钩子到本仓库
shell: powershell
run: "& $env:PYTHON -s -m pre_commit install --install-hooks"
- name: 决定跳过哪些钩子(Keil 依赖)
id: skip
shell: powershell
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 "::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)
shell: powershell
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" }
$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 则跳过)
# ------------------------------------------------------------------
l05-host-unit-tests:
name: L0.5 主机侧单测 (MSVC)
runs-on: windows
steps:
- uses: actions/checkout@v4
- name: 探测 MSVC(VS BuildTools / Community 等)
id: msvc
shell: powershell
run: |
# Keep in sync with VS_ROOTS in test/host/common/unit_test_harness.py
$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 "found cl.exe: $($cl.FullName)"; break }
}
}
if ($found) {
"value=yes" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
} else {
Write-Host "::warning::runner has no MSVC (VS C++ toolchain) -- skipping L0.5. Install VS BuildTools and re-run."
"value=no" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
}
- name: 运行主机侧单测
id: l05
if: steps.msvc.outputs.value == 'yes'
shell: powershell
run: |
# Resolve a working Python first (SYSTEM's PATH python may be unusable).
$repoRoot = (Get-Location).Path
$py = $null
foreach ($c in @(
(Get-Command python -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source),
(Join-Path $repoRoot '.venv\Scripts\python.exe'),
'C:\ProgramData\anaconda3\python.exe'
)) {
if ($c -and (Test-Path $c)) { $py = $c; break }
}
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
if (-not $runners) {
Write-Host "::warning::no test/host/**/run_unit_test.py found -- skipping"
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 "================================"
$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