docs(CI): §5.3 重写并记录覆盖率工具链落地,更正 §1.2.1 与本会话先前的错判
补上今天两件 CI 工作的落地记录,并把错误结论改对(历史行不动,只在上方加行)。 新增 §4.4「覆盖率工具链(mscoverage)与 VS2026 BuildTools」。为什么只能装: 那个 exe 没有免安装的独立发行版——实测 NuGet 两个候选包, Microsoft.Testing.Extensions.CodeCoverage 18.12.0 的 109 个条目里 .exe 数为 0, Microsoft.CodeCoverage 18.11.0 只有老版 CodeCoverage.exe(CLI 语法还不同), 所以它只随 Visual Studio 本体分发。安装入口是 aka.ms/vs/18/stable/ vs_buildtools.exe(release 那条短链不存在,会掉到"未找到"页);组件 ID Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage 由 18.11 的 VisualStudio.vsman 解析得到(清单里唯一引用 DynamicCodeCoverage 与 CodeCoverage.Console.Targeted 的组)。含可直接粘贴的三条管理员命令、-Force 陷阱、Restart-Service Schedule 判据,以及「装在非默认盘 = 门禁与 CI 都看不见」 的成因:Find-MsvcCl 与 _default_vs_roots 都只由 %ProgramFiles% 派生根。 §5.3 整节重写:本仓库已移除 uses:,记录六项实测(A/B/C/D/E/F,其中 E 是执行 从 ci.yml 抽出的正文、测的就是文件里那份字节)与两条自查命令,并明标两条 **未证边界**:PR 的 refs/pull/<n>/merge 兜底在本实例无法证实(0 个 PR、 ls-remote 该命名空间为空);Basic 令牌那一支本地验证不了(公开仓库匿名即过, 伪造令牌只能证明坏的会被拒)。旧版那段"可直接抄"的示例标为禁抄:shell: pwsh 在 runner 上 PATH 无 pwsh 会直接失败,--depth 1 让 base..head 报 unknown revision,两条都会让下一个照抄的人多红一轮。 更正两处过时/错误说法:① §1.2.1 表第三行——自定义 DEFAULT_ACTIONS_URL 在 1.25.4 已无可用取值,旧写法会把排障的人引向一个不存在的开关;② 我自己先前 写的「旧探测会把机器级变量覆回 C 盘 14.29」——本机实情是 C 盘 VS2019 已于 10-27 01:00 卸载(C 盘 x64 cl.exe 实测 0 个),旧探测的行为是「找不到 → 只 Warn → 不写」,于是变量一直指着失效路径。§7 的 checkout 行相应改为「看见此 症状=有人把 uses: 加回来了」。 另:口径文档 §7 第 8 条加实测补注(「CI runner 与本机同一台」对量基线那台不 成立,本机当日 vswhere 只有 VS2019);新增 remote-ci-ops skill 并接上 AGENTS 与 docs/CI/README 两处指针;指南与 README 各加变更记录。纯文档,按 workflow 的 paths-ignore 不触发 CI。
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
---
|
||||
name: remote-ci-ops
|
||||
description: 远程 CI(Gitea Actions + 本机 Windows 自建 runner)运维闭环:runner 保活与抢锁清理、"没有 run / 任务消失"的四因定位、红因三分法(job 没跑起来 / runner 缺环境 / 真门禁失败)、无令牌时用 Gitea 的 MySQL 直查 runner 在线与历次 run/job 时长与红绿史(含 status 枚举定死办法)、用 gitea-runner.exe generate-config 枚举 runner 真实配置键而不猜键名、本地对照日志落 ci-artifacts/、门禁数字口径、gitlint 在 CI 的两种空转(含 runner 无 git 身份致 exit 254 假失败)、PS 5.1 原生 stderr 与编码陷阱、改 workflow / 门禁脚本的红线与改后验收清单。背景与全部实测细节在 docs/CI/远程CI完整指南.md,本 skill 只给操作顺序与判据。
|
||||
whenToUse: CI 没跑、run 被取消或零日志、页面报的失败时长/红绿史跟本地或日志对不上、"这 job 到底跑没跑起来"、门禁假绿/假红、runner 显示 online 却每 10 秒重启、自建 runner 有哪些可用配置项(cache 之类,不确定键名)、要改 .gitea/workflows/ci.yml 或 scripts/ci_*.ps1|py、或没有 API 令牌却要判断 CI 状态时。
|
||||
---
|
||||
|
||||
# remote-ci-ops — 远程 CI 运维闭环
|
||||
|
||||
**权威细节**:`docs/CI/远程CI完整指南.md`(部署、全部实测坑、章节导航),唯一入口 `docs/CI/README.md`。
|
||||
本 skill 只回答两件事:**出问题时按什么顺序查**、**动手前后各验什么**。同一事实不在两处详述。
|
||||
|
||||
> **TODO(待同步回指南)**:`docs/CI/远程CI完整指南.md` §6.1 目前只写 `action_runner.last_online` 的在线核验与 `mysql.exe` CLI;
|
||||
> 本轮把同一通道扩到 `action_run` / `action_run_job` / `action_task` 的历史与时长,且实际驱动是
|
||||
> **pymysql**(不是 mysql.exe)。下面 §4.3 / §7 是本轮实测口径,落定后应并回`docs/CI/远程CI完整指南.md` §6.1 与 §2.3。
|
||||
|
||||
**本机事实**(变了就改指南,别改这里):runner `win-stm32-ci`(`gitea-runner` v4.0.1,labels `[windows]`,与 `runs-on: windows` 匹配);目录 `D:\Docker\act_runner\`;Gitea 1.25.4 监听 `127.0.0.1:3001`,数据库 MySQL `127.0.0.1:3306`/gitea(凭据在 `D:\Docker\Gitea\custom\conf\app.ini` 的 `[database]`,**口令不进脚本、不进报告**);本仓库 `repository.id = 10`(`wandering1/stm32f4-base`,另一仓 stm32f103 = 13);**CI 只跑这一台机器**,与其他机器无关。
|
||||
|
||||
## 0. 先分层,别一上来读 workflow
|
||||
|
||||
**分层之前先做 §1**:一次红很可能压根不是"门禁内容"问题,直接读 workflow 会白绕一圈。
|
||||
|
||||
| 层 | 就位判据 | 不在位的症状 |
|
||||
|---|---|---|
|
||||
| ① 服务端 Actions 开关 | `docs/CI/远程CI完整指南.md` §1.1 | push 后 Actions 页**完全空白** |
|
||||
| ② runner 在线且工具链对 SYSTEM 可见 | `docs/CI/远程CI完整指南.md` §4.1 + 下面 §4 | run 建了但一直排队;或 L0 死在"环境自检"、一个钩子都没跑 |
|
||||
| ③ workflow 与门禁 | `scripts/ci_local.py` 本地全绿 | 具体 job 红/绿与本地不一致 |
|
||||
|
||||
**本地永远先跑这个**(runner 掉线也不影响它,是 L0 的唯一保障;日志落法见 §5):
|
||||
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py --keep-going # L0 + L0b + L0.5
|
||||
```
|
||||
|
||||
## 1. 第一步:这个 job 到底跑没跑起来(红因三分法)
|
||||
|
||||
**只看一条判据**:runner 侧那份 job 日志(`D:\Docker\act_runner\log\job\*-task-NN.log`)里
|
||||
**有没有出现任何一条钩子名(`cppcheck.......Passed`)或 `pre-commit hooks -- passed=A failed=B skipped=C` 汇总行**。
|
||||
|
||||
| 类 | 判据(日志里的证据行) | 结论与动作 |
|
||||
|---|---|---|
|
||||
| **A. job 没跑起来** | checkout 步骤报拉不到 `actions/checkout` / 连 `github.com` 超时,**通篇零钩子输出** | 门禁内容无罪。内网无公网时的三种处置在 `docs/CI/远程CI完整指南.md` §5.3(推荐零依赖纯 git checkout)。**先修它再谈别的**,否则后面所有"红"都不可读 |
|
||||
| **B. runner 机器缺环境** | `::warning::runner 未安装 MSVC`、`no Python with a SYSTEM-visible pre-commit`、`runner missing cppcheck`、`test-coverage` 因缺 **mscoverage** 组件而 FAIL(按用户裁定缺件即 FAIL,不 skip) | 也是"非代码问题",但**别改成 skip 蒙过去**:按`docs/CI/远程CI完整指南.md` §4.1/§4.2 补机器级工具链或 `DTU_*`,改完重启 runner |
|
||||
| **C. 真门禁失败** | 钩子清单完整且 `failed>0` 或某条 `Failed`,**且 §5 的本地同命令复现同样的红** | 这才轮到读 workflow / 门禁脚本,按失败钩子逐条定位 |
|
||||
|
||||
**顺序不能反**:A/B 两类在本地都是"全绿",用"本地绿 + CI 红"直接推"环境问题"也推不出是 A 还是 B——只有钩子输出能把它们分开。本轮实测:页面报的红看着像门禁失败,实际是 A(checkout 联网超时),而 A 修好之后 L0 还会再红两次——一条 C、两条 B。
|
||||
|
||||
## 2. 症状 → 一条判据 → 动作
|
||||
|
||||
| 症状 | 判据(跑这一条就够) | 结论 / 动作 |
|
||||
|---|---|---|
|
||||
| Actions 页空白、无 run | 看改了啥 + 怎么推的 | 四因:①服务端开关未开 ②workflow 没推到远程 ③**纯文档**(`paths-ignore: docs/**`、`**/*.md`)④**新建分支的首次 push**——`before` 是全零 SHA,变更文件集算不出来被 `paths-ignore` 滤空。④ 的实测:建分支那次 push 无 run,紧接着一次普通 push 立刻出 task;绕法 `workflow_dispatch` 或再推一个非文档提交 |
|
||||
| **页面报的时长/红数与本地或日志对不上** | §4.3 查 `action_run`/`action_run_job` 的 created/started/stopped | **页面时长不可照抄**。本轮实测页面写 `Failing after 6m49s`,库里 41 次 run **最长 ≈4m33s**,run 41 总时长 95 s(L0 45 s / L0.5 43 s)——**没有任何一条记录对得上 6m49s**(多半掺了排队/重试/页面自身口径)。以库里的 `total=stopped-created`、`run=stopped-started`、`queue=started-created` 三个数为准 |
|
||||
| run 全 **cancelled**、零日志 | `& '.\scripts\ci_runner_bootstrap.ps1' -Status` | runner 掉线;且 `ci.yml` 的 `concurrency.cancel-in-progress: true` 会把排队 run 被下次 push 取消 → **症状是"任务消失"而不是"任务失败"**(历史上 16–21 连续 12 个 job 零日志即此) |
|
||||
| runner 显示 **online** 却每 10 秒重启、日志暴涨(实测 ~20 MB/天) | daemon 日志出现 `another gitea-runner process is already using ".runner"` | **双看门狗抢锁**:`Unregister-ScheduledTask` **不会**停掉旧实例起的进程 → 孤儿看门狗养着旧 daemon → 它持 `.runner.lock` → 新任务里的 daemon 起不来,而**服务端仍显示 online**(旧 daemon 在服务)。修:管理员 `-StartNow`(它**先杀看门狗、再杀 daemon**,顺序反了会被 10 秒内复活) |
|
||||
| L0 失败但**本地同一条命令全绿**,日志 `exit code: 254` + `Missing git configuration: please set user.name` | 看是不是 254 | **不是提交信息问题**:`actions/checkout` 把 HOME 指向每次任务的临时目录,SYSTEM 无全局 git 身份 → gitlint 在读规则**之前**就配置错误退出。已由 `scripts/ci_gitlint_range.ps1` 用 `GIT_CONFIG_*` 注入一次性身份修掉;**254 = 门禁没跑起来(exit 2),1 = 信息真违规(exit 1)**,别把两者混成一句 |
|
||||
| 钩子条数对不上 | `grep -c 'id:' .pre-commit-config.yaml` | **报告数 = 定义数 − 1**(gitlint 属 `commit-msg` stage,`--all-files` 跑不到且表现为全绿 = 静默空转)。增删钩子后重算这个差值 |
|
||||
| `test-coverage` 恒 `[ENV] exit 2`(缺 `Microsoft.CodeCoverage.Console.exe`),或换 VS 世代后大面积假红 | `& '.venv\Scripts\python.exe' scripts/check_test_coverage.py --list` 看它探到几个根;再看机器级 `DTU_MSVC_CL` 指的是哪套 cl | 该 exe **只随 VS 分发**(NuGet 实测拿不到);装在非默认盘还要显式指 `DTU_MSVC_CL`。安装入口 + 组件 ID + 三条管理员命令在 `docs/CI/远程CI完整指南.md` §4.4 |
|
||||
| `mem-numbers` 报"文档与 .map 不一致",但**没改过代码也没改过那篇文档** | `.map` 的 mtime 是否早于最近一次切分支 | **跨分支假象**:`.map` 是 gitignored 的 Keil 产物,切分支后它仍属于另一分支的代码。重新 `@build` 即一致,不要去改文档数字 |
|
||||
| 明明设了 `DTU_KEIL_UV4` 却报 `UV4.exe not found` | `[Environment]::GetEnvironmentVariable('DTU_KEIL_UV4','User')` | 会话没继承用户级变量。**回填时别按"进程里已有的变量名"枚举**——Machine 轮会把只在 User 定义的变量置空(PS 赋空值 = 删除),User 轮就再也看不到它。正确:名字取 Machine/User **并集**,且只在取到值时写入 |
|
||||
| 日志里出现 `gitea-runner.exe : …` + 4 行 `CategoryInfo`/`FullyQualifiedErrorId` | 实测 2 行真日志写成 8 行 | PS 5.1 的 `2>&1` 把每条原生 stderr 包成 ErrorRecord。修:`2>&1 \| ForEach-Object { "$_" }`(等价于原始行,且 `$LASTEXITCODE` 不受影响)。详见`docs/CI/远程CI完整指南.md` §7.3 |
|
||||
| 日志文件半 UTF-8 半乱码 | 文件头是否 `FF FE` | `>` / `*>>` 在 PS 5.1 = `Out-File` = **默认 UTF-16LE**;与 `Add-Content -Encoding ascii` 混用会一个文件两种编码 |
|
||||
|
||||
## 3. runner 保活与注册(唯一入口脚本)
|
||||
|
||||
```powershell
|
||||
# 免管理员预演,先看它会做什么
|
||||
& '.\scripts\ci_runner_bootstrap.ps1' -DryRun
|
||||
# 正式:写机器级 DTU_* + 装看门狗任务 + 立即起(需管理员)
|
||||
powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -SetEnv -StartNow
|
||||
# 体检(非管理员会明说哪些项读不到,而不是谎报)
|
||||
& '.\scripts\ci_runner_bootstrap.ps1' -Status
|
||||
```
|
||||
|
||||
要点(细节与理由见 `docs/CI/远程CI完整指南.md` §2.4):
|
||||
|
||||
- **不能只起 daemon**:v4 无服务支持,且**首次 Declare 失败就退出** → 一次 Gitea 重启就永久静默掉线。
|
||||
- **不能用 Windows 服务**(服务化方案已被实测推翻,见 `docs/CI/远程CI完整指南.md` §3.0);任务计划程序的"失败后重启"**永不触发**,因为 daemon 的退出是**正常退出**。
|
||||
- 任务对象:`-AtStartup` + `Delay=PT1M`、`ExecutionTimeLimit` 为零、`RestartCount 999`、`MultipleInstances IgnoreNew`、`$settings.StartWhenAvailable=$true`——⚠ `StartWhenAvailable` 是**任务设置**属性,挂到 trigger 上会抛异常(曾因此注册崩溃)。
|
||||
- `0x41301` = `SCHED_S_TASK_RUNNING`(不是失败);`-AtStartup` 的任务"下次运行时间"为空是正常的。
|
||||
- 改完 bootstrap 脚本里的启动器模板,**磁盘上的看门狗要下次 `-StartNow` 或开机才刷新**——不必为此专门重启 runner。
|
||||
|
||||
## 4. 没有 API 令牌时怎么判断状态
|
||||
|
||||
匿名调 `/api/v1/repos/.../actions/runs` 是 **401**。三条不依赖令牌的路,按"在线 → 落没落地 → 历史与时长"排。
|
||||
|
||||
### 4.1 在线核验(查 `action_runner`)
|
||||
|
||||
`action_runner.last_online` 与当前 Unix 秒相差 **< 30 秒**即在正常心跳;`agent_labels` 应为 `["windows"]`(不含 windows 就是标签配错)。口径见 `docs/CI/远程CI完整指南.md` §6.1。
|
||||
|
||||
### 4.2 看 runner 侧(最直接,能证明"job 真落到本机了")
|
||||
|
||||
```powershell
|
||||
$d = 'D:\Docker\act_runner\log'
|
||||
# 日志被 daemon 占着,必须共享读打开(ReadAllBytes 会 IOException)
|
||||
$fs = [System.IO.File]::Open("$d\runner-daemon.log",'Open','Read','ReadWrite')
|
||||
# 关键行:declare successfully / writing the log of task N to … / Running job with …
|
||||
Get-ChildItem "$d\job" -File | Sort-Object LastWriteTime -Descending | Select-Object -First 5
|
||||
```
|
||||
|
||||
判读:`declare successfully` 后长时间零输出 = **在线且空闲**;`log\job\*.log` 出现新文件 = 有 task 落地;每个 job 结束时 `[runner] task N finished: success|failure`。**这份 `finished:` 行也是 §4.3 里校验 status 枚举的裁判**(§1 的三分法同样从这些文件起手)。
|
||||
|
||||
⚠ 非管理员**读不到 SYSTEM 进程的 `CommandLine`**(返回 `$null` → 过滤器静默匹配 0 个)→ 绝不能据此说"没有看门狗"。`Process.StartTime` 对 SYSTEM 也不可读,但 CIM 的 `CreationDate` 可读 → 用**进程存活时长**对比 10 秒重启间隔来判断重启循环,**不要 grep 不轮转的日志尾部**(旧失败一直在尾部,会对健康 runner 报假警)。
|
||||
⚠ 查询进程自己的命令行含关键字会**自匹配**:把模式拆开写,如 `'run_runner_watch' + 'dog\.ps1'`。
|
||||
|
||||
### 4.3 查 run / job 历史与时长(推翻页面上的说法)
|
||||
|
||||
**驱动就认 pymysql**:本机 `C:/ProgramData/Anaconda3/python.exe` 实测 `pymysql=True`、`MySQLdb=False`、`mysql.connector` **缺失**(import 直接 `ModuleNotFoundError`)→ `docs/CI/远程CI完整指南.md` §6.1 的 `mysql.exe` CLI 路线本机走不通,改 pymysql 连 `127.0.0.1:3306`/gitea。
|
||||
|
||||
**口令只从 app.ini 运行时读**:`[database]` 段里 `HOST` 是 `host:port` 一体要拆开,`NAME`/`USER`/`PASSWD` 直接用;**绝不要把口令写进脚本字面量、报告、截图或提交**(本轮就是这么漏的一次)。同段还有 `PATH = ...gitea.db`,但 `DB_TYPE = mysql` → **别去读那个 sqlite 文件**。`token`/`PASSWD` 一类行在打印任何配置文件时统一 redact。
|
||||
|
||||
```python
|
||||
# 写成文件跑(ci-artifacts/_q.py),别内联,见 §6
|
||||
import re, pymysql
|
||||
ini = open(r'D:/Docker/Gitea/custom/conf/app.ini', encoding='utf-8').read()
|
||||
sec = ini.split('[database]')[1].split('\n[', 1)[0]
|
||||
kv = dict(re.findall(r'(?m)^(\w+)\s*=\s*(.*)$', sec))
|
||||
host, port = kv['HOST'].split(':')
|
||||
db = pymysql.connect(host=host, port=int(port), user=kv['USER'], password=kv['PASSWD'],
|
||||
database=kv['NAME'], charset='utf8mb4',
|
||||
cursorclass=pymysql.cursors.DictCursor)
|
||||
cur = db.cursor()
|
||||
cur.execute("SHOW TABLES LIKE 'action%'") # → action_run / action_run_job / action_task /
|
||||
# action_run_index / action_runner / action_task_step …
|
||||
cur.execute("SHOW COLUMNS FROM action_run_job") # 🔴 列名以这里为准,别凭记忆 SELECT
|
||||
```
|
||||
|
||||
**取数(本仓库务必带 `repo_id=10`,否则混进 stm32f103 的 run)**:
|
||||
|
||||
- `action_run`:`id, repo_id, status, title, ref, sha, event, created, started, stopped`,`ORDER BY id DESC`;
|
||||
- `action_run_job`:按 run 关联到 `id, status, attempt, task_id, created, started, stopped`(本轮 run 41 → job 85/86)。
|
||||
**`task_id` 就是 job 日志文件名的 `task-NN`** → `action_run_job.task_id = 66` ↔ `log/job/20261025-133227-task-66.log`,这是 DB 与 runner 日志互校的唯一把手;
|
||||
- 时长三个数:`total = stopped-created`、`run = stopped-started`、`queue = started-created`(本轮实测 queue 普遍 1 s,所以页面 6m49s 不可能是排队)。时间戳是 **Unix 秒**,`datetime.datetime.fromtimestamp(x)` 再打印;
|
||||
- **连续红次数按 job 数、不按 run**:把同一 job 历次 `status` 从新到旧排,数连续非"成功"的长度(本轮据此得出 L0 自上线 **13 次全红、从未绿过**,run 28~41)。
|
||||
|
||||
**status 枚举:先定死再下结论**(本轮最大的误判源)。
|
||||
|
||||
| 码 | 含义 | 依据 |
|
||||
|---|---|---|
|
||||
| 0 | Unknown / 未开始 | 本轮映射试错后才定 |
|
||||
| 1 | Success | 同上 |
|
||||
| 2 | **Failure** | run 41 = 2 → Failure,与页面红、与 runner 日志 `task finished: failure` 三方一致 |
|
||||
| ≥3 | **本轮未核实** | 试错时猜过 `3=Failure`/`4=Cancelled`/`5=Skipped`,**都没有交叉验证** → 用到之前先拿一条已知结局的 run 反推(TODO:从 Gitea 源码定死,别再靠猜) |
|
||||
|
||||
- **写查询时同时打印原始数字和映射名**;`action_run` 与 `action_run_job` **默认按同一套码处理是危险假设**,跨到别的表要重验。
|
||||
- 判据:同一个 run 换一套映射后红绿翻转(本轮 step 53 判 `Success`、step 56 判 `Failure`)→ 说明你还没定死枚举,**这时任何"红绿史"都不许出口**。
|
||||
- 中文标题在 bash 终端回显成乱码(本轮 `'docs(log): ???'`)只是**显示**问题,库里数据没坏 → **认 run 用 `id`/`sha`/`ref`,不要用标题文本匹配**;要看中文就把结果写文件再按 UTF-8 读。
|
||||
|
||||
## 5. 本地对照:日志一律落 `ci-artifacts/`
|
||||
|
||||
判"本地绿 CI 红"必须有同一份可引用的本地日志。落点在仓库内 gitignored 的 `ci-artifacts/`(`.gitignore`:159),**不要写仓库外绝对路径**:
|
||||
|
||||
```bash
|
||||
mkdir -p ci-artifacts && .venv/Scripts/python.exe scripts/ci_local.py --keep-going \
|
||||
> ci-artifacts/local_ci_$(date +%Y%m%d).log 2>&1; echo EXIT=$? >> ci-artifacts/local_ci_$(date +%Y%m%d).log
|
||||
```
|
||||
|
||||
**本轮实测的替换**:先写到 `D:/Code/_sync_backup_20261026/ci_local_*.log`(仓库外)在后台 bash job 里直接 `No such file or directory` → 改落 `ci-artifacts/` 才跑通。跑门禁这类分钟级任务用后台 job 起,再 `job_output` 收,别占着前台。Keil 构建对照同理落 `ci-artifacts/local_build_*.log`。
|
||||
|
||||
## 6. 脚本层:写查询与批量探测的规矩(本轮踩点集中在这里)
|
||||
|
||||
- **一段 Python 查询写成 JS 模板字符串会炸**(本轮两次:`Expected unicode escape`、`Expected ';', '}' or <eof>`)。正确做法:按行存字符串数组 → `join('\n')` → 落盘 `ci-artifacts/_qN.py` → 用 `C:/ProgramData/Anaconda3/python.exe` 跑 → **收尾统一 `fs.rmSync` 删掉**(临时查询脚本不入库,也别留在仓库里)。
|
||||
- **`run_code` 里不能用 `import x from '…'`**(`'import', and 'export' cannot be used outside of module code`)→ `const fs = await import('node:fs')`。
|
||||
- **批量探测拆小批**:一次调用塞 4 条 Windows 探测(worktree / 目录 / 服务枚举 / app.ini)撞过 120 s deadline,只拿到部分输出。一次 1~2 条,慢源(`Get-ChildItem -Recurse`、`Get-CimInstance Win32_Service` 全量、`generate-config`)单独跑并显式 `timeoutMs`。
|
||||
- **别对全部 runner job 日志一把梭 grep**:日志混合编码(UTF-16LE / CP936),逐文件返回一堆 `[ERR …]`。要么单文件 `head`/`sed -n 'A,Bp'`/`grep`(本轮有效路径),要么解析时逐文件 try/catch 容错。
|
||||
- **`pwsh`/`read`/`subagent` 这类工具只能在 `run_code` 程序里 `await tools.xxx()` 调**,直接当工具名调会 `unknown tool`;每次调用**都要带 `description`**(漏了会 `missing required property "description"`)。
|
||||
|
||||
## 7. runner 有哪些配置项:先 `generate-config`,别猜键名
|
||||
|
||||
**判断"这个键存不存在"的唯一凭据是 runner 自己 dump 出来的默认配置文本**,全程只读——不写 `config.yaml`、不删 `.runner`(那是注册凭据,删了要重新 register)、不重启/注销 runner(只有改 `runner.labels` 才必须重启,`docs/CI/远程CI完整指南.md` §2.3.2)。
|
||||
|
||||
```powershell
|
||||
& 'D:\Docker\act_runner\gitea-runner.exe' generate-config 2>&1 | Out-String -Width 160
|
||||
```
|
||||
|
||||
- 从输出里 **grep 目标键名**(本轮实测清单含 `cache` / `capacity` / `container` / `default_image` / `action_shallow_clone` / `access_key_file` / `addr` / `allocate_pty` / `bind_workdir` / `bucket` / `docker_host` / `docker_timeout` / `enabled` / `endpoint` / `env_file` / `envs` / `external_secret` / `external_secret_file` …,以及 `#set_act_env: true`)。
|
||||
- **dump 里绝大多数键是带 `#` 的注释行** → "在清单里" ≠ "本机已启用";是否被显式覆盖要跟 `config.yaml` 逐行比:**注释=默认值生效,非注释=本机写死了**。
|
||||
- **别拿段名当 grep 锚点**:本轮找 `[actions]` 段和 `DEFAULT_ACTIONS_URL` 时段名锚点落空、上下文打出来的是 `log_report_*` 那几行 → 直接按**键名** grep。另外服务端的 `DEFAULT_ACTIONS_URL` 是 app.ini 的键(`docs/CI/远程CI完整指南.md` §1.2.1),不要当成 runner 的键去 `config set`。
|
||||
- 老教训仍然成立:v4 的 `log` 段只有 `level` 和 `job`,**没有 `file`**,`config set log.file …` 会 `unknown config key`(`docs/CI/远程CI完整指南.md` §2.3.2)→ 改键前以 `generate-config` 输出为准;写完用 `gitea-runner.exe config get <键>` 自证。
|
||||
|
||||
## 8. 门禁口径:讲规则,不讲数字
|
||||
|
||||
数字随分支和提交漂移(实测同一句"18 个钩子"在另一分支是 26,master 后来涨到 33;单测从 3 套件/288 断言涨到 10 套件/647)。**只有规则稳定**:
|
||||
|
||||
- L0:`pre-commit hooks -- passed=A failed=B skipped=C`,其中 **A+B+C = 定义数 − 1**;`skipped` 本机唯一常见项是 `mem-numbers`(无 `.map` 即跳过,并以 `::warning::` 明示"CI 不守体积门禁",见`docs/CI/远程CI完整指南.md` §0.0)。
|
||||
- L0b:`gitlint: N commit message(s) checked, 0 failed`。
|
||||
- L0.5:`<N> runner(s), A/A assertions passed, fail=0`,**N = `test/host/` 下 `run_unit_test.py` 的个数**。
|
||||
- **判定只看 `failed=0`、`fail=0`、A==A**;抄历史数字当基准必然误判。
|
||||
|
||||
## 9. 改 workflow / 门禁脚本的红线
|
||||
|
||||
`.gitea/workflows/ci.yml`:
|
||||
|
||||
- `shell: powershell`,**不是 `pwsh`**(Windows 自带只有 5.1,`pwsh` 会 `Cannot find: pwsh in PATH`)。
|
||||
- **`run:` 块内必须纯 ASCII**:runner 把每块写成无 BOM 临时 `.ps1` 交给 5.1 → 按 CP936 解码 → 中文导致"字符串缺少终止符"(报错行号正好在含中文那行,极易误判为逻辑错)。中文只写在 YAML 的 `name:`/注释里。
|
||||
- 写 `GITHUB_ENV` / `GITHUB_PATH` / `GITHUB_OUTPUT` 必须显式 `-Encoding ascii`(`Out-File -Append` 默认 UTF-16LE)。
|
||||
- 零依赖存档:`Tee-Object` 到 `ci-artifacts/*.log` + `$GITHUB_STEP_SUMMARY` + `::notice::`;**不用 `actions/upload-artifact`**(内网可能拉不到外层 action,`docs/CI/远程CI完整指南.md` §5.3)。
|
||||
- 工具链探测收在 `scripts/ci_find_python.ps1`(可离线自测 + 机器级安装兜底);`DTU_*` 最高优先级,且**显式设置就必须有效、不静默回退**。
|
||||
|
||||
`scripts/ci_*.ps1`:纯 ASCII(含中文的 `.ps1` 才需要 BOM,见`docs/CI/远程CI完整指南.md` §7.1);`-Encoding utf8NoBOM` 是 **PS 7 才有的**,5.1 要写文件用 `[System.IO.File]::WriteAllText($p, $s, (New-Object System.Text.UTF8Encoding($false)))`。
|
||||
|
||||
gitlint 专属:消息文件必须写在**仓库内**(跨盘符被 pre-commit 拒 `path is on mount 'C:', start on mount 'D:'`)且 **UTF-8 无 BOM**(BOM 占标题 1 字符,扭曲 100 上限)。
|
||||
|
||||
## 10. 改完必须验(顺序别省)
|
||||
|
||||
```powershell
|
||||
# .ps1:纯 ASCII + 语法
|
||||
$b=[System.IO.File]::ReadAllBytes($p); @($b|?{$_ -gt 127}).Count # 必须 0
|
||||
$e=$null; [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $p),[ref]$null,[ref]$e)|Out-Null; @($e).Count # 必须 0
|
||||
|
||||
# .yml:可解析 + 全部 run: 块纯 ASCII + 无 BOM + LF
|
||||
& '.venv\Scripts\python.exe' -c "import yaml;d=yaml.safe_load(open(r'.gitea/workflows/ci.yml',encoding='utf-8'));print(list(d))"
|
||||
|
||||
# 文档:链接与章节引用
|
||||
& '.venv\Scripts\python.exe' scripts/check_doc_links.py --all
|
||||
|
||||
# 全量门禁(等价 CI 的两个 job)
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py --keep-going
|
||||
```
|
||||
|
||||
提交信息:标题 ≤100、**正文每行 ≤80**、UTF-8 无 BOM、`git commit -F <文件>`;**先程序化数一遍长度再提交**(中文标题最容易超,`-m` 还会被终端代码页转码)。
|
||||
|
||||
推送后到 runner 侧确认(§4.2):两个 job 各自的 `SUMMARY` 行 + `finished: success`。**"推送成功"不等于"CI 跑过"**。
|
||||
|
||||
**汇报口径**:说"红了几次 / 跑了多久"必须写明**来源**(库里的 `action_run_job` 时长,还是 runner 日志,还是页面)与**所用的 status 映射**;三者不一致时以库 + 日志为准并说清页面为什么不可信。只读排查就全程只读——不改文件、不 commit/push、不动 runner 注册状态。
|
||||
|
||||
## 11. 分支与发布(走通的做法)
|
||||
|
||||
- 预演冲突面,不动工作区:`git merge-tree --write-tree --name-only <a> <b>`(exit 1 = 有冲突)。
|
||||
- 数字口径类冲突的正确解法:**改成规则 + 分分支快照**,而不是选一边留数字——否则同一篇文档在另一分支上必错,下次合并再撞一次。
|
||||
- 变更记录是**历史,永不改写**,只在最上方按日期加行。
|
||||
- 想验证 CI 又不想动主干:复制一条分支推上去(`git branch <copy> <src>` → `git push -u origin <copy>`),跑完再删。注意**新建分支的首次 push 不触发 run**(§2 症状表第 1 行)。
|
||||
- 主干落后很多时先同步再排障:备份脏文件 → 只 stash 被跟踪的改动 → `checkout` → `merge --ff-only` → `stash pop`,同步完再跑 §1(本轮本地落后 248 个提交,先同步才看得出真实红因)。
|
||||
- 删分支前先算独有提交:`git rev-list --count origin/master..<branch>`,**> 0 就别删**(删了只能靠 SHA 找回);本地一律 `git branch -d`(未合并它会自己拒绝)。
|
||||
- `git commit` / `git push` 都是"先问"动作(AGENTS.md 边界节),不是任何任务的隐含步骤。
|
||||
@@ -185,4 +185,4 @@
|
||||
| 待办与审计发现登记簿 | `docs/任务规划/README.md` |
|
||||
| 多任务并行开发(开空间 / 合并回收 / 台架与构建排队) | **`task-worktree` skill**(`@task`,用到自动加载)、`docs/环境与部署/并行任务工作树方案.md` |
|
||||
| 换机 / 工具路径环境变量 | `docs/环境与部署/` |
|
||||
| 远程 CI(Gitea Actions,**只跑本机一台 runner**) | [`docs/CI/README.md`](docs/CI/README.md) |
|
||||
| 远程 CI(Gitea Actions,**只跑本机一台 runner**) | **`remote-ci-ops` skill**(排障顺序与判据,用到自动加载)、[`docs/CI/README.md`](docs/CI/README.md) |
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
目录下的两篇指南 `CI配置指南.md` 与 `Gitea_Runner部署指南.md` 合并而来,**两篇均已删除**,
|
||||
去重后为单一来源)。
|
||||
|
||||
> **agent 排障时的操作顺序**在 **`remote-ci-ops` skill**(`.dsh/skills/remote-ci-ops/SKILL.md`,
|
||||
> 用到自动加载):症状 → 一条判据 → 动作,加上改 workflow / 门禁脚本的红线与改后验收清单。
|
||||
> 分工是——**本目录讲"为什么"和"怎么装",skill 讲"出问题时按什么顺序查"**,同一事实不在两处详述。
|
||||
|
||||
| 想干什么 | 去看 |
|
||||
|---|---|
|
||||
| 先搞清 CI 需要哪三样东西就位、本机拓扑、部署前置清单 | §0 / §0.1 / §0.2 |
|
||||
@@ -29,6 +33,7 @@
|
||||
| `config.yaml` 各字段语义(capacity / timeout / labels / 日志目录…) | §2.3 |
|
||||
| **常驻:为什么必须看门狗、不能只起 daemon、也不能做成 Windows 服务** | §2.4 + §3.0 |
|
||||
| runner 装什么软件、为什么变量必须对 **SYSTEM** 可见、机器级 `DTU_*` | §4(§4.1.1~§4.1.3 是三个真坑) |
|
||||
| **覆盖率工具链(mscoverage)怎么装、机器级 MSVC 怎么指**(含三条可直接粘贴的管理员命令) | §4.4 |
|
||||
| workflow 位置 / 触发 / 内网拉不到 `actions/checkout` 的三种处置 | §5(§5.3 零依赖写法) |
|
||||
| 两个 job 各自跑哪些步骤、两条 Python 探测链要同序同步 | §5.5 |
|
||||
| 端到端验证清单、无网页登录时直查 MySQL 核验 | §6 / §6.1 |
|
||||
@@ -81,6 +86,8 @@
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-27 | 指南 §5.3 **整节重写并落地**:workflow 两个 job 的 `uses:` 归零(原生 git 全量检出,六项实测 A/B/C/D/E/F + 两条自查命令见 §5.3);§1.2.1 第三行按 1.25.4 实际行为改正(自定义 `DEFAULT_ACTIONS_URL` 已无效,旧写法会把人引向一个不存在的开关);§7 的 checkout 行改为“看见这症状=有人把 `uses:` 加回来了”。另:`scripts/ci_runner_bootstrap.ps1` 的 MSVC 探测改为 `vswhere` + 工具集钉世代(默认 14.51,与覆盖率基线同版本),`-SetEnv` 开始成对写 `DTU_MS_COVERAGE` |
|
||||
| 2026-10-27 | 新增指南 §4.4「覆盖率工具链(mscoverage)与 VS2026 BuildTools」并加进上面的目录表。当天另修掉 `cppcheck` 两根真红(`ci/remote-ci` 的 `5869c03`)——此前 L0 的 28 次红里混着**三类**:runner 缺覆盖率组件、外网拉 `actions/checkout` 抖动、真代码问题。**别再一律当成环境噪声**,按 §7 逐条定性 |
|
||||
| 2026-10-12 | 🐛 **首次真跑出 run**:`L0.5` **成功**(`10 runner(s), 647/647, fail=0`,与本地逐字一致);`L0` **假失败**——gitlint 因 runner 无 git 身份而 `exit 254`(`Missing git configuration: please set user.name`),旧文案把它说成"提交信息违规",且该步 `exit 1` 连带让 26 个钩子全部没跑。已在 `ci_gitlint_range.ps1` 用 `GIT_CONFIG_*` 注入一次性身份(不写文件)并区分 254/1 退出码。另记:**新建分支的首次 push 不触发 run**,需 `workflow_dispatch` 或再推一个非文档提交。详见指南 §7 |
|
||||
| 2026-10-12 | 🔀 本轮 CI 修复随 `master` **合入 `v2-on-ui`**(该分支另有 21 个功能提交;冲突仅 `AGENTS.md` 与 `docs/README.md` 两处,解法是"保留其新版结构 + 把 CI 指针搬进对应位置")。因该分支门禁更多(**26 钩子 / 10 单测套件** vs master 的 18 / 3),指南里写死的数字已改为**规则 + 分分支快照**,见指南 §6 / §7 / §10 |
|
||||
| 2026-10-12 | ✅ 双看门狗缺陷**实测收敛**(管理员 `-StartNow` 清掉 3 个残留 → 单实例、链路恢复、runner online)。另修两处:daemon 日志走 stderr 被 PS 5.1 `2>&1` 包成 ErrorRecord(2 行真日志写成 8 行,每 4 行 3 行噪声)→ 启动器改 `ForEach-Object { "$_" }`;`-Status` 诚实化(非管理员读不到 SYSTEM 进程命令行 → 明说需提权而非谎报"无看门狗";重启循环判据改看进程存活时长,不再靠不轮转日志的尾部字符串报假警)。详见指南 §7.3 |
|
||||
|
||||
+149
-32
@@ -156,7 +156,7 @@ ENABLED = true
|
||||
|---|---|
|
||||
| **不配(默认)** | 从 **[github.com](https://github.com)** 拉取 action |
|
||||
| `self` | 从**你自己的 Gitea 实例**拉取(要求 action 已镜像进你的实例) |
|
||||
| 具体 URL(如 `https://gitea.com`) | 从该地址拉取 |
|
||||
| 具体 URL | ⚠ **本实例的 1.25.4 上这一档没有可用取值**:该版本只保留 `github`(默认)与 `self` 两个关键字,写具体 URL **不再改变下载源**(2026-10 核对时踩到的第一现场就是这里,本表旧版写的是老行为)。别把希望放在这个键上——**修法见 §5.3:不依赖 `uses:`** |
|
||||
|
||||
> ⚠️ **不要把你的实例域名写进去当普通 URL**——要"从自己实例拉"就用关键字 **`self`**;
|
||||
> 且**写了 `self` 还得先把 action 镜像进自己实例**,否则 `actions/checkout` 依然拉不到。
|
||||
@@ -166,7 +166,7 @@ ENABLED = true
|
||||
| 场景 | 配置 |
|
||||
|---|---|
|
||||
| 服务器**能**访问 github.com | **什么都不配**(默认走 github.com,最省事) |
|
||||
| 内网,**不能**访问 github.com | 建议**改用零依赖写法**(见本文 §5.3 方案 3),`DEFAULT_ACTIONS_URL` 配不配都行;<br>若坚持用 `actions/checkout@v4`,则需 `DEFAULT_ACTIONS_URL = self` **且**把该 action 镜像进实例 |
|
||||
| 内网,**不能**访问 github.com | **本仓库现状(2026-10-27)**:workflow 里 `uses:` 已归零,`DEFAULT_ACTIONS_URL` 配不配都不影响(见 §5.3)。若哪天有人把 `uses:` 加回来,这条路就会重新变成外网依赖——`self` 需先镜像 action,而自定义 URL 在本版本无效 |
|
||||
|
||||
> Gitea 官方 FAQ 原文:*"From `https://github.com` by default, or from your own instance
|
||||
> when `[actions].DEFAULT_ACTIONS_URL` is `self`."* 使用别处 action 也可在 workflow 里直接写全路径:
|
||||
@@ -887,6 +887,85 @@ Start-ScheduledTask -TaskName GiteaActionsRunner
|
||||
|
||||
这条命令跑的就是 CI 的同一套判定。**本地能过,runner 上基本就能过**(前提是 PATH 一致)。
|
||||
|
||||
### 4.4 覆盖率工具链(mscoverage)与 VS2026 BuildTools——2026-10-27 实测落地
|
||||
|
||||
`test-coverage` 总闸依赖 `Microsoft.CodeCoverage.Console.exe`;缺它按用户裁定 **FAIL、不 skip**
|
||||
(2026-10-24;口径见 `docs/测试说明/主机单测覆盖率口径.md` §7 第 8 条)。这台 runner 在 2026-10-27 之前
|
||||
**恒** `[ENV] exit 2`:机器上只有 VS2019 生成工具(MSVC 14.29),没有该 exe。
|
||||
|
||||
**为什么只能走安装**:该 exe **没有免安装的独立发行版**——2026-10-27 实测 NuGet 两个候选包:
|
||||
`Microsoft.Testing.Extensions.CodeCoverage` 18.12.0(109 个条目、**.exe 数为 0**,全是 managed dll,那是
|
||||
给 MSTest / Testing-Platform 用的扩展库)与 `Microsoft.CodeCoverage` 18.11.0(只有老版
|
||||
`build/netstandard2.0/CodeCoverage/CodeCoverage.exe`,**零个 Console**)→ `Microsoft.CodeCoverage.Console.exe`
|
||||
只随 Visual Studio 本体分发(17.9+ / 18.x 的 `Common7\IDE\Extensions\Microsoft\CodeCoverage.Console\`)。
|
||||
|
||||
**安装入口与组件 ID**(`aka.ms/vs/18/release/…` 实测**不存在**(落到 Bing 的短链未找到页),`stable` 与
|
||||
`insiders` 才有;组件 ID 由 `aka.ms/vs/18/stable/channel` → `VisualStudio.vsman`(18.11.12324.101,17 MB)
|
||||
解析得到:`Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage` 是 18.11 清单里**唯一**引用
|
||||
`Microsoft.VisualStudio.TestTools.DynamicCodeCoverage` 与 `Microsoft.CodeCoverage.Console.Targeted`
|
||||
(X86/X64/ARM64 三份 vsix)的组):
|
||||
|
||||
```powershell
|
||||
# 管理员 PowerShell。本机实装在 E 盘——换机若装回 C 盘默认路径,下面的「非默认盘」警告即不适用
|
||||
curl.exe -L -o "$env:TEMP\vs_buildtools18.exe" https://aka.ms/vs/18/stable/vs_buildtools.exe
|
||||
& "$env:TEMP\vs_buildtools18.exe" --quiet --wait --norestart --nocache `
|
||||
--installPath "E:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools" `
|
||||
--add Microsoft.VisualStudio.Workload.VCTools `
|
||||
--add Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage `
|
||||
--includeRecommended
|
||||
```
|
||||
|
||||
装完实测内容:`CodeCoverage.Console\Microsoft.CodeCoverage.Console.exe`(mscoverage 18.11.0-preview.26421.2,
|
||||
**能脱离 vcvars 独立跑**,`collect -f cobertura` 就是门禁所用的那条命令)+ `Team Tools\Dynamic Code Coverage
|
||||
Tools\CodeCoverage.exe`(老通道,脚本不用它)+ MSVC **14.44.35207 / 14.51.36231 / 14.52.36815**;Windows SDK
|
||||
复用 C 盘现成的 `Windows Kits\10\10.0.26100`。**选 14.51 是为了与量 `scripts/test_coverage_baseline.json` 那台
|
||||
同版本**(口径 §2 记 VS 2026 / MSVC 14.51)——覆盖率分母随编译器变(口径 §2.1 实测 `sha256.c`:gcc 105 行 vs
|
||||
MSVC 114 行),**换编译器世代 = 换分母**;指错版本只会把「ENV 红」变成「假红」。
|
||||
|
||||
⚠ **装在非默认盘 = 门禁与 CI 都看不见,必须显式指机器级变量**:`scripts/ci_runner_bootstrap.ps1` 的
|
||||
`Find-MsvcCl`(§2.4)与 `test/host/common/unit_test_harness.py` 的 `_default_vs_roots()` 都只由
|
||||
`%ProgramFiles%` / `%ProgramFiles(x86)%` 派生根(本机 = C 盘),且 `Find-MsvcCl` **取首个命中**(候选表里
|
||||
2019 排在最前)。好消息是 **`DTU_MS_COVERAGE` 不用设**:`scripts/check_test_coverage.py` 的 `_vs_roots()`
|
||||
第一个候选是「`cl.exe` 上溯 8 级的 VS 根」——机器级 `DTU_MSVC_CL` 一指过去,同根下的标准相对路径就自动命中
|
||||
该 exe(本机实测:40/40 套跑通、相对基线零回退)。。`-SetEnv` 现在也会把
|
||||
`DTU_MS_COVERAGE` **显式写上**(探测顺序:先按选中的 `cl.exe` 反推同一套 VS 根,再遍历 vswhere 实例)——
|
||||
**这两个变量要成对改**:它们指向同一套 VS 才是自洽状态;写死它的代价是将来卸掉 VS 时门禁会
|
||||
硬失败并打印 `[ENV]`,而那正是我们想要的(宁可响亮地红,不要静默地绿)。
|
||||
|
||||
**本机三条命令(管理员 PowerShell,可整段粘贴)**:
|
||||
|
||||
```powershell
|
||||
# ① 机器级 MSVC 指到 E 盘 VS2026 的 14.51(覆盖率分母与基线同版本)
|
||||
[Environment]::SetEnvironmentVariable('DTU_MSVC_CL','E:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC\Tools\MSVC\14.51.36231\bin\Hostx64\x64\cl.exe','Machine')
|
||||
|
||||
# ② 重启 runner 守护进程(非 -Status 路径会重写看门狗、并先 Unregister 再 Register 计划任务,离线十几秒)
|
||||
& 'D:\Code\DTU Code\STM32F4-Base\scripts\ci_runner_bootstrap.ps1' -StartNow
|
||||
|
||||
# ③ 只读体检;关键行 = MACHINE DTU_MSVC_CL = E:\...\14.51.36231\bin\Hostx64\x64\cl.exe
|
||||
& 'D:\Code\DTU Code\STM32F4-Base\scripts\ci_runner_bootstrap.ps1' -Status
|
||||
```
|
||||
|
||||
- ✅ **`-SetEnv` 的 MSVC 探测已重写**(同日)。旧版 `Find-MsvcCl` 只扫 `%ProgramFiles%` 派生的 C 盘根、
|
||||
且**取首个命中**(候选表 2019 在最前)。本机 C 盘的 VS2019 已于 **2026-10-27 01:00** 被卸(`…\2019`
|
||||
只剩空目录、C 盘 x64 `cl.exe` 实测 **0 个**),所以旧版在本机的真实行为**不是**“覆回 14.29”,而是
|
||||
**找不到 → 只 Warn → 什么都不写**,机器级变量于是长期指着那条已失效的 C 盘路径(后果:CI 的 L0.5
|
||||
走 `DTU_MSVC_CL` 显式通道 → 路径不存在 = 硬失败,不做自动回退,见 `unit_test_harness.py`)。
|
||||
现在改成 `vswhere -all -products *` 取实例(**能看见非默认盘**)+ 工具集版本降序,并按 `-MsvcToolset`
|
||||
**前缀钉住世代**(默认 `14.51.` = 与覆盖率基线同版本;钉不上会连 WARN 再回落最新,绝不静默),
|
||||
`-MsvcLatest` 显式要最新。实测本机:候选 3 个(14.44 / 14.51 / 14.52,全在 E 盘)→ 默认返回
|
||||
**14.51.36231**;`-Latest` 返回 14.52;`-MsvcToolset 99.` 走告警回落路径。
|
||||
- ⚠ 但 `Set-MachineEnv` **没有 `-Force` 就不覆盖已有值**(保留 + 告警)——所以探测修好后,**首次仍要带
|
||||
`-Force`** 才能把机器级变量从旧值换成新探到的路径;带 `-Force` 前**先看清它探到了哪套**(`-SetEnv`
|
||||
会打印候选与命中),别盲刷。
|
||||
- ⚠ 若 ③ 显示变量已对、而 CI 日志仍打印旧 `cl.exe`:计划任务服务的环境块没刷新,`Restart-Service Schedule`
|
||||
后重跑 ②。**唯一算数的证据是 CI 日志**——L0.5 步骤会打印它实际使用的 `cl.exe` 来源。
|
||||
- 装完 18 世代后 `vswhere` 会挑到最高版 → 模拟器与 `ui-golden` 的 CMake 生成器变成 `Visual Studio 18 2026`
|
||||
(`simulator/build_sim.ps1` 已支持 16/17/18 三档),UI 相关改动按 `ui-verify` skill 重跑像素回归。
|
||||
- 残留待核:口径 §7 第 8 条记的「Community 18.10 与 BuildTools 18.9 两份安装」**不在本机**——2026-10-27
|
||||
`vswhere -all -products *` 实测本机 18 世代实例只有刚装的这套 E 盘 BuildTools(另一账户下是否有 per-user
|
||||
安装,非提权读不到该账户 HKCU,**未证**)。→ 「CI runner 与本机同一台」这句要按**量基线那台可能是另一台
|
||||
机器**来理解;换机或换账户一律先 `check_test_coverage.py --list` 再跑全量。
|
||||
|
||||
---
|
||||
|
||||
## 5. 仓库端:workflow 文件
|
||||
@@ -923,38 +1002,69 @@ on:
|
||||
- 纯文档改动**不触发**(省 runner);
|
||||
- 想手动跑一次:仓库 → Actions → 选 `CI` → **Run workflow**。
|
||||
|
||||
### 5.3 内网无公网时的 action 拉取(重要)
|
||||
### 5.3 内网无公网时的 action 拉取:本仓库已彻底移除 `uses:`(2026-10-27 落地)
|
||||
|
||||
workflow 用了 `actions/checkout@v4`。**默认从 `github.com` 拉取**(见 §1.2.1;
|
||||
不是从你自己的实例,也不是从 gitea.com —— 这点很反直觉);实例不通
|
||||
github.com 会在 checkout 步骤卡住/失败。三种处置:
|
||||
**为什么非自己动不可**:`uses:` 的解析发生在 **`Prepare all required actions`** 阶段——**先于任何步骤**、
|
||||
无条件联网(默认 github.com,见 §1.2.1),而且**每个 job 各自解析进自己的 `.cache/act/<hash>`,跨 job 不
|
||||
复用**。这条链在本机实测不稳:run 36(10-21)/ 41(10-25)/ 42(10-26,分支 `IEC60870`)三次都是 **~45 s
|
||||
后整 job 死亡、零钩子输出**,而同一分钟手工 `curl` 同一地址返回 200——**手工可达 ≠ CI 可用**(解析器跑在
|
||||
SYSTEM 下)。Gitea 1.25.4 又去掉了自定义 `DEFAULT_ACTIONS_URL`(§1.2.1 表第三行),服务端没有可拧的键,
|
||||
**唯一归本仓库管的修法就是不再依赖它**。
|
||||
|
||||
1. **镜像到自有实例**:`DEFAULT_ACTIONS_URL = self`,**并先把 `actions/checkout` 导入你自己的 Gitea**;
|
||||
2. **指向可达镜像**:`DEFAULT_ACTIONS_URL` 指向某个能访问的 action 站(或在 workflow 里
|
||||
直接写全路径 `uses: https://<host>/owner/repo@ref`);
|
||||
3. **零依赖**(内网最稳,推荐):把 checkout 换成纯 git 命令,不依赖任何 action:
|
||||
**现状**:两个 job 的第 1 步都是原生 git 检出,workflow 里 `uses:` **归零**。检出正文**内联**在 workflow
|
||||
里,**不能**抽成 `scripts/*.ps1`——它执行时脚本自己还没被检出(鸡生蛋),这与 `ci_find_python.ps1` /
|
||||
`ci_gitlint_range.ps1` 的抽取方向**相反**;代价是两个 job 各持一份、**必须逐字一致**(自查见本节末)。
|
||||
|
||||
```yaml
|
||||
- name: Checkout
|
||||
shell: pwsh
|
||||
run: |
|
||||
git init .
|
||||
git remote add origin $env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY.git
|
||||
git fetch --depth 1 origin $env:GITHUB_SHA
|
||||
git checkout $env:GITHUB_SHA
|
||||
**这份检出做了什么**(等价 `checkout@v4` + `fetch-depth: 0`):`init` 或复用现有 `.git` → `remote
|
||||
set-url origin <server_url>/<repository>.git` → 完整 refspec `+refs/heads/*:refs/remotes/origin/*` 全量
|
||||
`fetch --prune --no-tags` → 若事件 sha 不在分支 refspec 覆盖内(PR 合并提交等)再显式 fetch 事件 ref →
|
||||
**验证 sha 真在历史里**(不在就 `::error::` 硬失败,**绝不默默构建一棵错的树**)→ `checkout --detach` →
|
||||
`reset --hard` → `clean -ffd`。
|
||||
|
||||
四个刻意的选择(每条都有实测依据):
|
||||
|
||||
| 选择 | 为什么 |
|
||||
|---|---|
|
||||
| `clean -ffd`,**不是** `-ffdx` | `-x` 会删**被忽略**的文件,而 runner 工作区跨运行复用它们(pip / pre-commit 缓存)。实测:放一个 gitignored 的 `ci-artifacts/kept.log` + 一个未跟踪垃圾 + 改一个跟踪文件 → 重跑后 `dirty_files=0`、垃圾被清、**被忽略文件原样保留** |
|
||||
| `GIT_TERMINAL_PROMPT=0` + `-c credential.helper=` | 否则一次认证挑战会让 git **等人类输入**,而 runner 没有控制台 → 白占满 30 分钟 job 超时、表现得像“CI 挂了”。实测灌一个坏令牌:`fatal: unable to get password from user` → **rc=1 立即失败**,不挂起 |
|
||||
| 令牌只走 per-invocation `-c http.extraheader` | 不写进 `.git/config`、不回显。匿名也能拉(仓库公开:`/info/refs?service=git-upload-pack` 实测 200)→ CI 不因此新增“令牌必须可用”的依赖 |
|
||||
| 不抓 tag | 本仓库 tag 数 = 0,且无任何脚本用 `git describe --tags`(2026-10-27 实测);要用时再加 |
|
||||
|
||||
> 🔴 **旧版本节给过一段“可以直接抄”的示例,里面两条都是坑,别再照抄**:① `shell: pwsh` —— runner 的
|
||||
> PATH 里没有 `pwsh`,会直接 `Cannot find: pwsh in PATH`,job 在跑任何业务逻辑之前就失败(本文件头部
|
||||
> 已写死只能 `shell: powershell`);② `git fetch --depth 1` —— 浅克隆让 `scripts/ci_gitlint_range.ps1`
|
||||
> 的 `base..head` 和任何需要历史的门禁报 unknown revision。至于方案 1/2(`self` 需先把 action 镜像进
|
||||
> 实例、自定义 URL 在 1.25.4 无效),都不如**直接不依赖**。
|
||||
|
||||
**实测六项**(临时工作区、内层一律 `powershell.exe` **5.1** 忠实复现 runner“写成无 BOM .ps1 再执行”的方式;
|
||||
关键一步是 **E:把正文从 `ci.yml` 里抽出来跑**,测的就是文件里那份字节):
|
||||
|
||||
| # | 场景 | 结果 |
|
||||
|---|---|---|
|
||||
| A | 全新工作区、匿名无令牌 | rc=0:`commits=665`、refspec 完整、分离 HEAD、远端分支 4 / tag 0、`dirty_files=0` |
|
||||
| B | 复用工作区(改跟踪文件 + 未跟踪垃圾 + gitignored 文件) | rc=0:`dirty_files=0`、垃圾清掉、**ignored 保留**、`17ece86..HEAD` 解出 **18 个提交**(恰等于上游那 18 笔 → gitlint 的 range 可用) |
|
||||
| C | 目标 sha 不在远端(未推的本地提交) | **rc=1** `::error::<sha> is not present in the fetched history`,且 HEAD 纹丝不动 |
|
||||
| D | 灌一个坏令牌 | rc=1,`fatal: unable to get password from user`(**快速失败**,没挂住) |
|
||||
| E | 执行**从 `ci.yml` 抽出的**正文 | rc=0、与 A 同数;**零 ErrorRecord 噪声**;首次全量拉取 172 s(18.65 MiB / loopback) |
|
||||
| F | 同一工作区再跑一次 | rc=0、秒级——**稳态 CI 不重复付全量代价**,只有工作区全新时才慢 |
|
||||
> ⚠ **一条未证的边界**:`pull_request` 也是本 workflow 的触发器,而 PR 事件的 `github.ref` 是
|
||||
> `refs/pull/<n>/merge`,**不在**我用的分支 refspec 内 → 会走「再显式 fetch 事件 ref」那条兜底。
|
||||
> 这台实例**至今 0 个 PR**(库里 `pull_request` 计数 = 0),`git ls-remote 'refs/pull/*'` 也返回空,
|
||||
> 所以「Gitea 1.25.4 到底提不提供 merge ref」**在这里无法证实**。真撞上的话**症状是响亮的**:
|
||||
> `::error::<sha> is not present in the fetched history` + rc=1,**不会**默默测一棵错的树;届时的
|
||||
> 兜底是改 fetch `refs/pull/<n>/head`(gitlint 的 base 本来就是 `github.event.pull_request.base.sha`,
|
||||
> 不受影响)。同一条备注也适用于 `${{ github.server_url }}`:原生检出与 `checkout@v4` 用的是**同一个**
|
||||
> server_url 拼法,**可达性假设没有变化**——变的只是不再联网 github.com。
|
||||
|
||||
**自查(改 workflow 前后都跑,第二条是内联换来的同步义务)**:
|
||||
|
||||
```powershell
|
||||
# 1) uses: 必须为 0(注释里的不算)
|
||||
Select-String -Path '.gitea/workflows/ci.yml' -Pattern '^\s*-?\s*uses:'
|
||||
# 2) 两个 job 的第 1 步正文必须逐字一致
|
||||
& '.venv\Scripts\python.exe' -c "import yaml;d=yaml.safe_load(open('.gitea/workflows/ci.yml',encoding='utf-8'));a=[j['steps'][0]['run'] for j in d['jobs'].values()];print('JOBS=%d CHARS=%d' % (len(a),len(a[0])));print('OK identical' if len(set(a))==1 else 'DIFFERS -- keep in sync (see docs/CI guide 5.3)')"
|
||||
```
|
||||
|
||||
> 方案 3 无需任何外部 action 与任何 `DEFAULT_ACTIONS_URL` 配置;代价是失去 checkout 的缓存优化
|
||||
> (对本仓库影响很小)。
|
||||
> 参考:Gitea FAQ「Where will the runner download scripts when using actions such as `actions/checkout@v4`?」
|
||||
|
||||
**本机环境判断(2026-10-02)**:Gitea 装了 `OFFLINE_MODE = true`,说明这台机器的外网访问
|
||||
**可能受限**。如果 runner 拉不到 github.com,checkout 步骤会卡住。
|
||||
|
||||
> ⚠️ 若走方案 1:**`self` 是关键字,不是写你的域名**(见 §1.2.1)。
|
||||
|
||||
**建议**:**先按原样试一次**。若 checkout 步骤报网络错误,再换成上面的零依赖写法(方案 3)。
|
||||
|
||||
### 5.4 产物存档与结果汇总(2026-10-02 起)
|
||||
|
||||
**目标**:CI 失败时**不查 MySQL / 不翻 runner 磁盘**,在 Gitea 页面即可看到完整输出与摘要。
|
||||
@@ -988,8 +1098,8 @@ github.com 会在 checkout 步骤卡住/失败。三种处置:
|
||||
|
||||
| job | `runs-on` | 步骤要点 |
|
||||
|---|---|---|
|
||||
| `l0-gates` | `windows` | checkout(`fetch-depth: 0`,gitlint/cubemx 需完整历史)→ 环境自检(**Python 探测走 `scripts/ci_find_python.ps1`**、cppcheck 按 `DTU_CPPCHECK` → PATH → 标准安装目录,缺则 `::error::` + exit 1)→ `pre_commit install --install-hooks` → **提交信息门禁 `scripts/ci_gitlint_range.ps1`**(见 §7 排障表"钩子条数对不上"行)→ **决定跳过哪些钩子**(探测 `.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 全红") |
|
||||
| `l0-gates` | `windows` | **原生 git 检出**(全量 refspec,不再用 `actions/checkout`;见 §5.3)→ 环境自检检(**Python 探测走 `scripts/ci_find_python.ps1`**、cppcheck 按 `DTU_CPPCHECK` → PATH → 标准安装目录,缺则 `::error::` + exit 1)→ `pre_commit install --install-hooks` → **提交信息门禁 `scripts/ci_gitlint_range.ps1`**(见 §7 排障表"钩子条数对不上"行)→ **决定跳过哪些钩子**(探测 `.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` | **原生 git 检出**(与 `l0-gates` 第 1 步逐字一致,内联带来的同步义务,自查见 §5.3 末)→ **探测 MSVC**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 全红") |
|
||||
|
||||
> 🔴 **两条 Python 探测链必须同序同步**:本地 `scripts/ci_local.py` 与 CI 侧
|
||||
> `scripts/ci_find_python.ps1` 的候选顺序要一致。2026-10-11 那次 L0 全灭,就是 workflow
|
||||
@@ -1064,7 +1174,7 @@ date +%s # 对比 last_online
|
||||
| push 后 Actions 页**空白/无任务** | ① 服务端开关未开 ② workflow 没 push 到远程 ③ 只改了 `docs/**`(被 `paths-ignore` 滤掉) ④ **新建分支的首次 push**(实测 2026-10-12:创建 `v2-on-ui-ci` 那次 push 没产生 run——runner 在线空闲、job 日志零新增;紧接着一次普通 push 立刻出 task。原因:`before` 是全零 SHA,变更文件集算不出来,`paths-ignore` 求值时被滤空) | ①②③ 逐条核对 §1 / §5.1 / §5.2;④ 用 `workflow_dispatch` 手动触发,或再推一个非文档路径的提交 |
|
||||
| 任务一直 **Waiting / 排队** | 没有 label 匹配 `windows` 的 runner | 检查 `--labels "windows:host"`;Settings→Runners 是否 Idle |
|
||||
| `l0-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) |
|
||||
| `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §5.3 |
|
||||
| `Prepare all required actions` 或 `checkout` 卡住/超时(~45 s 后整 job 亡、零钩子输出) | 有人把 `uses:` 加回了 workflow —— 该阶段**先于任何步骤**无条件联网,手工 curl 可达不代表 runner 可达 | 本仓库 2026-10-27 起已无 `uses:`(§5.3 有六项实测与自查命令);自查:`Select-String .gitea/workflows/ci.yml -Pattern '^\s*-?\s*uses:'` 应为空 |
|
||||
| `l05` 日志出现 `::warning::runner 未安装 MSVC` | VS 装在非默认路径或未装 | 见 §4.2(默认路径 / 同步改两处根目录) |
|
||||
| `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` |
|
||||
@@ -1072,6 +1182,7 @@ date +%s # 对比 last_online
|
||||
| `Tee-Object` 步骤报 `TerminatorExpectedAtEndOfString` / 字符串缺少终止符 | `run:` 块混入了非 ASCII(GBK 解码错位) | 见文件头 🔴🔴;`run:` 内必须纯 ASCII |
|
||||
| `l05` 报 `fatal error C1083: 无法打开包括文件 "stdio.h"` | Windows SDK 缺失 | 装 VS 时勾"使用 C++ 的桌面开发"+ SDK |
|
||||
| 单测退出码 2 | 环境错误(多为无 MSVC) | 同上 |
|
||||
| `test-coverage` 恒 Failed、日志写 `[ENV] 找不到 Microsoft.CodeCoverage.Console.exe`;或换了 VS 世代后覆盖率大面积「假红」 | 该机没有 VS 17.9+/18.x(**该 exe 只随 VS 分发**,NuGet 拿不到);或 VS 装在非默认盘而机器级 `DTU_MSVC_CL` 仍指旧世代 | §4.4(安装入口 + 组件 ID + 三条可直接粘贴的命令 + `-Force` 陷阱) |
|
||||
| `pre-commit` 首次运行久 | 首次要下载各钩子的环境 | 正常,runner 缓存后会快 |
|
||||
| `daemon` 启动报 **connection refused / dial tcp** | `--instance` 填了不可达地址 | 删 `.runner` → 重新 `register`,`--instance http://127.0.0.1:3001` |
|
||||
| 启动报 **token** 相关错误 | Registration Token 过期(约 1 小时)或已用过 | 回网页重新创建 runner 拿新 token,重新 `register` |
|
||||
@@ -1310,6 +1421,10 @@ powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -DryRun
|
||||
# 兜底通道(只有变量没设时才会走到):把 pre-commit 装进【全局】site-packages,见 §4.1.1
|
||||
# ⚠️ 改过任何环境变量都必须 Stop 再 Start 任务——环境块是任务启动时的快照
|
||||
|
||||
# 5b) 覆盖率工具链(mscoverage)/ 把机器级 MSVC 指到 VS2026 —— 见 §4.4
|
||||
# 安装入口、组件 ID、三条可直接粘贴的管理员命令都在 §4.4;本机 VS 装在 E 盘,
|
||||
# 非默认盘必须显式设 DTU_MSVC_CL(否则门禁与 CI 都看不见它)。别加 -Force
|
||||
|
||||
# 6) 复验(-s = 模拟 SYSTEM 视角,必须过)
|
||||
& 'C:\ProgramData\Anaconda3\python.exe' -s -m pre_commit --version # pre-commit 4.6.2
|
||||
|
||||
@@ -1352,6 +1467,8 @@ powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -DryRun
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-27 | ✂️ **CI 侧两处根治 + 更正我上一条的一处推断**:① **workflow 去掉 `uses:`**——两个 job 的第 1 步换成原生 git 全量检出(完整 refspec + 分离 HEAD + `reset --hard` + `clean -ffd`、令牌只走 per-invocation `-c http.extraheader`、`GIT_TERMINAL_PROMPT=0` 防挂死、目标 sha 不在历史里就 `::error::` 硬失败而不是默默构建错树)。动因是 run 36/41/42 三次 **`Prepare all required actions` 阶段 ~45 s 外网超时把整个 job 打死、零钩子输出**,而同一分钟手工 `curl` 返回 200。§5.3 整节重写,含六项实测(A/B/C/D/E/F——关键是 E:**把正文从 `ci.yml` 里抽出来跑**,测的就是文件里那份字节)与两条自查命令;§7 的 checkout 行同步改为“看见此症状=有人把 `uses:` 加回来了”。② **`scripts/ci_runner_bootstrap.ps1` 的 `Find-MsvcCl` 重写**:`vswhere -all -products *` 取实例(**能看见非默认盘**)+ 工具集版本降序 + `-MsvcToolset` 前缀**钉住世代**(默认 `14.51.`,与覆盖率基线同版本;钉不上连 WARN 再回落最新,绝不静默)+ 新增 `-MsvcLatest`;`-SetEnv` 增写 `DTU_MS_COVERAGE`(须与 `DTU_MSVC_CL` 成对改)。③ **更正**:先前写“旧探测会把机器级变量覆回 C 盘 14.29”——本机实情是 C 盘 VS2019 已于 **01:00 卸载**(`…\2019` 只剩空目录、C 盘 x64 `cl.exe` 实测 0 个),旧 `-SetEnv` 在本机是**找不到 → 只 Warn → 不写**,于是变量一直指着失效路径(CI 的 L0.5 走显式通道即硬失败,不做静默回退)。实测新逻辑:候选 3 个(14.44/14.51/14.52,全在 E 盘)→ 默认命中 **14.51.36231**,`-Latest` 给 14.52,`-MsvcToolset 99.` 走告警回落。④ §1.2.1 表第三行按 1.25.4 实际行为改正(自定义 `DEFAULT_ACTIONS_URL` 取值已无效)——旧写法会把下一个排障的人引向一个不存在的开关 |
|
||||
| 2026-10-27 | 🧯 **拆穿「L0 恒红 = 环境噪声」这个想当然 + 覆盖率工具链补齐**:① `cppcheck` 恒红**不是**版本漂移——本机与 runner 同机、同 exe(2.21.0)、同参数复现即红,两根 `uninitializedVariable`(`s_cmd_con` 在空信息体分支把未初始化 `body` 传下去、`s_spont_step` 的 `blen` 作用域过宽)是真代码问题,已在 `ci/remote-ci` 的 `5869c03` 修掉(`@build` 0/0,`Code 231712→231732`)。② 新增 §4.4:`Microsoft.CodeCoverage.Console.exe` **只随 VS 分发**(NuGet 两候选包实测 .exe 数为 0 / 只有老版 `CodeCoverage.exe`),本机补装 VS2026 BuildTools 18.11 于 **E 盘**后仍因非默认盘不被看见,须显式机器级 `DTU_MSVC_CL`(指 14.51 = 与 `scripts/test_coverage_baseline.json` 同版本);三条管理员命令、`-Force` 陷阱、`Restart-Service Schedule` 判据一并写入。③ 排障教训:**页面时长与本地对不上时以 `commit_status` + runner 日志为准**——页面那条「L0 Failing after 6m49s」在库里是 run 26 的 409 s(cppcheck 真红),同一 sha 另有一次 5 s 的 checkout 超时,**两者不是一回事**,别拿一个解释另一个 |
|
||||
| 2026-10-25 | 🔒 **A-5 残余销项联动(§0.0 重写)**:体积门禁由"CI 不守"改为**同码凭证强制**——本地 `@build` 0/0 后 `ram_baseline.py --update` 盖 `scripts/size_attestation.json`,L0 作业末尾 `check_size_gate.py --ci` 复核同码性 + 红线复算(runner 无需 Keil);主干 push 拦红、其余事件 warn-only;`mem-budget` 补进 SKIP 名单诚实显示。指纹取 git 索引 blob(跨 checkout 稳定,修 7c4628b);三项仍守不住的口径唯一正文 = 脚本 docstring |
|
||||
| 2026-10-12 | 🐛 **第一次真跑出 run,当场暴露 gitlint 门禁空转**:把本轮修复推到新分支 `v2-on-ui-ci` 后拿到 task 31/32——**L0.5 成功**(`10 runner(s), 647/647 assertions passed, fail=0`,与本地逐字一致),**L0 失败**却与提交信息毫无关系:`exit code: 254` + `Missing git configuration: please set user.name`。根因是 `actions/checkout` 把 HOME 指向每次任务的临时目录、SYSTEM 账号没有 git 身份,gitlint 在读到规则之前就配置错误退出;旧脚本还把失败原因**写死**成"标题超 100 字符",加上该步 `exit 1` 让后面 26 个钩子**一个都没跑**。修法:脚本先探测身份,缺失就用 `GIT_CONFIG_COUNT/KEY/VALUE` 注入一次性身份(不写文件、不碰开发者机器),并区分 254(门禁没跑起来 → `exit 2`)与真实违规(→ `exit 1`)。另记一条新坑:**新建分支的首次 push 不产生 run**(§7 排障表 ④) |
|
||||
| 2026-10-12 | 🔀 **随 `master` 合入 `v2-on-ui`:数字口径改为分支中立**。本指南原先写死 master 的数字(18 钩子 / 17 报告、3 runner / 288 断言),而 `v2-on-ui` 是 **26 钩子 / 10 套件**——同一篇文档在两个分支上必然有一个是错的。§6 验收第 8 行、§7 排障表、§10 验收标准三处改为讲规则(**报告数 = 定义数 − 1**;**runner 数 = `test/host/` 下 `run_unit_test.py` 个数**)+ 分分支实测快照。本次合并只带文档与脚本,**零 C 代码改动**;合并后本分支 `@build` 0 错 0 警、`ci_local.py` 全绿(L0 25 钩子 + L0.5 10 runner / 647 断言) |
|
||||
|
||||
@@ -163,6 +163,11 @@
|
||||
16:17,都是各自安装当日,不是后来补勾)→ VS 18 的 BuildTools 默认就带它。真实风险面只剩:没有
|
||||
VS 只有裸 MSVC 的机器、`DTU_MS_COVERAGE` 指错、以及靠 PATH 兜底的环境。CI runner 与本机同一台
|
||||
(`docs/CI/README.md`);**换机仍先跑 `--list` 再跑全量**。
|
||||
⚠ **2026-10-27 实测补注(换机场景真发生了)**:这台 runner 机当日 `vswhere -all -products *` 只有
|
||||
VS2019 生成工具(MSVC 14.29)→ 上面「CI runner 与本机同一台」这句对**量基线那台**不成立。补装 VS2026
|
||||
BuildTools 18.11 后仍因装在 E 盘(非默认盘)被门禁与 CI 同时看不见,最终靠显式机器级 `DTU_MSVC_CL` 指
|
||||
14.51(与量基线同版本)跑通 40/40 套、零基线回退。安装入口、组件 ID、三条命令与 `-Force` 陷阱统一记在
|
||||
`docs/CI/远程CI完整指南.md` §4.4——**本处不重复,免得两处漂移**。
|
||||
9. **并集口径下"单套百分比"没有意义。** 同一文件被多套触及很常见(`sha256.c` 被 `sha256` 与
|
||||
`ftp_auth` 都编译),所以对外只报**并集**,单套只报"触及几个文件 / 多少行"。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user