mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
Merge pull request #25 from JianFeeeee/ci/harden-csrc-gate
ci: 加固 C 门禁 —— 卡死不再拖满超时,缺工具不再假装通过
This commit is contained in:
119
.github/workflows/ci.yml
vendored
119
.github/workflows/ci.yml
vendored
@ -167,19 +167,124 @@ jobs:
|
||||
csrc:
|
||||
name: C infrastructure gates
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
# 门禁本身实测 0~1 秒(csrc-lint/abi/headers/sanitize/cross 各自 <1s)。
|
||||
# 这 15 分钟全部是给 apt-get 的余量 —— 卡死历来只发生在装包阶段。
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
# clang 供双编译器告警对照;gcc-aarch64 供跨架构编译门禁。
|
||||
# 门禁在缺工具时是显式 SKIP 而不是假通过,这里装齐以免静默降级。
|
||||
- name: 安装 C 工具链
|
||||
# ── 安装 C 工具链 ─────────────────────────────────────────────
|
||||
#
|
||||
# ★ 加固点(2026-10-22):实测两次该 job 卡死 20 分钟并超时,
|
||||
# 而**本地同一门禁 0~1 秒全通过**(make check-csrc)。卡死点全在
|
||||
# apt-get:ubuntu-latest runner 上 `apt-get update` 偶发挂住不返回,
|
||||
# 既没有失败也没有进展,只能等 job 级 timeout。
|
||||
#
|
||||
# 三重加固:
|
||||
# 1. 逐包检查是否已安装 —— runner 镜像通常自带 cmake/clang,
|
||||
# 已装就跳过 apt-get(多数情况直接走这条,完全不碰网络)
|
||||
# 2. 需要装时给 apt-get 加自身的超时与重试,让它**自己失败**,
|
||||
# 而不是把整个 job 拖到 job 级 timeout(后者报错信息毫无指向性)
|
||||
# 3. 装完立即 `command -v` 复核,缺工具就红 —— 见下方"缺工具即失败"
|
||||
- name: 安装 C 工具链(已装则跳过)
|
||||
# 120s(update) + 180s(install) = 300s,给 5 分钟正好;
|
||||
# 超出说明 timeout 命令本身没生效,此时应当红而不是继续等。
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq cmake clang gcc-aarch64-linux-gnu
|
||||
set -uo pipefail
|
||||
# ★ 不再需要 gcc-aarch64-linux-gnu(2026-10-22)。
|
||||
#
|
||||
# 实测:装它要从源拉 10MB+ 的 cpp-13 等依赖,在 runner 上卡住超时
|
||||
# (15:47:39 开始下 cpp-13-aarch64,180s 后仍未完成)。
|
||||
# 而 csrc-cross 只需要 `-fsyntax-only`,clang 自带交叉前端即可:
|
||||
# `clang --target=aarch64-linux-gnu` 会真的切到 aarch64 目标
|
||||
# (__aarch64__ 宏生效、指针宽度按该架构算),足以暴露本门禁要抓的
|
||||
# 架构问题。Makefile 的 csrc-cross 已按
|
||||
# CC_ARM64 > clang --target > aarch64-gcc > SKIP 的优先级挑选。
|
||||
need=""
|
||||
command -v cmake >/dev/null || need="$need cmake"
|
||||
command -v clang >/dev/null || need="$need clang"
|
||||
# aarch64 的 libc 头(1.7MB,仅头文件,**不含编译器**)。
|
||||
# 用它 + 已预装的 clang 交叉前端,替代 10MB+ 的完整交叉 GCC
|
||||
# (实测那个会卡在下载 cpp-13-aarch64,180s 超时)。
|
||||
[ -d /usr/aarch64-linux-gnu/include ] || need="$need libc6-dev-arm64-cross"
|
||||
|
||||
if [ -z "$need" ]; then
|
||||
echo "✅ 依赖已存在,跳过 apt-get:$(command -v cmake clang | tr '\n' ' ')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "需要安装:$need"
|
||||
|
||||
# ★ 2026-10-22 实测:加 Acquire::*Timeout=60 + Retries=3 之后仍然卡满
|
||||
# 6 分钟 step 超时,且**日志里一个 apt 输出都没有** —— 因为 `-qq`
|
||||
# 把一切静默了。卡死不可观测正是它拖满超时的原因:既看不到卡在哪个
|
||||
# 源,也看不到到底在下载什么。
|
||||
#
|
||||
# 所以:
|
||||
# · 去掉 `-qq`,保留可读进度(否则下次卡死仍然无从诊断)
|
||||
# · 用 `timeout` 命令做外层硬超时 —— Acquire::Timeout 只管单个连接,
|
||||
# 不管 apt 整体(源重试、DPkg 锁等待都不受它约束)
|
||||
# · 失败后**不重试整段**:宁可红一次让人看见,也不要为偶发网络
|
||||
# 抖动掩盖真实问题
|
||||
APT_OPTS="-o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 -o Acquire::Retries=2"
|
||||
# shellcheck disable=SC2086
|
||||
if ! timeout 120 sudo apt-get $APT_OPTS update; then
|
||||
echo "::error::apt-get update 超时/失败(120s)—— runner 网络或源不可用"
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
if ! timeout 180 sudo apt-get $APT_OPTS install -y --no-install-recommends $need; then
|
||||
echo "::error::apt-get install 超时/失败(180s)需要安装:$need"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 缺工具即失败(★ 这条是本次加固的核心)────────────────────
|
||||
#
|
||||
# 此前门禁在缺工具时打 `[SKIP] ... exit 0` —— **CI 照样变绿**。
|
||||
# 那意味着:apt-get 悄悄失败(或将来有人删掉安装步骤)⇒ 五道门禁
|
||||
# 全部跳过 ⇒ job 绿 ⇒ 让人以为 C 侧有门禁,实际一个都没跑。
|
||||
#
|
||||
# 静默降级比红更危险:红会有人修,绿没人看。
|
||||
# 所以 CI 里显式要求工具必须在场,SKIP 视为失败。
|
||||
- name: 确认 C 工具链齐备(缺则失败,不接受 SKIP)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
miss=""
|
||||
for t in cmake clang; do
|
||||
if command -v "$t" >/dev/null 2>&1; then
|
||||
echo " ✅ $t: $(command -v "$t")"
|
||||
else
|
||||
echo " ❌ $t 缺失" >&2
|
||||
miss="$miss $t"
|
||||
fi
|
||||
done
|
||||
[ -z "$miss" ] || {
|
||||
echo "::error::C 工具链缺失:$miss —— 缺工具时门禁会 SKIP 并 exit 0,等于假装通过"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── 跑门禁 ──────────────────────────────────────────────────
|
||||
# 门禁自身 0~1 秒,给它 5 分钟绰绰有余;超出说明是异常(不是慢)。
|
||||
- name: make check-csrc
|
||||
run: make check-csrc
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
set -o pipefail
|
||||
make check-csrc 2>&1 | tee /tmp/csrc.log
|
||||
|
||||
# ★ 二次确认:任何 [SKIP] 都视为失败。
|
||||
#
|
||||
# Makefile 里每道门禁在缺依赖时 `echo "[SKIP] ..."; exit 0`,
|
||||
# 这在本机是合理的(开发者未必装了交叉工具链),但在 CI 里
|
||||
# 就等于把"没验证"伪装成"通过"。上面那步已保证工具齐备,
|
||||
# 这里再拦一次是为了覆盖将来新增门禁时漏配依赖的情况。
|
||||
if grep -q '\[SKIP\]' /tmp/csrc.log; then
|
||||
echo "::error::门禁出现 [SKIP],说明有检查没真正执行:"
|
||||
grep '\[SKIP\]' /tmp/csrc.log >&2
|
||||
echo "CI 里不接受 SKIP —— 要么装上依赖,要么明确从 check-csrc 摘掉这条。" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 全部门禁实际执行,无 SKIP"
|
||||
|
||||
# ── 文档站构建(mkdocs,纯 Python,无外部依赖)──
|
||||
docs:
|
||||
|
||||
72
Makefile
72
Makefile
@ -1,3 +1,7 @@
|
||||
# 门禁脚本需要 bash 的 PIPESTATUS(/bin/sh=dash 不支持)。
|
||||
# 没有它就无法区分"编译器失败"与"head 成功" —— 实测会让门禁假通过。
|
||||
SHELL := /bin/bash
|
||||
|
||||
.PHONY: test-gui all build build-plain build-cli build-gui clean install test run build-static build-linux-arm64 lint fmt sync-client-versions check-client-versions check-starmap-shared check-client-protocol csrc csrc-test csrc-lint csrc-abi csrc-headers csrc-sanitize csrc-cross csrc-fuzz check-csrc check-csrc-full
|
||||
# HOMED_TAGS 默认带 onnxruntime:发行版**默认启用**本地向量空间(与
|
||||
# deploy/packaging/build.sh 保持一致)。
|
||||
@ -255,21 +259,71 @@ csrc-fuzz:
|
||||
# C 侧的架构相关问题(endianness 假设、指针宽度、size_t vs int 宽度、
|
||||
# -fsanitize 不可用)只有真的用目标编译器编一遍才会暴露。
|
||||
# 与 deploy/packaging/build.sh 的 arm64 目标共用同一套 CC 变量。
|
||||
#
|
||||
# ## 为什么首选 clang --target,而不是 aarch64-linux-gnu-gcc(2026-10-22)
|
||||
#
|
||||
# 这道门禁只做 `-fsyntax-only`(不链接、不运行),而 clang 自带交叉前端:
|
||||
# `clang --target=aarch64-linux-gnu` 会真的切到 aarch64 目标(`__aarch64__`
|
||||
# 宏生效、指针宽度按该架构计算),足以暴露本门禁要抓的那类问题
|
||||
# (endianness 假设、指针/size_t 宽度、不支持的 C 扩展)。
|
||||
#
|
||||
# 而装 `gcc-aarch64-linux-gnu` 需要从源拉 10MB+ 的 cpp-13 等依赖 ——
|
||||
# 实测在 CI runner 上会卡住超时(见 .github/workflows/ci.yml 的加固说明)。
|
||||
# 既然只需要语法层验证,就不该为它引入一个会卡死的下载依赖。
|
||||
#
|
||||
# 优先级:CC_ARM64(显式指定) > clang --target > aarch64-linux-gnu-gcc > SKIP
|
||||
#
|
||||
# ★ 宿主头泄漏陷阱(CI 实测):只加 -isystem 指向 aarch64 头还不够 ——
|
||||
# 宿主 /usr/include/stdint.h 会先被找到,它再 include <bits/libc-header-start.h>
|
||||
# (amd64 布局),aarch64 目标下必然找不到:
|
||||
# /usr/include/stdint.h:26: fatal error: 'bits/libc-header-start.h' file not found
|
||||
# 所以 clang-target 模式必须 -nostdinc 剃掉全部默认路径,再按
|
||||
# 「clang 内建头 → aarch64 目标头」顺序加回(见下方 ARM64_INC)。
|
||||
# explicit / cross-gcc 模式用编译器自带搜索路径,不传这些。
|
||||
#
|
||||
# ★ 两个假通过陷阱(实测踩到,勿删):
|
||||
# 1. 显式 CC_ARM64 指向不存在的命令时,`cmd | head` 的退出码是 head 的(恒 0),
|
||||
# command-not-found(127) 被吞 ⇒ 门禁显示"通过"。所以循环里改用 PIPESTATUS[0],
|
||||
# 且对显式指定的编译器先做 command -v 校验。
|
||||
# 2. 上述 PIPESTATUS 需要 bash;Make 默认 /bin/sh=dash 不支持(见文件顶 SHELL)。
|
||||
.PHONY: csrc-cross
|
||||
csrc-cross:
|
||||
@echo "== C 侧交叉编译(linux/arm64)=="
|
||||
@CC_ARM64=$${CC_ARM64:-aarch64-linux-gnu-gcc}; \
|
||||
if ! command -v $$CC_ARM64 >/dev/null 2>&1; then \
|
||||
echo " [SKIP] $$CC_ARM64 不存在(未装交叉工具链)"; exit 0; \
|
||||
fi; \
|
||||
ok=1; \
|
||||
for src in $(CSRC_SRCS); do \
|
||||
if ! $$CC_ARM64 $(CSRC_CFLAGS) -Werror -fsyntax-only $$src 2>&1 | head -20; then \
|
||||
ok=0; \
|
||||
@ok=1; \
|
||||
if [ -n "$${CC_ARM64:-}" ]; then \
|
||||
if ! command -v "$$CC_ARM64" >/dev/null 2>&1; then \
|
||||
echo " [FAIL] CC_ARM64=$$CC_ARM64 不存在"; exit 1; \
|
||||
fi; \
|
||||
CC="$$CC_ARM64"; MODE="explicit"; \
|
||||
elif command -v clang >/dev/null 2>&1; then \
|
||||
CC="clang --target=aarch64-linux-gnu"; MODE="clang-target"; \
|
||||
elif command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \
|
||||
CC="aarch64-linux-gnu-gcc"; MODE="cross-gcc"; \
|
||||
else \
|
||||
echo " [SKIP] 无 clang、无 aarch64-linux-gnu-gcc(未装任何交叉前端)"; exit 0; \
|
||||
fi; \
|
||||
\
|
||||
ARM64_INC="-nostdinc -isystem $$(clang -print-resource-dir)/include"; \
|
||||
for d in /usr/aarch64-linux-gnu/include /usr/include/aarch64-linux-gnu; do \
|
||||
[ -d "$$d" ] && ARM64_INC="$$ARM64_INC -isystem $$d"; \
|
||||
done; \
|
||||
\
|
||||
if [ "$$MODE" = "clang-target" ]; then \
|
||||
printf '#include <string.h>\nint main(void){return 0;}\n' > /tmp/_archchk.c; \
|
||||
if ! clang --target=aarch64-linux-gnu $$ARM64_INC -fsyntax-only /tmp/_archchk.c >/dev/null 2>&1; then \
|
||||
echo " [SKIP] clang 交叉前端不可用(缺 aarch64 libc 头,装 libc6-dev-arm64-cross 即可)"; exit 0; \
|
||||
fi; \
|
||||
fi; \
|
||||
\
|
||||
EXTRA_INC=""; \
|
||||
[ "$$MODE" = "clang-target" ] && EXTRA_INC="$$ARM64_INC"; \
|
||||
for src in $(CSRC_SRCS); do \
|
||||
$$CC $$EXTRA_INC $(CSRC_CFLAGS) -Werror -fsyntax-only $$src 2>&1 | head -20; \
|
||||
rc=$${PIPESTATUS[0]}; \
|
||||
if [ "$$rc" != "0" ]; then ok=0; fi; \
|
||||
done; \
|
||||
if [ "$$ok" = "1" ]; then \
|
||||
echo " $$CC_ARM64: 0 告警、编译通过 ✓($(words $(CSRC_SRCS)) 个源文件)"; \
|
||||
echo " $$CC [$$MODE]: 0 告警、编译通过 ✓($(words $(CSRC_SRCS)) 个源文件)"; \
|
||||
else \
|
||||
echo " [FAIL] arm64 交叉编译失败"; exit 1; \
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user