From 7122fa38fbfc59b7d4b038d2a7d1177b69b2179a Mon Sep 17 00:00:00 2001 From: Episkey-G Date: Tue, 25 Aug 2026 16:31:27 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20=E4=BE=9D=E8=B5=96=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=97=B6=E9=87=8D=E8=AF=95=E7=BC=96=E8=AF=91?= =?UTF-8?q?=EF=BC=8C=E7=BC=96=E8=AF=91=E9=94=99=E8=AF=AF=E4=BB=8D=E7=AB=8B?= =?UTF-8?q?=E5=88=BB=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit codegen PR #180 的 ci-syntax 红了,原因不是代码: Unresolveable build extension: Plugin org.sonatype.central: central-publishing-maven-plugin:0.8.0 ... could not be resolved 是没能从中央仓库拉到那个插件。它在 pom 里带 true, Maven 即使只跑 compile 也必须在读取项目阶段先解析它,所以一次网络抖动 就能打断一个纯编译的闸。同期 7 个 codegen PR 里只坏了这一个。 代价不只是要人重跑:自动合并要求 mergeable_state == clean, 一次抖动会让那一轮发布退回人工,并为一个瞬时故障发一条「自动合并受阻」告警。 没有用 -Dmaven.wagon.http.retryHandler.count,那个参数在这里是空转的: Maven 3.9 起默认传输实现是 maven-resolver 的 HttpTransporter 而非 wagon, 实测 mvn -X 输出为 `Using transporter HttpTransporter with priority 5.0`, wagon 的参数根本不会被读到。native transport 自己的重试是开着的, 而 #180 仍然失败,说明那次不是可重试的 HTTP 条件。故只能在命令这一层重试。 判据只对解析类失败重试,编译错误立刻失败——无差别重试会让一个真实的 编译错误在 50 个 module 上跑三遍,把最该快速反馈的失败拖慢三倍。 本地实测三条路径: 正常编译 exit 0,1 次尝试(51 个 Compiling / 52 个 module) 真实编译错 exit 2,1 次尝试,3 秒返回,日志写明「不重试」 解析失败 exit 1,3 次尝试,44 秒(10s + 30s 退避) --- Makefile | 6 +++- scripts/mvn-compile.sh | 62 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100755 scripts/mvn-compile.sh diff --git a/Makefile b/Makefile index d728e539..01484e47 100644 --- a/Makefile +++ b/Makefile @@ -21,9 +21,13 @@ fmt: # 重复的 在 maven 里只有 WARNING、不会让构建失败,本 target 对它无闸。 # 要补需引入 maven-enforcer-plugin 的 banDuplicatePomDependencyVersions, # 经评估后决定不引入,故该缺口保留。 +# +# 编译命令包了一层 scripts/mvn-compile.sh:它只在「依赖解析失败」时重试, +# 编译错误立刻失败。起因是 codegen PR #180 被一次拉不到发布插件的网络抖动打红, +# 那既不是代码问题也不是闸误判,却会让那一轮发布退回人工。详见脚本文件头。 .PHONY: ci-syntax ci-syntax: - mvn -B -DskipTests compile + @bash $(CURDIR)/scripts/mvn-compile.sh # 警告:当前是空壳,exit 0 但什么都没检查,不要把它的通过当成风格已过检。 # 填实需要先跑一次全仓 google-java-format(见 fmt),是个独立的大 diff,不在 CI 改造范围内。 diff --git a/scripts/mvn-compile.sh b/scripts/mvn-compile.sh new file mode 100755 index 00000000..e66396bd --- /dev/null +++ b/scripts/mvn-compile.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# ci-syntax 的编译闸:mvn -B -DskipTests compile,仅在「依赖解析失败」时重试。 +# +# ── 为什么需要这个包装 ──────────────────────────────────────────────────────── +# 2026-08-21 的 codegen PR #180,ci-syntax 红了,原因不是代码: +# Unresolveable build extension: Plugin org.sonatype.central: +# central-publishing-maven-plugin:0.8.0 or one of its dependencies could not be resolved +# Failed to read artifact descriptor for ...:jar:0.8.0 +# 是 Maven 没能从中央仓库拉到那个插件。它在 pom 里带 true, +# Maven 即使只跑 compile 也必须在「读取项目」阶段先解析它——所以一次网络抖动 +# 就能打断一个纯编译的闸门。同期 7 个 codegen PR 里坏了这一个。 +# +# 门禁红的代价不只是要人重跑:codegen PR 的自动合并要求 mergeable_state == clean, +# 一次抖动会让那一轮发布退回人工,并发一条「自动合并受阻」告警——为一个瞬时故障。 +# +# ── 为什么不是加 -Dmaven.wagon.http.retryHandler.count ─────────────────────── +# 那个参数**在这里是空转的**。Maven 3.9 起默认传输实现是 maven-resolver 的 +# HttpTransporter,不是 wagon;实测 `mvn -X` 输出里是 +# [DEBUG] Using transporter HttpTransporter with priority 5.0 +# wagon 的参数根本不会被读到。native transport 有自己的 +# aether.connector.http.retryHandler.count(默认已开启重试),而 #180 仍然失败, +# 说明那次不是一个可重试的 HTTP 条件。所以只能在命令这一层重试。 +# +# ── 判据:只对解析类失败重试,编译错误立刻失败 ─────────────────────────────── +# 无差别重试会让一个真实的编译错误跑三遍(本仓 50 个 module,每遍一两分钟), +# 把最该快速反馈的那种失败拖慢三倍。故只在输出命中解析类失败特征时才重试。 + +set -uo pipefail + +ATTEMPTS=3 +BACKOFF=(10 30) # 第 1、2 次失败后各等多久 +LOG="$(mktemp -t mvn-compile.XXXXXX)" +trap 'rm -f "$LOG"' EXIT + +# 解析类失败的特征。这些都是「拿不到构件」而非「代码不对」,重试有意义。 +RESOLUTION_ERRORS='Unresolveable build extension|Failed to read artifact descriptor|Could not resolve dependencies|Could not transfer artifact|Non-resolvable parent POM|Could not find artifact|Connection reset|Connection timed out|Read timed out' + +for i in $(seq 1 "$ATTEMPTS"); do + # tee 保证日志照常输出到 CI 界面,同时留一份供判据匹配。 + # 用 PIPESTATUS 取 mvn 自己的退出码——管道默认给的是 tee 的。 + mvn -B -DskipTests compile 2>&1 | tee "$LOG" + code="${PIPESTATUS[0]}" + + if [ "$code" -eq 0 ]; then + [ "$i" -gt 1 ] && echo "==> 第 $i 次尝试成功(前 $((i - 1)) 次为依赖解析失败)" + exit 0 + fi + + if ! grep -qE "$RESOLUTION_ERRORS" "$LOG"; then + echo "==> 编译失败(非依赖解析问题),不重试。退出码 $code" >&2 + exit "$code" + fi + + if [ "$i" -eq "$ATTEMPTS" ]; then + echo "==> 依赖解析连续失败 $ATTEMPTS 次,判定为真实故障而非抖动。退出码 $code" >&2 + exit "$code" + fi + + wait_s="${BACKOFF[$((i - 1))]}" + echo "==> 第 $i 次失败于依赖解析,${wait_s}s 后重试(共 $ATTEMPTS 次)" >&2 + sleep "$wait_s" +done