Skip to content

ci: add a PR gate that lints the generated code - #69

Merged
Episkey-G merged 2 commits into
masterfrom
ci/add-pr-gate
Aug 19, 2026
Merged

ci: add a PR gate that lints the generated code#69
Episkey-G merged 2 commits into
masterfrom
ci/add-pr-gate

Conversation

@Episkey-G

Copy link
Copy Markdown
Collaborator

What

  • php.ymlci.ymlci-syntax / lint / test, aggregated by ci-gate
  • new .github/workflows/compat.yml — runs the existing make compatible (PHPCompatibility, testVersion 5.6), which nothing in CI was running
  • new scripts/ci-syntax.sh + scripts/dup-check.sh
  • Makefile: ci-syntax target; test-cov also emits coverage.xml

.gitlab-ci.yml is untouched.

The two gates

  • G1 php -l over every file in src/
  • G2 scripts/dup-check.sh php src — duplicate top-level class/interface/trait in one file

G2 is not redundant: php -l catches duplicate use (compile-time symbol clash) but exits 0 on two class Foo in the same file — that's a runtime error, invisible to lint. Verified both ways on this repo.

A trap worth flagging: php -l exits 255, and xargs aborts on 255

The obvious spelling is wrong, and it fails in the direction that looks fine:

find src -name '*.php' -print0 | xargs -0 -P4 -n1 php -l

Measured in a php:8.3-cli container (GNU findutils xargs 4.10.0, i.e. what ubuntu-latest has) against this repo, which currently has 23 bad files:

exit files reported
bare xargs … php -l 124 1xargs: php: exited with status 255; aborting
this PR's scripts/ci-syntax.sh 1 23

The exit code is non-zero either way, so it looks like the gate works — it just silently hides 22 of the 23. macOS's BSD xargs doesn't abort on 255, so testing on a Mac gives a false sense of safety.

The script sidesteps xargs' exit code entirely: the wrapper always exits 0 and which file failed is carried out through a results file. There is also a guard that the number of result lines equals the number of input files (2293/2293) — that guard is the regression detector for this trap.

Only this PR's files block

ci-syntax scans all of src/ but only fails on files changed in this PR; hits in untouched files print as warnings and don't affect the exit code.

Without this, the 23 pre-existing bad files would red-flag every product's codegen PR until all 12 affected products happen to be released — blocking releases for reasons unrelated to their content.

Changed set: git diff --name-only --diff-filter=d "$CI_BASE_SHA...HEAD" -- src (three-dot). CI_BASE_SHA is supplied by the workflow, with fetch-depth: 0 on checkout. Fail-closed: unset, unresolvable, not a git repo, or a failed diff all fall back to strict whole-tree and print the reason. Local make ci-syntax with no variable is strict whole-tree, so local and CI never disagree silently.

Verified locally

case exit
no CI_BASE_SHA (strict whole tree) 2 (make) — the 23 pre-existing files
a commit touching only a clean product 0, all 23 still printed as warnings
duplicate use injected into a changed file 2
two same-named classes injected into a changed file 2 (and php -l alone exits 0 — G2 is what catches it)
unresolvable CI_BASE_SHA 2, falls back to strict, prints the reason
make lint / make test-cov / make compatible 0 / 0 / 0

Expected: ci-gate will be red on merge

23 *Response.php files across 12 products emit a duplicate use, which is a PHP fatal — the class cannot be instantiated. The cause was a generator template bug, already fixed upstream; these files are build output and get corrected when their product is next regenerated. Please don't hand-edit them — they would grow back on the next codegen. Until then they are warnings and block nothing, and the warning count is a usable progress metric.

- 新增 scripts/ci-syntax.sh,把语法闸做成「全量扫描 + 分级判定」:
  G1 `php -l` 抓解析错误与编译期符号冲突(模板漏去重导致的重复 `use`);
  G2 scripts/dup-check.sh 抓同一文件内重复的顶层 class/interface/trait
  —— 这类重复 `php -l` 一律 exit 0,属运行期错误,必须单独查。
  两段都全量扫 src/,但退出码只由本轮变更的文件决定,其余问题只告警。

  为什么分级:仓里存量有 23 个 *Response.php 带重复 `use`(12 个产品、81 行),
  整棵树一律阻断会让**任意一个**产品的 codegen PR 被这些无关的存量问题拦红,
  check 变红 → mergeable_state 转 unstable → 自动合并永久失效;而这些文件是
  生成产物,只有对应产品各自重新发布才会被修好的模板覆盖,等多久不可控。
  同一后果在 python 侧已被实证并否决(pipeline 2507836 发的是 UHost+UAI-Modelverse、
  根本没重新生成 uphost,uphost 的存量问题照样被扫出来),2026-07-31 定的处置是
  「判定边界取本轮变更文件,存量只告警」,见 ucloud-sdk-release/scripts/
  python-fatal-check.sh 文件头。本次是同一决策在 php 仓的落地。
  存量仍全量扫、全量打印:不阻断不等于不看见,债务要保持可度量。

  变更集来自环境变量 CI_BASE_SHA,严格集取 `git diff --name-only $CI_BASE_SHA...HEAD`
  在 src/ 下的 .php(三点 diff,基线分支前进过也只算 PR 自己引入的改动)。
  fail-closed:未设置、解析不出 commit、diff 失败,一律退回整棵树严格判定并打印
  退回原因。本地不设该变量跑 `make ci-syntax` 看到的就是整棵树严格,与改造前一致。

  实测 `php -l` 失败时退出码为 255,GNU xargs 见到 255 会立刻中止、只报第一个坏文件
  (BSD xargs 无此行为,本地跑不出来)。故 wrapper 无论 php 退什么码都 exit 0,
  「哪个文件坏了」改用结果文件传出,判定不依赖 xargs 退出码;再用「结果行数必须等于
  输入文件数」兜底,一旦提前中止就 FATAL,不会当成没问题。

- scripts/dup-check.sh 增加 `--strict-list <清单文件>`:目录照样全量扫,
  只有清单内文件的命中影响退出码并标 [严格],清单外标 [告警]。
  不给该参数时行为与改造前完全一致,既有用法不受影响。

- php.yml 改名 ci.yml:setup-php 8.3 + composer install,
  ci-syntax / lint / test 三个 job 汇聚到 ci-gate。
  分支保护只需把 ci-gate 配成 required check,其余 job 增删不影响它。
  触发从裸 [push, pull_request] 收窄到 master;checkout v1 升到 v7。
  ci-syntax job 加 fetch-depth: 0(默认浅克隆拿不到 base sha,会静默退回整棵树严格)
  并注入 CI_BASE_SHA:pull_request 取 base.sha,push 取 before。
  codecov 上传是 test job 内的 step 且 continue-on-error,不进 ci-gate 的 needs。

- 新增 compat.yml:定时跑 make compatible(PHPCompatibility,testVersion 5.6),
  这是 composer.json 里 "php": ">=5.6" 这条下界唯一的真实验证手段,此前无 CI 在跑。
  只挂 schedule + workflow_dispatch,不在 PR 上产生 check。

- test-cov 额外产出 coverage.xml 供 codecov 消费。原先这个产物是 workflow 里
  裸跑 phpunit 得到的;现在语言命令一律只经 Makefile 走,就得由 target 自己出。

- 不动 .gitlab-ci.yml。
兼容矩阵是纯巡检,不参与门禁判定,也没人会盯一个非阻断的定时任务。
声明下界还成不成立是另一个问题,要验的时候手工跑一次即可,
不必为它常驻一条每天运行的 workflow。
@Episkey-G
Episkey-G merged commit 3e14d70 into master Aug 19, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant