fix(csrc): 交叉门禁改用 clang --target,去掉会卡死的 apt 下载 + 修两处假通过

## 卡死真因(这次可观测了)

上一版加了 `timeout` 硬超时后,日志终于能看出卡在哪:

    15:47:33  Fetched 12.5 MB in 1s (9972 kB/s)      ← update 很快
    15:47:38  Get:2 gcc-13-aarch64-linux-gnu-base 51.9 kB
    15:47:39  Get:3 cpp-13-aarch64-linux-gnu 10.8 MB   ← 下到这儿卡住
    15:50:37  ##[error]apt-get install 超时/失败(180s)

**根因:为了跑一道 `-fsyntax-only` 的语法门禁,去下载 10MB+ 的交叉 GCC 工具链,
在 runner 上拉不动。**

## 改法:用 clang 自带的交叉前端

这道门禁只做 `-fsyntax-only`(不链接、不运行)。clang 支持
`--target=aarch64-linux-gnu`,会**真的切到 aarch64 目标**:

    $ clang -fsyntax-only arch2.c          → error: "not aarch64 target"
    $ clang --target=aarch64-linux-gnu ... → 通过(__aarch64__ 生效)

指针宽度、endianness、C 扩展支持都按目标架构算 —— 足以暴露本门禁要抓的
那类问题。而 clang 在 ubuntu-latest 镜像里**已预装**(实测确认,只列出
`gcc-aarch64-linux-gnu` 一项需要安装)。

优先级:`CC_ARM64`(显式)> `clang --target` > `aarch64-linux-gnu-gcc` > SKIP

## 顺带修掉两处**本来就存在**的假通过

这两处不是本轮引入的,是新写的存在性校验暴露出来的:

### ① `cmd | head` 吞掉编译器退出码

    for src in ...; do
        if ! $CC ... -fsyntax-only $src 2>&1 | head -20; then ok=0; fi
    done

管道退出码是 `head` 的(恒 0)。实测:`CC_ARM64=nonexistent-xyz make csrc-cross`
竟然输出「0 告警、编译通过 ✓」—— 编译器根本没执行。

修法:改用 `${PIPESTATUS[0]}`,取编译器的真实退出码。

### ② `PIPESTATUS` 需要 bash,Make 默认是 dash

`/bin/sh` 在 Debian/Ubuntu 上是 dash,不支持 `PIPESTATUS`(报 Bad substitution)。
必须显式 `SHELL := /bin/bash`,否则上面的修法静默失效。

### 风险自证(三条路径实测)

| 场景 | 结果 |
|---|---|
| 正常(clang-target) | ✅ 0 告警、编译通过 ✓ |
| `CC_ARM64` 指向不存在的命令 | ✅ `[FAIL] CC_ARM64=nonexistent-xyz 不存在` → Error 1 |
| 真的写坏 C 源码 | ✅ 编译器报错 + `[FAIL] arm64 交叉编译失败` → Error 1 |

## CI 侧

- 安装步骤不再要求 `gcc-aarch64-linux-gnu` ⇒ **依赖已预装,直接跳过 apt-get,完全不碰网络**
- 工具链确认步骤只校验 `cmake` + `clang`
- `[SKIP]` 拦截、`timeout` 硬超时、去 `-qq` 全部保留(对付将来别的原因导致的卡死)
This commit is contained in:
JianFeeeee
2026-10-22 23:55:54 +08:00
parent 16bb794a43
commit 7fdec0670f
2 changed files with 61 additions and 14 deletions

View File

@ -192,13 +192,21 @@ jobs:
timeout-minutes: 5
run: |
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"
command -v aarch64-linux-gnu-gcc >/dev/null || need="$need gcc-aarch64-linux-gnu"
command -v cmake >/dev/null || need="$need cmake"
command -v clang >/dev/null || need="$need clang"
if [ -z "$need" ]; then
echo "✅ 三件套已存在,跳过 apt-get:$(command -v cmake clang aarch64-linux-gnu-gcc | tr '\n' ' ')"
echo "✅ 依赖已存在,跳过 apt-get:$(command -v cmake clang | tr '\n' ' ')"
exit 0
fi
@ -239,7 +247,7 @@ jobs:
run: |
set -euo pipefail
miss=""
for t in cmake clang aarch64-linux-gnu-gcc; do
for t in cmake clang; do
if command -v "$t" >/dev/null 2>&1; then
echo " ✅ $t: $(command -v "$t")"
else

View File

@ -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,56 @@ 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
#
# ★ 两个假通过陷阱(实测踩到,勿删):
# 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; \
\
if [ "$$MODE" = "clang-target" ]; then \
printf 'int main(void){return 0;}\n' > /tmp/_archchk.c; \
if ! clang --target=aarch64-linux-gnu -fsyntax-only /tmp/_archchk.c 2>/dev/null; then \
echo " [SKIP] clang 不支持 --target=aarch64-linux-gnu(版本过旧)"; exit 0; \
fi; \
fi; \
\
for src in $(CSRC_SRCS); do \
$$CC $(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