修复CI门禁三因:runner掉线致12个job零日志、L0探测回归全灭、gitlint在CI从不执行
1) runner 掉线:run 16~21 共 12 个 job 零日志。gitea-runner v4 的
daemon 无服务支持、首次 Declare 失败即退出进程,Gitea/机器重启
一次 CI 就静默停摆;叠加 cancel-in-progress 后症状是"任务凭空
消失"。新增 scripts/ci_runner_bootstrap.ps1:等 Gitea 就绪 +
崩溃自拉起看门狗 + 开机计划任务 + -SetEnv 写 MACHINE 级 DTU_*。
2) L0 自 33a4aa4 起在 runner 上必挂:Python 候选被收窄成 PATH python
(msys64,无 pre-commit) + 仓库 .venv(gitignore),cppcheck 目录兜底
亦被删,而机器级 DTU_* 从未设置。探测收进 ci_find_python.ps1
(两 job 共用、可离线自测、恢复机器级安装兜底)。
3) gitlint 声明 stages:[commit-msg],而 --all-files 只跑 pre-commit
stage → 17 个钩子定义 CI 只报 16 个且全绿。新增
ci_gitlint_range.ps1 逐条校验本次范围,ci_local.py 增 L0b 段。
另:GITHUB_ENV/PATH/OUTPUT 写入显式 -Encoding ascii(PS 5.1 的
Out-File -Append 默认 UTF-16LE,会把带 NUL 的行追加进 UTF-8 文件)
This commit is contained in:
+126
-105
@@ -43,6 +43,34 @@
|
||||
# 那些由 Gitea 按 UTF-8 读,不喂给 PowerShell,安全)。
|
||||
# ⚠️ 与 AGENTS.md「字符串字面量必须纯 ASCII」同源;改脚本时务必守住。
|
||||
#
|
||||
# 🔴🔴 工具链探测必须【可离线自测 + 保留机器级安装兜底】(2026-10-11 第三次实测修正):
|
||||
# `33a4aa4`(DTU_* 变量改造)把 Python 探测内联进本文件,并把候选**收窄成两个**
|
||||
# (PATH python + 仓库 .venv)——而 runner 上这两个都是死路:
|
||||
# · SYSTEM 账号的 PATH python = `C:\msys64\ucrt64\bin\python.exe`,**没有** pre-commit;
|
||||
# · 仓库 `.venv` 已 gitignore,runner 的全新工作区里**根本不存在**。
|
||||
# 唯一可用的机器级解释器(`C:\ProgramData\anaconda3\python.exe`)不再被探测,而文档
|
||||
# 要求的机器级 `DTU_PRECOMMIT_PYTHON` 从未设置(Machine 作用域一个 DTU_* 都没有)
|
||||
# → run 14/15 的 L0 **全部死在"环境自检",一个钩子都没跑**。cppcheck 同理:
|
||||
# 旧写法"不在 PATH 就把 C:\Program Files\Cppcheck 加进 PATH"的兜底也被删了。
|
||||
# **故探测逻辑搬进 `scripts/ci_find_python.ps1`**(两个 job 共用、可离线跑、失败时
|
||||
# 打印全部候选),并恢复常见机器级安装位置兜底;`DTU_*` 仍是最高优先级、
|
||||
# 且"显式设置就必须有效、不静默回退"的语义不变。
|
||||
#
|
||||
# 🔴 gitlint 属 commit-msg stage,`--all-files` **永远跑不到它**(2026-10-11 审计):
|
||||
# `.pre-commit-config.yaml` 定义 17 个钩子,CI 只报 16 个(`passed=15 skipped=1`)——
|
||||
# 缺的正是 gitlint:其钩子声明 `stages: [commit-msg]`,而 `pre-commit run --all-files`
|
||||
# 只跑 pre-commit stage。即 AGENTS.md 的"单行中文标题 <100 字符"门禁**本地有、CI 无**,
|
||||
# 且表现为"全绿"而非失败,所以一直没人发现。现由 `scripts/ci_gitlint_range.ps1`
|
||||
# 逐条校验本次 push/PR 引入的提交(消息文件必须写在**仓库内**且 **UTF-8 无 BOM**:
|
||||
# 跨盘符会被 pre-commit 拒绝 `path is on mount 'C:', start on mount 'D:'`,
|
||||
# BOM 会占掉标题 1 个字符、扭曲 100 上限)。
|
||||
#
|
||||
# 🔴 写 `GITHUB_ENV` / `GITHUB_PATH` / `GITHUB_OUTPUT` 必须显式 `-Encoding ascii`
|
||||
# (2026-10-11 实测):PS 5.1 的 `Out-File -Append` **默认 UTF-16LE**——往 runner
|
||||
# 用 UTF-8 建的同一个文件里追加,落盘就是 `B\x00B\x00B\x00=\x001\x00` 这种带 NUL 的行
|
||||
# (实测字节:`42 00 42 00 42 00 3D 00 31 00`)。本文件写入的这些键值全是 ASCII,
|
||||
# 故统一显式 ascii;`GITHUB_STEP_SUMMARY` 是 Markdown,保持 utf8。
|
||||
#
|
||||
# ⚠️ 前置(部署到 Gitea 时需确认):
|
||||
# - 该 Gitea 实例已启用 Actions:`[actions] ENABLED = true`(site admin 配置)
|
||||
# - 已注册一个 **Windows** runner(labels 含 `windows`),且装有:
|
||||
@@ -97,98 +125,73 @@ jobs:
|
||||
# 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.
|
||||
# Probe order per the DTU_* env var plan (kept in sync with
|
||||
# scripts/ci_local.py):
|
||||
# DTU_PRECOMMIT_PYTHON (explicit; invalid value = hard error)
|
||||
# -> PATH python -> repo .venv
|
||||
# Discovery lives in scripts/ci_find_python.ps1 so that
|
||||
# * both jobs share ONE implementation,
|
||||
# * it can be run (and its failure list inspected) offline,
|
||||
# * the well-known machine-wide install locations stay probed.
|
||||
# The previous inline 2-candidate probe is what killed every L0 run
|
||||
# since 33a4aa4 -- see the file header.
|
||||
#
|
||||
# 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.
|
||||
$py = $null
|
||||
$saved = $ErrorActionPreference
|
||||
$envPy = $env:DTU_PRECOMMIT_PYTHON
|
||||
if ($envPy) {
|
||||
if (-not (Test-Path $envPy)) {
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Write-Host "::error::DTU_PRECOMMIT_PYTHON is set but does not exist: $envPy"
|
||||
Write-Host "::error::fix the variable (full path to a python.exe with pre-commit), or unset it to use auto-discovery"
|
||||
exit 1
|
||||
}
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $envPy -s -m pre_commit --version 2>&1 | Out-Null
|
||||
$rc = $LASTEXITCODE
|
||||
$ErrorActionPreference = $saved
|
||||
if ($rc -ne 0) {
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Write-Host "::error::DTU_PRECOMMIT_PYTHON does not have a SYSTEM-visible pre-commit: $envPy"
|
||||
exit 1
|
||||
}
|
||||
$py = $envPy
|
||||
} else {
|
||||
$candidates = @(
|
||||
(Get-Command python -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source),
|
||||
(Join-Path $repoRoot '.venv\Scripts\python.exe')
|
||||
)
|
||||
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'
|
||||
# The script prints diagnostics with Write-Host and emits ONLY the
|
||||
# chosen path on the success stream, so capturing it is exact.
|
||||
# It also appends PYTHON=<path> to GITHUB_ENV (as ASCII) for later
|
||||
# steps, which is why $env:PYTHON works in the steps below.
|
||||
$py = & './scripts/ci_find_python.ps1' -NeedPreCommit -WriteEnv
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
if (-not $py) {
|
||||
Write-Host "::error::no Python with a SYSTEM-visible pre-commit (DTU_PRECOMMIT_PYTHON not set; tried PATH python and repo .venv)"
|
||||
Write-Host "::error::pre-commit is probably installed only in a USER site (invisible to SYSTEM)."
|
||||
Write-Host "::error::fix: set a MACHINE-level DTU_PRECOMMIT_PYTHON for the runner service and restart it, or install pre-commit globally for the SYSTEM account (see the Gitea runner deployment guide in docs/)"
|
||||
exit 1
|
||||
Write-Host "::error::ci_find_python.ps1 succeeded but returned no path"
|
||||
exit 2
|
||||
}
|
||||
Write-Host "using python: $py"
|
||||
& $py --version
|
||||
& $py -s -m pre_commit --version
|
||||
"PYTHON=$py" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
$py = "$py".Trim()
|
||||
|
||||
# --- cppcheck -----------------------------------------------------
|
||||
# Order per the DTU_* plan: DTU_CPPCHECK (explicit; invalid = hard
|
||||
# error, no fallback) -> PATH (Get-Command cppcheck). No fixed
|
||||
# install-dir guess: a USER-scope PATH entry is invisible to SYSTEM,
|
||||
# so on the runner set a MACHINE-level DTU_CPPCHECK if cppcheck is
|
||||
# not on SYSTEM's PATH.
|
||||
# Order: DTU_CPPCHECK (explicit; invalid = hard error, no fallback)
|
||||
# -> PATH -> the standard install directory.
|
||||
#
|
||||
# The directory fallback is NOT cosmetic: the runner service runs as
|
||||
# SYSTEM, whose PATH has no Cppcheck entry (a USER-scope PATH entry is
|
||||
# invisible to it), and no machine-level DTU_CPPCHECK is set on a
|
||||
# stock runner. Dropping this fallback in 33a4aa4 is what made L0 die
|
||||
# on cppcheck as well, right after the Python probe had been fixed.
|
||||
$found = $null
|
||||
$envCpp = $env:DTU_CPPCHECK
|
||||
if ($envCpp) {
|
||||
if (-not (Test-Path $envCpp)) {
|
||||
Write-Host "::error::DTU_CPPCHECK is set but does not exist: $envCpp"
|
||||
Write-Host "::error::fix the variable (full path to cppcheck.exe), or unset it to use auto-discovery"
|
||||
exit 1
|
||||
}
|
||||
$dir = Split-Path -Parent $envCpp
|
||||
$env:Path = "$env:Path;$dir"
|
||||
$dir | Out-File -FilePath $env:GITHUB_PATH -Append
|
||||
Write-Host "using cppcheck from DTU_CPPCHECK: $envCpp"
|
||||
& $envCpp --version
|
||||
$found = $envCpp
|
||||
Write-Host "using cppcheck from DTU_CPPCHECK: $found"
|
||||
} else {
|
||||
$cpp = Get-Command cppcheck -ErrorAction SilentlyContinue
|
||||
if (-not $cpp) {
|
||||
Write-Host "::error::runner missing cppcheck (not on SYSTEM PATH) -- set a machine-level DTU_CPPCHECK, or add its directory to the machine PATH, then re-run"
|
||||
exit 1
|
||||
$cmd = Get-Command cppcheck -ErrorAction SilentlyContinue
|
||||
if ($cmd) {
|
||||
$found = $cmd.Source
|
||||
Write-Host "found cppcheck on PATH: $found"
|
||||
} else {
|
||||
$pf = $env:ProgramFiles
|
||||
$pf86 = ${env:ProgramFiles(x86)}
|
||||
if (-not $pf) { $pf = 'C:\Program Files' }
|
||||
if (-not $pf86) { $pf86 = 'C:\Program Files (x86)' }
|
||||
foreach ($cand in @(
|
||||
(Join-Path $pf 'Cppcheck\cppcheck.exe'),
|
||||
(Join-Path $pf86 'Cppcheck\cppcheck.exe'))) {
|
||||
if (Test-Path $cand) { $found = $cand; break }
|
||||
}
|
||||
}
|
||||
Write-Host "found cppcheck: $($cpp.Source)"
|
||||
cppcheck --version
|
||||
}
|
||||
if (-not $found) {
|
||||
Write-Host "::error::runner missing cppcheck (not on SYSTEM PATH, not in the standard install dir)"
|
||||
Write-Host "::error::set a machine-level DTU_CPPCHECK (see scripts/ci_runner_bootstrap.ps1 -SetEnv) or add its directory to the machine PATH, then re-run"
|
||||
exit 1
|
||||
}
|
||||
$cppDir = Split-Path -Parent $found
|
||||
$env:Path = "$env:Path;$cppDir"
|
||||
# ASCII on purpose: Out-File's PS 5.1 default is UTF-16LE, and other
|
||||
# steps append to this same file -- mixed encodings corrupt it.
|
||||
$cppDir | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding ascii
|
||||
& $found --version 2>&1 | Out-Host
|
||||
|
||||
Write-Host "runner environment check passed"
|
||||
|
||||
@@ -196,6 +199,34 @@ jobs:
|
||||
shell: powershell
|
||||
run: "& $env:PYTHON -s -m pre_commit install --install-hooks"
|
||||
|
||||
- name: 提交信息门禁(gitlint,commit-msg stage)
|
||||
shell: powershell
|
||||
env:
|
||||
DTU_BASE_SHA: ${{ github.event.before }}
|
||||
DTU_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
DTU_HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
# Pure ASCII only (see file header).
|
||||
#
|
||||
# WHY THIS STEP EXISTS: gitlint is a `commit-msg` stage hook, and
|
||||
# `pre-commit run --all-files` only runs the `pre-commit` stage -- so
|
||||
# the commit-message rule was enforced locally and NEVER here, while
|
||||
# the job still reported green (17 hooks defined, 16 reported).
|
||||
# pre-commit has no notion of "the commits of this push", so the
|
||||
# script feeds each commit message to the hook one at a time.
|
||||
#
|
||||
# Range: PR -> base..head; push -> event.before..sha, falling back to
|
||||
# the newest commits when `before` is all-zero (new branch) or
|
||||
# unreachable (force push). Merge commits are skipped (git writes them).
|
||||
$base = $env:DTU_PR_BASE_SHA
|
||||
if (-not $base) { $base = $env:DTU_BASE_SHA }
|
||||
& './scripts/ci_gitlint_range.ps1' -BaseSha $base -HeadSha $env:DTU_HEAD_SHA `
|
||||
-MaxCommits 50 -PythonExe $env:PYTHON
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "::error::commit message gate failed (gitlint) -- see the per-commit output above"
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
- name: 决定跳过哪些钩子(Keil 依赖)
|
||||
id: skip
|
||||
shell: powershell
|
||||
@@ -213,14 +244,14 @@ jobs:
|
||||
$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
|
||||
"value=" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii
|
||||
"sizegate=on" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii
|
||||
"> 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
|
||||
"value=mem-numbers" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii
|
||||
"sizegate=off" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii
|
||||
"> :warning: Volume gate (mem-numbers): **NOT RUN** -- no `.map` (no Keil build)." |
|
||||
Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
|
||||
}
|
||||
@@ -339,10 +370,10 @@ jobs:
|
||||
}
|
||||
}
|
||||
if ($found) {
|
||||
"value=yes" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"value=yes" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii
|
||||
} 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
|
||||
"value=no" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii
|
||||
}
|
||||
|
||||
- name: 运行主机侧单测
|
||||
@@ -350,28 +381,18 @@ jobs:
|
||||
if: steps.msvc.outputs.value == 'yes'
|
||||
shell: powershell
|
||||
run: |
|
||||
# Resolve a working Python first (SYSTEM's PATH python may be unusable).
|
||||
# Order per the DTU_* plan: DTU_PRECOMMIT_PYTHON (explicit; invalid =
|
||||
# hard error) -> PATH python -> repo .venv.
|
||||
$repoRoot = (Get-Location).Path
|
||||
$py = $null
|
||||
$envPy = $env:DTU_PRECOMMIT_PYTHON
|
||||
if ($envPy) {
|
||||
if (-not (Test-Path $envPy)) {
|
||||
Write-Host "::error::DTU_PRECOMMIT_PYTHON is set but does not exist: $envPy"
|
||||
exit 1
|
||||
}
|
||||
$py = $envPy
|
||||
}
|
||||
# Resolve a working Python with the SAME shared probe as L0 (no
|
||||
# -NeedPreCommit: these harnesses are stdlib-only). The old inline
|
||||
# probe here had the same 2-candidate blind spot as L0 -- it only
|
||||
# survived by luck, because the stdlib tests also run on the msys64
|
||||
# python that happens to be on SYSTEM's PATH.
|
||||
$py = & './scripts/ci_find_python.ps1' -WriteEnv
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
if (-not $py) {
|
||||
foreach ($c in @(
|
||||
(Get-Command python -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source),
|
||||
(Join-Path $repoRoot '.venv\Scripts\python.exe')
|
||||
)) {
|
||||
if ($c -and (Test-Path $c)) { $py = $c; break }
|
||||
}
|
||||
Write-Host "::error::ci_find_python.ps1 succeeded but returned no path"
|
||||
exit 2
|
||||
}
|
||||
if (-not $py) { Write-Host "::error::no python found (set DTU_PRECOMMIT_PYTHON or fix the runner PATH)"; exit 1 }
|
||||
$py = "$py".Trim()
|
||||
Write-Host "using python: $py"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path 'ci-artifacts' | Out-Null
|
||||
@@ -423,8 +444,8 @@ jobs:
|
||||
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
|
||||
"count=$runnerCount" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii
|
||||
"agg=$agg" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii
|
||||
exit $fail
|
||||
|
||||
- name: 归档 L0.5 日志(供下载/排障)
|
||||
|
||||
Reference in New Issue
Block a user