Files
MeowOS-For-R3P2/scripts/build/test_build_devkit.sh
T
AskaEth 80c0bd4eb4 feat(m53): 自写 timeout(1) 并接入 devkit 本地源码构建路径
设备没有 timeout,busybox 1.31 也无该 applet;2026-11-11 18:44 一条挂在
硬件上的 bluetoothctl 累积 55 个残留进程耗尽进程表,dropbear 无法 fork,
整机在线却不可登录。本单给设备一个可用的 timeout。

- scripts/build/devkit-src/timeout.c(新,Apache-2.0,489 行)
  独立实现,未拷贝 busybox/GPL 代码;fork+setpgid(0,0) 后 kill(-pgid),
  孙进程一并终止;waitpid 回收直接子进程不留僵尸;-s/-k 两阶段终止;
  退出码 124/137/128+N/125/126/127 对齐 coreutils 语义。
  计时不用 timerfd(musl 的 timerfd_settime 在 4.4 上静默失效,实测
  timeout 2 sleep 30 等满 30s 才返回),改用 setitimer(ITIMER_REAL)+SIGALRM,
  与 SIGCHLD 一起进 signalfd,由一次 ppoll 等待,零轮询、不读时钟。
- build_devkit.sh:新增 build_local(B 路径,不碰上游 procps/lsof/ncurses
  流程);TOOLS/TOOL_NAMES/MANIFEST/licenses/stamp/--with-sources 接入;
  产物同时标注 Apache-2.0 并随附许可证原文。
- test_build_devkit.sh:T9(产物静态/架构/零 NEEDED/MANIFEST/license)、
  T10(源码纪律:独立实现、不调 clock_gettime、不用 timerfd、进程组模型)。
- DEVKIT.md/README.md:产物表、PATH 实测(ssh 非登录 shell 无 opt/bin,
  且设备无 bash → 用 sh -lc 或绝对路径)、禁裸跑纪律与 18:44 事故时间线、
  timerfd 静默失效的第二类 time64 坑、2026-11-11 真机验收原始输出。
2026-11-11 21:23:52 +08:00

172 lines
8.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================================
# test_build_devkit.sh — build_devkit.sh 的回归测试(不重编)
#
# 耗时:热缓存约 15 秒;若需冷启动基线重建(首次运行/源码未缓存)约 1–2 分钟。
#
# 覆盖:
# r1 P1 回归点 —— stamp 命中(跳过编译)时,--with-sources 必须真正产出 src/
# r2 P2-1 —— licenses/README 被删后 --with-sources 必须恢复,
# --verify-only 必须拒绝缺许可证的交付
# MANIFEST 内容漂移检测、src/ 缺文件与内容漂移自愈
# M53 —— 本地源码路径:timeout 产物存在/静态/MIPS32r2/零 NEEDED、
# MANIFEST 有 Apache-2.0 行、license 原文随附、
# 源码纪律(独立实现、不调 clock_gettime、不用 timerfd、setpgid+kill(-pgid))
#
# 前置:MEOW_DEVKIT_WORK 里已有一次构建(脚本会先跑一次增量构建,命中则秒回;
# 缓存缺失时会联网下载/编译,第一次运行约 1 分钟)。
#
# 用法:bash scripts/build/test_build_devkit.sh [-v]
# 退出码 0 = 全过;非 0 = 用例失败数。
# ============================================================================
set -u
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT/scripts/build/build_devkit.sh"
export MEOW_DEVKIT_WORK="${MEOW_DEVKIT_WORK:-$HOME/.cache/meow-devkit}"
OUT="$ROOT/fw_apps/devkit"
TMP="$MEOW_DEVKIT_WORK/test-tmp"
VERBOSE=0
[ "${1:-}" = "-v" ] && VERBOSE=1
PASS=0
FAILN=0
pass() { PASS=$((PASS + 1)); printf 'PASS %s\n' "$*"; }
fail() { FAILN=$((FAILN + 1)); printf 'FAIL %s\n' "$*"; }
run() { # run <说明> <命令...>
local desc="$1"; shift
local log="$TMP/last.log"
if "$@" >"$log" 2>&1; then
[ "$VERBOSE" = 1 ] && sed 's/^/ | /' "$log"
return 0
fi
printf ' 命令失败: %s\n' "$*"
sed 's/^/ | /' "$log" | tail -20
return 1
}
mkdir -p "$TMP"
# 无论成败都恢复现场,别把测试的破坏留在产物目录里
restore() {
[ -f "$TMP/MANIFEST.txt.bak" ] && cp "$TMP/MANIFEST.txt.bak" "$OUT/MANIFEST.txt"
rm -rf "$TMP"
}
trap restore EXIT
echo "== 0) 确保基线构建(幂等;已构建则秒回)"
run "基线构建" bash "$SCRIPT" || { echo "!!! 基线构建失败,后续用例无意义"; exit 1; }
pass "基线构建"
echo "== 1) P1 回归:stamp 命中 + --with-sources 必须产出 src/"
rm -rf "$OUT/src"
run "普通重跑(应命中 stamp 且不建 src/)" bash "$SCRIPT" || fail "T1a 普通重跑"
[ ! -d "$OUT/src" ] && pass "T1a 普通重跑不创建 src/(符合预期)" || fail "T1a 普通重跑意外创建了 src/"
run "--with-sources(stamp 命中路径)" bash "$SCRIPT" --with-sources || fail "T1b --with-sources 退出码非 0"
for f in ncurses-6.5.tar.gz procps-ng-4.0.4.tar.xz lsof-4.99.4.tar.xz build_devkit.sh DEVKIT.md timeout.c; do
[ -s "$OUT/src/$f" ] && pass "T1b src/$f 存在且非空" || fail "T1b 缺 src/$f(P1 未修好)"
done
echo "== 2) 缺文件自愈:删掉构建脚本后再 --with-sources"
rm -f "$OUT/src/build_devkit.sh"
run "--with-sources 补齐" bash "$SCRIPT" --with-sources || fail "T2 --with-sources 退出码非 0"
[ -s "$OUT/src/build_devkit.sh" ] && pass "T2 src/build_devkit.sh 被补回" || fail "T2 未补回 src/build_devkit.sh"
echo "== 3) 内容漂移自愈:把 src 里的源码包改坏"
printf 'tamper\n' >> "$OUT/src/ncurses-6.5.tar.gz"
run "--with-sources 重铺" bash "$SCRIPT" --with-sources || fail "T3 --with-sources 退出码非 0"
if cmp -s "$OUT/src/ncurses-6.5.tar.gz" "$MEOW_DEVKIT_WORK/src/ncurses-6.5.tar.gz"; then
pass "T3 被改坏的源码包已恢复成钉住版本"
else
fail "T3 源码包未恢复"
fi
echo "== 4) --verify-only:正常通过"
run "--verify-only" bash "$SCRIPT" --verify-only && pass "T4a --verify-only 通过" || fail "T4a --verify-only 失败"
echo "== 5) --verify-only:能查出 MANIFEST 内容漂移"
cp "$OUT/MANIFEST.txt" "$TMP/MANIFEST.txt.bak"
awk 'BEGIN{done=0} { if (!done && $1=="vmstat") { $NF="0000000000000000000000000000000000000000000000000000000000000000"; done=1 } print }' \
"$TMP/MANIFEST.txt.bak" > "$OUT/MANIFEST.txt"
if run "--verify-only(MANIFEST 被篡改)" bash "$SCRIPT" --verify-only; then
fail "T5 --verify-only 未发现 MANIFEST sha 被篡改"
else
pass "T5 --verify-only 正确拒绝被篡改的 MANIFEST"
fi
cp "$TMP/MANIFEST.txt.bak" "$OUT/MANIFEST.txt"
run "--verify-only(恢复后)" bash "$SCRIPT" --verify-only && pass "T5b 恢复后重新通过" || fail "T5b 恢复后仍失败"
echo "== 6) 交付完整性:删 licenses/ 后 --with-sources 必须恢复"
LICENSES="procps-ng-4.0.4-COPYING procps-ng-4.0.4-COPYING.LIB ncurses-6.5-COPYING lsof-4.99.4-COPYING musl-COPYRIGHT meowos-timeout-LICENSE"
rm -rf "$OUT/licenses"
run "--with-sources 补 licenses" bash "$SCRIPT" --with-sources || fail "T6 --with-sources 退出码非 0"
for f in $LICENSES; do
[ -s "$OUT/licenses/$f" ] && pass "T6 licenses/$f 已恢复" || fail "T6 缺 licenses/$f"
done
echo "== 7) --verify-only 必须拒绝缺 licenses 的交付"
rm -rf "$OUT/licenses"
if run "--verify-only(缺 licenses)" bash "$SCRIPT" --verify-only; then
fail "T7 --verify-only 未发现 licenses/ 缺失"
else
pass "T7 --verify-only 正确拒绝缺 licenses 的交付"
fi
run "--with-sources 恢复" bash "$SCRIPT" --with-sources || fail "T7 --with-sources 恢复失败"
run "--verify-only(恢复后)" bash "$SCRIPT" --verify-only && pass "T7b 恢复后通过" || fail "T7b 恢复后仍失败"
echo "== 8) 交付完整性:删 README.txt 后 --with-sources 必须恢复"
rm -f "$OUT/README.txt"
run "--with-sources 补 README" bash "$SCRIPT" --with-sources || fail "T8 --with-sources 退出码非 0"
[ -s "$OUT/README.txt" ] && pass "T8 README.txt 已恢复" || fail "T8 README.txt 未恢复"
echo "== 9) timeout(本地源码路径):产物存在 + 静态/架构/零 NEEDED"
if [ -f "$OUT/bin/timeout" ]; then
pass "T9a bin/timeout 存在"
DESC="$(file -b "$OUT/bin/timeout")"
case "$DESC" in *"statically linked"*) pass "T9b 静态链接" ;; *) fail "T9b 非静态: $DESC" ;; esac
case "$DESC" in *"MIPS32 rel2"*) pass "T9c MIPS32 rel2" ;; *) fail "T9c 架构不符: $DESC" ;; esac
[ "$(readelf -d "$OUT/bin/timeout" 2>/dev/null | grep -c NEEDED)" = 0 ] \
&& pass "T9d readelf -d 零 NEEDED" || fail "T9d 有 NEEDED 依赖"
readelf -h "$OUT/bin/timeout" | grep -q mips32r2 \
&& pass "T9e ELF Flags 含 mips32r2" || fail "T9e ELF Flags 无 mips32r2"
else
fail "T9a 缺 $OUT/bin/timeout(本地源码没编进来)"
fi
ROW="$(awk '$1=="timeout" && $(NF-1) ~ /^[0-9]+$/ && length($NF)==64' "$OUT/MANIFEST.txt")"
if [ -n "$ROW" ]; then
pass "T9f MANIFEST 有 timeout 表格行(sha256 + 体积)"
case "$ROW" in *Apache-2.0*) pass "T9g MANIFEST 标注 Apache-2.0" ;; *) fail "T9g MANIFEST 未标 Apache-2.0: $ROW" ;; esac
else
fail "T9f MANIFEST 缺 timeout 表格行"
fi
head -2 "$OUT/licenses/meowos-timeout-LICENSE" 2>/dev/null | grep -q 'Apache License' \
&& pass "T9h licenses/meowos-timeout-LICENSE 是 Apache-2.0 原文" \
|| fail "T9h 缺 Apache-2.0 许可证原文"
echo "== 10) timeout 源码纪律:本仓独立实现,且不碰坏掉的 clock_gettime"
SRC_T="$ROOT/scripts/build/devkit-src/timeout.c"
grep -q 'SPDX-License-Identifier: Apache-2.0' "$SRC_T" \
&& pass "T10a 源码带 Apache-2.0 SPDX 头" || fail "T10a 源码缺 Apache-2.0 SPDX 头"
grep -q '独立实现,未拷贝' "$SRC_T" \
&& pass "T10b 源码声明独立实现、未拷贝 GPL 代码" || fail "T10b 缺独立实现声明"
if grep -nE 'clock_gettime|gettimeofday' "$SRC_T" | grep -qvE '^[0-9]+:[[:space:]]*(/\*|\*|//)'; then
fail "T10c 源码调用了 clock_gettime/gettimeofday(4.4 内核上 time64 ABI 错配)"
else
pass "T10c 源码不调用 clock_gettime/gettimeofday(只在注释里提及)"
fi
if grep -nE 'timerfd_settime|timerfd_create' "$SRC_T" | grep -qvE '^[0-9]+:[[:space:]]*(/\*|\*|//)'; then
fail "T10e 源码用了 timerfd(musl 的 timerfd_settime 在 4.4 上静默失效,必须用 setitimer)"
else
pass "T10e 源码不用 timerfd(超时走 setitimer+SIGALRM)"
fi
grep -q 'setitimer(ITIMER_REAL' "$SRC_T" && grep -q 'SIGALRM' "$SRC_T" \
&& pass "T10f 超时机制是 setitimer(ITIMER_REAL)+SIGALRM" \
|| fail "T10f 缺 setitimer/SIGALRM 超时机制"
grep -q 'setpgid' "$SRC_T" && grep -q 'kill(-' "$SRC_T" \
&& pass "T10d 进程组模型到位(setpgid + kill(-pgid))" \
|| fail "T10d 缺进程组模型(孙进程会漏杀)"
echo
printf '== 结果: %d 通过, %d 失败\n' "$PASS" "$FAILN"
[ "$FAILN" = 0 ]