P2-1 时长解析:后缀乘数只作用在整数部分,nsec 没跟着乘。 0.05m 被算成 50ms(应 3s)、1.5m 算成 60.5s(应 90s)、0.5h 算成 0.5s。 修法:sec 与 nsec 都乘 mult(各自带溢出检查,mult==1 行为不变)。 --help 与头注释的示例改为逐个标出等价值(2.5s=2.5s、1.5m=90s、0.5h=1800s), 不再留文档写着、实际算错的例子。 新增 T11:宿主上编译 parser 单测(27 例,含 0.01m/0.05m/1.5m/0.5h)真跑, 并单独点名评审抓到的三条,防止回归。 P2-2 文档数字:§2 产物表 47 708 → 49 676,合计 2 189 472 → 2 191 440; 全表按 MANIFEST 重核(其余七项未漂移),§11.1 补上新的体积/md5/sha256。 P2-3 信号失败不静默:新增 kill_child()(kill(-pgid) 失败时 ESRCH 视为组已消失, 退化为 kill(pid),EPERM 等明确告警)、verify_process_group()(发信号前用 getpgid 核对建组是否生效)、父进程 setpgid 的异常 errno 告警;连兜底 kill 都失败时退出码 125 而不是谎报 124。新增 -DTIMEOUT_TEST_SETPGID_FAIL 故障注入 与 T12(a-d)验证告警路径可达且兜底真的杀掉子进程。 真机复验(md5 aded39e0,49 676 B):124/137/0/孙进程清零/保证挂住的命令 3.01s 被 124 结束/0.01m=0.61s、0.05m=3.01s、2.5s=2.51s/无参 125;收工 bt=0 sleep=0, Music 199164544。回归 44 通过 0 失败。
776 lines
41 KiB
Bash
Executable File
776 lines
41 KiB
Bash
Executable File
#!/bin/bash
|
||
# ============================================================================
|
||
# build_devkit.sh — 交叉编译静态 MIPS32r2 调试工具集(TF 卡 MeowOS/opt/devkit)
|
||
#
|
||
# 为什么:设备(HiBy R3 Pro II / Ingenic X1600 / Linux 4.4.94 / MIPS32r2)上
|
||
# busybox 提供的 free/ps/lsof 字段不全(都是 busybox 软链),且没有 vmstat/slabtop。
|
||
# 内存分析时最缺 slabtop(拆 Slab 占用)与 vmstat(系统级趋势)。本脚本用 zig 自带
|
||
# musl 交叉编出完全静态的二进制,放进 TF 卡供开发调试。
|
||
#
|
||
# 产物(全部静态链接、strip 过、无 NEEDED):
|
||
# fw_apps/devkit/bin/{vmstat,slabtop,free,ps,pmap,sysctl,lsof,timeout}
|
||
# fw_apps/devkit/MANIFEST.txt 版本/许可证/体积/校验摘要
|
||
# fw_apps/devkit/licenses/ 各上游 COPYING(分发留痕)
|
||
# fw_apps/devkit/README.txt 上卡后怎么加 PATH、和 busybox 的同名关系
|
||
# (--with-sources 时另有 fw_apps/devkit/src/:三份源码包 + 本脚本 +
|
||
# DEVKIT.md + timeout.c,GPLv2 §3 的“对应源码”含构建控制脚本)
|
||
#
|
||
# 两条编译路径(互不干扰):
|
||
# A. 上游包(procps-ng / lsof / ncurses)—— 固定版本 + sha256 + 缓存
|
||
# B. 本地源码(scripts/build/devkit-src/*.c)—— 本仓 Apache-2.0 独立实现
|
||
# timeout.c 就是 B 路径:设备没有 timeout(1),busybox 1.31 也无该 applet,
|
||
# 而设备曾因一条挂死的外部命令(bluetoothctl)耗尽进程表、fork 失效、
|
||
# SSH 完全不可达(2026-11-11 18:44)。B 路径不联网、不打补丁、不改上游流程。
|
||
#
|
||
# 源码固定为「版本 + sha256 + 固定 URL」,全部校验后才编译;缓存命中则零联网。
|
||
#
|
||
# 工具链:zig cc --target=mipsel-linux-musleabi(需 zig 0.17.x)
|
||
# 不依赖 ~/.tools 下的私有脚本,自己生成 wrapper($WORK/bin/zigcc)。
|
||
# 不需要内核头(musl 头足够;只有 lsof 缺 11 个 TCP 状态宏,本脚本 -D 补上)。
|
||
# ⚠ 该工具链在 4.4 内核上是 time64 ABI 错配:`vmstat -t` 时间戳列会是空,
|
||
# 原因/证据/后续修法见 DEVKIT.md「已知限制:工具链 time64」。不是 -D 能修的。
|
||
# 同一坑的第二个表现:musl 的 timerfd_settime() 静默失效(返回 0 但不计时)。
|
||
# timeout 因此既不读时钟也不用 timerfd:超时用 setitimer(ITIMER_REAL)+SIGALRM,
|
||
# 子进程退出用 SIGCHLD,两者进同一个 signalfd,由一次 ppoll 等(零轮询)。
|
||
#
|
||
# 许可证(二进制将来随卡分发时请保留此节):
|
||
# procps-ng 4.0.4 GPL-2.0-or-later(COPYING=GPLv2)
|
||
# *** 分发二进制必须提供对应源码(GPLv2 §3,含构建脚本)***
|
||
# procps library/ LGPL-2.1+:ps/vmstat/slabtop/free/pmap/sysctl 静态链接 libproc2,
|
||
# LGPL-2.1 §6 要求提供可重链接 object code 或书面要约(见 DEVKIT.md §6)
|
||
# ncurses 6.5 MIT 风格(X11 许可),保留版权声明即可
|
||
# lsof 4.99.4 Purdue Research Foundation 许可(BSD 风格 4 条),
|
||
# 要求文档与源码中保留作者与 Purdue 的署名(COPYING 第 2 条)
|
||
# timeout Apache-2.0(本仓自有,scripts/build/devkit-src/timeout.c)
|
||
# 独立实现,未拷贝 busybox/GPL 代码;许可证原文随产物分发
|
||
# 本脚本链接的 ncurses 只作为 slabtop 的静态依赖进入产物,不单独分发。
|
||
#
|
||
# 用法:
|
||
# bash scripts/build/build_devkit.sh # 增量:缓存命中则不联网
|
||
# bash scripts/build/build_devkit.sh --force # 强制重编
|
||
# bash scripts/build/build_devkit.sh --clean # 清空工作区与 out/src 后重编
|
||
# bash scripts/build/build_devkit.sh --offline # 只用已缓存的源码包(CI/离线)
|
||
# bash scripts/build/build_devkit.sh --with-sources
|
||
# # 整理 src/(三份源码包 + 本脚本 + DEVKIT.md)。**产物已是最新时也会执行**,
|
||
# # 且整理后按 sha256 复核;任何一步失败即非零退出(P1 回归点)。
|
||
# bash scripts/build/build_devkit.sh --verify-only
|
||
# # 只对现有产物自检:静态/架构/NEEDED + MANIFEST 逐条 sha256/体积比对。
|
||
# # 只需 file / readelf / sha256sum / awk;不需要 zig、make、date、网络。
|
||
#
|
||
# 环境变量:
|
||
# MEOW_DEVKIT_WORK 工作区(源码包/编译树/prefix),默认 ~/.cache/meow-devkit
|
||
# MEOW_DEVKIT_JOBS 并行度,默认 nproc
|
||
# ============================================================================
|
||
set -eu
|
||
|
||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||
SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
|
||
OUT_DIR="$ROOT/fw_apps/devkit"
|
||
BIN_DIR="$OUT_DIR/bin"
|
||
WORK="${MEOW_DEVKIT_WORK:-$HOME/.cache/meow-devkit}"
|
||
SRC_DIR="$WORK/src"
|
||
BUILD_DIR="$WORK/build"
|
||
PREFIX="$WORK/prefix"
|
||
WRAP_DIR="$WORK/bin"
|
||
JOBS="${MEOW_DEVKIT_JOBS:-$(nproc 2>/dev/null || echo 4)}"
|
||
|
||
ZIG_TARGET="mipsel-linux-musleabi"
|
||
ZIG_WANT="0.17" # 已验证;0.18+ 参数可能变,见 check_toolchain
|
||
|
||
NCURSES_VER="6.5"
|
||
PROCPS_VER="4.0.4"
|
||
LSOF_VER="4.99.4"
|
||
|
||
# 本地源码(B 路径):本仓 Apache-2.0 独立实现,不下载、不 patch、不进上游流程。
|
||
# 每个 *.c 编一个同名可执行文件;新增工具只需丢一个 .c 进这个目录。
|
||
LOCAL_SRC_DIR="$ROOT/scripts/build/devkit-src"
|
||
LOCAL_TOOLS="timeout"
|
||
|
||
# 设备内核版本:写进 lsof 的 LINUXV,让它按目标内核(4.4)选代码路径
|
||
TARGET_KERNEL_VSTR="4.4.94"
|
||
|
||
# name|url|sha256 (sha256 是“哪份源码”的唯一权威,URL 只是取法之一)
|
||
SRC_NCURSES="ncurses-${NCURSES_VER}.tar.gz|https://mirrors.tuna.tsinghua.edu.cn/gnu/ncurses/ncurses-${NCURSES_VER}.tar.gz|136d91bc269a9a5785e5f9e980bc76ab57428f604ce3e5a5a90cebc767971cc6"
|
||
SRC_PROCPS="procps-ng-${PROCPS_VER}.tar.xz|https://downloads.sourceforge.net/project/procps-ng/Production/procps-ng-${PROCPS_VER}.tar.xz|22870d6feb2478adb617ce4f09a787addaf2d260c5a8aa7b17d889a962c5e42e"
|
||
SRC_LSOF="lsof-${LSOF_VER}.tar.xz|https://deb.debian.org/debian/pool/main/l/lsof/lsof_${LSOF_VER}+dfsg.orig.tar.xz|dc3e7f51cf03bc7fbcdd9e62d4a52825682092031bd1ae08b15fe7f4f582aebf"
|
||
|
||
# 卡上 src/ 必须包含的文件:三份源码包 + 构建脚本 + 本文档 + 本地源码。
|
||
# GPLv2 §3 的“对应源码”明确包含 "scripts used to control compilation and
|
||
# installation",缺了脚本,收卡人拿不到本轮用的 -D/configure 参数。
|
||
# timeout.c 不在 GPL 义务范围内(Apache-2.0 自有),但带上才能复现整包。
|
||
SRC_STAGE_LIST="ncurses-${NCURSES_VER}.tar.gz procps-ng-${PROCPS_VER}.tar.xz lsof-${LSOF_VER}.tar.xz build_devkit.sh DEVKIT.md timeout.c"
|
||
|
||
# 交付的工具——验证、MANIFEST 比对、stamp 自检都从这里取,避免多处手写漂移
|
||
TOOL_NAMES="vmstat slabtop free ps pmap sysctl lsof $LOCAL_TOOLS"
|
||
|
||
# licenses/ 下必须存在的许可证副本(缺一个都算交付不完整):
|
||
# procps 是 GPL-2.0+(COPYING)且 library/ 是 LGPL-2.1+(COPYING.LIB),
|
||
# ncurses/lsof 是 MIT 风格 / Purdue,musl 是 MIT 且七个产物都静态链入了它。
|
||
# timeout 是本仓 Apache-2.0 自有实现,分发二进制时按 Apache-2.0 §4 附许可证原文。
|
||
LICENSE_FILES="procps-ng-${PROCPS_VER}-COPYING procps-ng-${PROCPS_VER}-COPYING.LIB ncurses-${NCURSES_VER}-COPYING lsof-${LSOF_VER}-COPYING musl-COPYRIGHT meowos-timeout-LICENSE"
|
||
|
||
FORCE=0
|
||
CLEAN=0
|
||
OFFLINE=0
|
||
WITH_SOURCES=0
|
||
VERIFY_ONLY=0
|
||
DOWNLOADS=0
|
||
|
||
log() { printf '\n== %s\n' "$*"; }
|
||
info() { printf ' %s\n' "$*"; }
|
||
die() { printf '\n!!! %s\n' "$*" >&2; exit 1; }
|
||
|
||
usage() {
|
||
sed -n '3,/^# ============/p' "$SCRIPT_PATH" | sed 's/^# \{0,1\}//'
|
||
}
|
||
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
-f|--force) FORCE=1 ;;
|
||
--clean) CLEAN=1 ;;
|
||
--offline) OFFLINE=1 ;;
|
||
--with-sources) WITH_SOURCES=1 ;;
|
||
--verify-only) VERIFY_ONLY=1 ;;
|
||
-h|--help) usage; exit 0 ;;
|
||
*) die "未知参数: $1(-h 看用法)" ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 0) 工具链与宿主依赖
|
||
# ---------------------------------------------------------------------------
|
||
check_toolchain() {
|
||
local zig_bin major_minor
|
||
zig_bin="$(command -v zig || true)"
|
||
[ -n "$zig_bin" ] || die "找不到 zig。装法:pkg install zig(需 0.17.x)"
|
||
ZIG_VERSION="$("$zig_bin" version)"
|
||
major_minor="${ZIG_VERSION%.*}"
|
||
[ "$major_minor" = "$ZIG_WANT" ] || \
|
||
die "zig 版本是 $ZIG_VERSION,本脚本只在 $ZIG_WANT.x 上验证过(target/静态链接行为可能变)"
|
||
|
||
mkdir -p "$WRAP_DIR"
|
||
cat > "$WRAP_DIR/zigcc" <<EOF
|
||
#!/bin/sh
|
||
exec "$zig_bin" cc --target=$ZIG_TARGET "\$@"
|
||
EOF
|
||
chmod +x "$WRAP_DIR/zigcc"
|
||
# 统一用 PATH 上的裸名 "zigcc" 当 CC:这样二进制里不会嵌进绝对工具链路径
|
||
# (lsof 会把 ${CC} 写进 version.h),换机器/换工作区也能逐字节复现。
|
||
PATH="$WRAP_DIR:$PATH"; export PATH
|
||
ZIGCC="zigcc"
|
||
info "zig $ZIG_VERSION → $WRAP_DIR/zigcc"
|
||
|
||
# 七个产物都静态链入了 zig 自带 musl(MIT),其 COPYRIGHT 要求随副本保留,
|
||
# 所以构建时必须能拿到它,拿不到就明确失败(不能交付“缺许可证”的包)。
|
||
ZIG_LIB_DIR="$("$zig_bin" env 2>/dev/null | sed -n 's/.*\.lib_dir *= *"\(.*\)".*/\1/p')"
|
||
MUSL_COPYRIGHT="${ZIG_LIB_DIR}/libc/musl/COPYRIGHT"
|
||
[ -s "$MUSL_COPYRIGHT" ] || die "找不到 musl 的 COPYRIGHT(zig lib_dir=$ZIG_LIB_DIR),无法留痕许可证"
|
||
info "musl COPYRIGHT → $MUSL_COPYRIGHT"
|
||
|
||
for t in make curl tar sha256sum file readelf; do
|
||
command -v "$t" >/dev/null 2>&1 || die "缺宿主工具: $t"
|
||
done
|
||
# strip 必须能处理 ELF32/MIPS:优先 llvm-strip(zig 自带同源 llvm),
|
||
# GNU strip 来自 binutils 一般也支持,但 Android 自带的那份不保证。
|
||
if command -v llvm-strip >/dev/null 2>&1; then
|
||
STRIP=llvm-strip
|
||
elif command -v "$zig_bin" >/dev/null 2>&1 && [ -x "$(dirname "$zig_bin")/llvm-strip" ]; then
|
||
STRIP="$(dirname "$zig_bin")/llvm-strip"
|
||
else
|
||
STRIP=strip
|
||
fi
|
||
info "strip → $STRIP"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 1) 源码获取(固定版本 + sha256;缓存命中不联网)
|
||
# ---------------------------------------------------------------------------
|
||
fetch_src() {
|
||
local spec="$1" name url sha got
|
||
name="${spec%%|*}"; spec="${spec#*|}"
|
||
url="${spec%%|*}"; sha="${spec#*|}"
|
||
if [ -f "$SRC_DIR/$name" ]; then
|
||
got="$(sha256sum "$SRC_DIR/$name" | cut -d' ' -f1)"
|
||
if [ "$got" = "$sha" ]; then
|
||
info "源码已缓存: $name"
|
||
return 0
|
||
fi
|
||
info "缓存校验不符,删除重下: $name"
|
||
rm -f "$SRC_DIR/$name"
|
||
fi
|
||
[ "$OFFLINE" = 1 ] && die "离线模式但缺少源码: $SRC_DIR/$name"
|
||
info "下载 $name"
|
||
curl -fL --retry 3 --connect-timeout 20 -o "$SRC_DIR/$name.part" "$url" \
|
||
|| { rm -f "$SRC_DIR/$name.part"; die "下载失败: $url"; }
|
||
got="$(sha256sum "$SRC_DIR/$name.part" | cut -d' ' -f1)"
|
||
[ "$got" = "$sha" ] || { rm -f "$SRC_DIR/$name.part"; die "sha256 不符: $name(期望 $sha 实得 $got)"; }
|
||
mv "$SRC_DIR/$name.part" "$SRC_DIR/$name"
|
||
DOWNLOADS=$((DOWNLOADS + 1))
|
||
}
|
||
|
||
unpack() { # unpack <tarball> <destdir>
|
||
rm -rf "$2"; mkdir -p "$2"
|
||
tar xf "$SRC_DIR/$1" -C "$2" --strip-components=1
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 1b) 交付源码包(--with-sources):卡上 src/ 的完整性与整理
|
||
# P1 回归点:stamp 命中时也必须走这里,绝不能因为“跳过编译”就把源码静默漏掉。
|
||
# ---------------------------------------------------------------------------
|
||
expected_sha_for() { # expected_sha_for <tarball名> → 钉住的 sha256
|
||
case "$1" in
|
||
"ncurses-${NCURSES_VER}.tar.gz") printf '%s' "$SRC_NCURSES" | awk -F'|' '{print $3}' ;;
|
||
"procps-ng-${PROCPS_VER}.tar.xz") printf '%s' "$SRC_PROCPS" | awk -F'|' '{print $3}' ;;
|
||
"lsof-${LSOF_VER}.tar.xz") printf '%s' "$SRC_LSOF" | awk -F'|' '{print $3}' ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
sources_complete() {
|
||
local f got
|
||
[ -d "$OUT_DIR/src" ] || return 1
|
||
for f in $SRC_STAGE_LIST; do
|
||
[ -s "$OUT_DIR/src/$f" ] || return 1
|
||
done
|
||
# 三份源码包按钉住的 sha 逐一核对(版本漂移比缺文件更危险:看起来有源码,其实对不上)
|
||
for f in "ncurses-${NCURSES_VER}.tar.gz" "procps-ng-${PROCPS_VER}.tar.xz" "lsof-${LSOF_VER}.tar.xz"; do
|
||
got="$(sha256sum "$OUT_DIR/src/$f" | cut -d' ' -f1)"
|
||
[ "$got" = "$(expected_sha_for "$f")" ] || return 1
|
||
done
|
||
cmp -s "$SCRIPT_PATH" "$OUT_DIR/src/build_devkit.sh" || return 1
|
||
cmp -s "$ROOT/scripts/build/DEVKIT.md" "$OUT_DIR/src/DEVKIT.md" || return 1
|
||
# 本地源码也要与仓库逐字节一致:src/ 里的 timeout.c 必须能编出同一个二进制
|
||
cmp -s "$LOCAL_SRC_DIR/timeout.c" "$OUT_DIR/src/timeout.c" || return 1
|
||
return 0
|
||
}
|
||
|
||
stage_sources() {
|
||
local f
|
||
log "整理 $OUT_DIR/src(三份源码包 + 本地源码 + 构建脚本 + 文档)"
|
||
fetch_src "$SRC_NCURSES"
|
||
fetch_src "$SRC_PROCPS"
|
||
fetch_src "$SRC_LSOF"
|
||
rm -rf "$OUT_DIR/src"
|
||
mkdir -p "$OUT_DIR/src"
|
||
for f in "ncurses-${NCURSES_VER}.tar.gz" "procps-ng-${PROCPS_VER}.tar.xz" "lsof-${LSOF_VER}.tar.xz"; do
|
||
cp "$SRC_DIR/$f" "$OUT_DIR/src/" || die "复制源码包失败: $f"
|
||
done
|
||
cp "$SCRIPT_PATH" "$OUT_DIR/src/build_devkit.sh" \
|
||
|| die "复制构建脚本失败: $SCRIPT_PATH(GPLv2 §3 要求带构建脚本)"
|
||
cp "$ROOT/scripts/build/DEVKIT.md" "$OUT_DIR/src/DEVKIT.md" \
|
||
|| die "复制 DEVKIT.md 失败: $ROOT/scripts/build/DEVKIT.md"
|
||
cp "$LOCAL_SRC_DIR/timeout.c" "$OUT_DIR/src/timeout.c" \
|
||
|| die "复制本地源码失败: $LOCAL_SRC_DIR/timeout.c(Apache-2.0,复现 timeout 需要)"
|
||
sources_complete || die "src/ 整理后仍不完整(sha256 或文件缺失)"
|
||
info "共 $(du -sh "$OUT_DIR/src" | cut -f1):$(ls "$OUT_DIR/src" | tr '\n' ' ')"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 2) ncurses:只为 slabtop 提供静态 ncursesw(不单独分发)
|
||
# procps 的 slabtop/top 硬依赖 ncurses(configure 里 --without-ncurses 会把
|
||
# 它们整个跳过),所以这是编 slabtop 的必要成本,不是为 htop 顺手加的。
|
||
# terminfo 目录必须指向设备真实路径(/usr/share/terminfo),否则交互式
|
||
# slabtop 会 "Error opening terminal":默认值是编译时 prefix,设备上没有。
|
||
# slabtop -o(一次性输出)不初始化 curses,本来不受影响。
|
||
# ---------------------------------------------------------------------------
|
||
build_ncurses() {
|
||
log "编译 ncurses $NCURSES_VER(静态 widec,无 progs/man/tests)"
|
||
unpack "ncurses-${NCURSES_VER}.tar.gz" "$BUILD_DIR/ncurses-src"
|
||
rm -rf "$BUILD_DIR/ncurses"; mkdir -p "$BUILD_DIR/ncurses"
|
||
(
|
||
cd "$BUILD_DIR/ncurses"
|
||
CC="$ZIGCC" BUILD_CC=cc CFLAGS="-O2 -ffile-prefix-map=$BUILD_DIR/ncurses-src=/ncurses-$NCURSES_VER" \
|
||
"$BUILD_DIR/ncurses-src/configure" \
|
||
--host="$ZIG_TARGET" --prefix="$PREFIX/ncurses" \
|
||
--without-shared --with-normal --without-debug --without-profile \
|
||
--without-manpages --without-tests --without-ada \
|
||
--without-cxx --without-cxx-binding --without-progs --without-tack \
|
||
--enable-widec --disable-db-install \
|
||
--enable-pc-files --with-pkg-config-libdir="$PREFIX/ncurses/lib/pkgconfig" \
|
||
--with-default-terminfo-dir=/usr/share/terminfo \
|
||
--with-terminfo-dirs="/usr/share/terminfo:/etc/terminfo:/lib/terminfo" \
|
||
> "$BUILD_DIR/ncurses-configure.log" 2>&1 \
|
||
|| { tail -20 "$BUILD_DIR/ncurses-configure.log"; die "ncurses configure 失败"; }
|
||
make -j"$JOBS" > "$BUILD_DIR/ncurses-make.log" 2>&1 \
|
||
|| { tail -20 "$BUILD_DIR/ncurses-make.log"; die "ncurses make 失败"; }
|
||
make install > "$BUILD_DIR/ncurses-install.log" 2>&1 \
|
||
|| { tail -20 "$BUILD_DIR/ncurses-install.log"; die "ncurses install 失败"; }
|
||
)
|
||
info "libncursesw.a 就绪: $PREFIX/ncurses/lib"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 3) procps-ng:vmstat / slabtop / free / ps / top / pmap
|
||
# ---------------------------------------------------------------------------
|
||
build_procps() {
|
||
log "编译 procps-ng $PROCPS_VER"
|
||
unpack "procps-ng-${PROCPS_VER}.tar.xz" "$BUILD_DIR/procps-src"
|
||
rm -rf "$BUILD_DIR/procps"; mkdir -p "$BUILD_DIR/procps"
|
||
(
|
||
cd "$BUILD_DIR/procps"
|
||
# PKG_CONFIG_LIBDIR 必须收紧:否则会捡到宿主(Termux/aarch64)的 ncursesw.pc,
|
||
# 结果是一边用 musl 头一边混进 bionic 头,watch/top 直接编不过。
|
||
# 注意:下面这段环境变量赋值必须写成“无注释的连续续行”,
|
||
# 注释行会截断续行链,导致变量只作用于一条空命令。
|
||
#
|
||
# ac_cv_func_malloc_0_nonnull / realloc:交叉编译时 autoconf 的运行期探测
|
||
# 无法执行,不缓存这两个值会生成 rpl_malloc/rpl_realloc 引用而链接失败。
|
||
PKG_CONFIG_PATH="$PREFIX/ncurses/lib/pkgconfig" \
|
||
PKG_CONFIG_LIBDIR="$PREFIX/ncurses/lib/pkgconfig" \
|
||
ac_cv_func_malloc_0_nonnull=yes ac_cv_func_realloc_0_nonnull=yes \
|
||
CC="$ZIGCC" CFLAGS="-O2 -ffunction-sections -fdata-sections -ffile-prefix-map=$BUILD_DIR/procps-src=/procps-ng-$PROCPS_VER" \
|
||
LDFLAGS="-static -Wl,--gc-sections" \
|
||
"$BUILD_DIR/procps-src/configure" \
|
||
--host="$ZIG_TARGET" --prefix="$PREFIX/procps" \
|
||
--disable-shared --enable-static --disable-nls --disable-kill \
|
||
> "$BUILD_DIR/procps-configure.log" 2>&1 \
|
||
|| { tail -20 "$BUILD_DIR/procps-configure.log"; die "procps configure 失败"; }
|
||
# 防呆:确认 ncurses 来自本 devkit 的 prefix,而不是宿主 Termux 的那份
|
||
grep -q "$PREFIX/ncurses" "$BUILD_DIR/procps/Makefile" \
|
||
|| die "procps 的 NCURSES_CFLAGS/LIBS 没指向 devkit prefix(pkg-config 串到宿主了)"
|
||
make -j"$JOBS" > "$BUILD_DIR/procps-make.log" 2>&1 \
|
||
|| { tail -20 "$BUILD_DIR/procps-make.log"; die "procps make 失败"; }
|
||
)
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 4) lsof:真版 lsof(busybox lsof 只有 pid/fd/设备号,没有 size/offset/协议)
|
||
# lsof 4.99.4 自带老式 Configure(不需要 autoconf),但版本探测读的是宿主
|
||
# /usr/include/linux/version.h + uname,在 Termux/aarch64 上拿不到目标内核版本,
|
||
# 于是 Makefile 里退化成 -DLINUXV=00000 -DLSOF_VSTR="0.0.0"(Configure 会打
|
||
# "unsupported dialect or version")。这两个宏在 linux dialect 里只影响
|
||
# `lsof -v` 的显示,不影响代码分支 —— 但仍显式覆盖成目标内核 4.4.94,便于事后对账。
|
||
# zig 的 musl 头没有 glibc 侧 linux/tcp.h 间接带来的 TCP 状态宏(TCP_ESTABLISHED
|
||
# 等 11 个),这里按 linux/tcp.h 的定义补齐;不补则 dsock.c 编译失败。
|
||
# ---------------------------------------------------------------------------
|
||
build_lsof() {
|
||
log "编译 lsof $LSOF_VER"
|
||
rm -rf "$BUILD_DIR/lsof"
|
||
mkdir -p "$BUILD_DIR/lsof"
|
||
tar xf "$SRC_DIR/lsof-${LSOF_VER}.tar.xz" -C "$BUILD_DIR/lsof" --strip-components=1
|
||
(
|
||
cd "$BUILD_DIR/lsof"
|
||
CC="$ZIGCC" ./Configure -n linux > "$BUILD_DIR/lsof-configure.log" 2>&1 \
|
||
|| { tail -20 "$BUILD_DIR/lsof-configure.log"; die "lsof Configure 失败"; }
|
||
grep -q 'unsupported dialect' "$BUILD_DIR/lsof-configure.log" \
|
||
&& info "注意:Configure 报 unsupported dialect(宿主 uname 6.x),版本宏下面用 -D 覆盖"
|
||
# lsof 的 Makefile 里 CFLAGS = CDEFS INCL DEP DEBUG;额外宏走 DEBUG 注入。
|
||
# -O2 必须显式给:zig cc 无 -O 时按 Debug 编,会插入 UBSan 桩导致链接失败。
|
||
make -j"$JOBS" CC="$ZIGCC" \
|
||
DEBUG="-O2 -DLINUXV=44094 -DLSOF_VSTR=\"$TARGET_KERNEL_VSTR\" -DTCP_ESTABLISHED=1 -DTCP_SYN_SENT=2 -DTCP_SYN_RECV=3 -DTCP_FIN_WAIT1=4 -DTCP_FIN_WAIT2=5 -DTCP_TIME_WAIT=6 -DTCP_CLOSE=7 -DTCP_CLOSE_WAIT=8 -DTCP_LAST_ACK=9 -DTCP_LISTEN=10 -DTCP_CLOSING=11" \
|
||
> "$BUILD_DIR/lsof-make.log" 2>&1 \
|
||
|| { tail -20 "$BUILD_DIR/lsof-make.log"; die "lsof make 失败"; }
|
||
)
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 4b) 本地源码(B 路径):scripts/build/devkit-src/*.c
|
||
# 与上游包完全隔离:不下载、不 configure、不 make、不改 TOOLS 里上游的
|
||
# 源码/构建参数。只做一次 zig cc,产物落在 $BUILD_DIR/local/<name>,
|
||
# 之后由 collect_and_verify 统一 strip + 静态/架构/NEEDED 自检。
|
||
# -ffile-prefix-map 抹掉宿主路径(与上游同样的可复现性要求,见 DEVKIT.md §10);
|
||
# -static 显式给,不依赖工具链默认值。
|
||
# ---------------------------------------------------------------------------
|
||
build_local() {
|
||
local f name
|
||
log "编译本地源码($LOCAL_SRC_DIR,Apache-2.0 自有实现)"
|
||
rm -rf "$BUILD_DIR/local"
|
||
mkdir -p "$BUILD_DIR/local"
|
||
for f in "$LOCAL_SRC_DIR"/*.c; do
|
||
[ -f "$f" ] || die "本地源码目录为空: $LOCAL_SRC_DIR"
|
||
name="$(basename "$f" .c)"
|
||
info "zigcc → $name"
|
||
"$ZIGCC" -O2 -Wall -Wextra -static \
|
||
-ffunction-sections -fdata-sections \
|
||
-ffile-prefix-map="$LOCAL_SRC_DIR"=/devkit-src \
|
||
-Wl,--gc-sections \
|
||
-o "$BUILD_DIR/local/$name" "$f" \
|
||
> "$BUILD_DIR/local-$name.log" 2>&1 \
|
||
|| { tail -30 "$BUILD_DIR/local-$name.log"; die "$name 编译失败"; }
|
||
done
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 5) 收集 + strip + 逐项验证
|
||
# ---------------------------------------------------------------------------
|
||
# 产出清单:<源路径>|<目标名>|<出处>|<许可证>
|
||
# 只放「真机实测能跑且真有价值」的:vmstat/slabtop(busybox 没有)、
|
||
# free/ps/pmap(busybox 版字段残缺)、sysctl(busybox 1.31 没有此 applet)、
|
||
# lsof(busybox 版只有 pid/fd)、timeout(busybox 1.31 没有此 applet,2026-11-11
|
||
# 整机进程耗尽事故后成为设备侧刚需)。
|
||
# 不交付 top:表头时钟被工具链 time64 打坏(不是 procps 的 bug,见 DEVKIT.md §9),
|
||
# 且卡上已有 htop 3.5.3(673KB)→ 属重复且不可信。
|
||
TOOLS="
|
||
$BUILD_DIR/procps/src/vmstat|vmstat|procps-ng $PROCPS_VER|GPL-2.0-or-later
|
||
$BUILD_DIR/procps/src/slabtop|slabtop|procps-ng $PROCPS_VER|GPL-2.0-or-later
|
||
$BUILD_DIR/procps/src/free|free|procps-ng $PROCPS_VER|GPL-2.0-or-later
|
||
$BUILD_DIR/procps/src/ps/pscommand|ps|procps-ng $PROCPS_VER|GPL-2.0-or-later
|
||
$BUILD_DIR/procps/src/pmap|pmap|procps-ng $PROCPS_VER|GPL-2.0-or-later
|
||
$BUILD_DIR/procps/src/sysctl|sysctl|procps-ng $PROCPS_VER|GPL-2.0-or-later
|
||
$BUILD_DIR/lsof/lsof|lsof|lsof $LSOF_VER|Purdue/BSD-style
|
||
$BUILD_DIR/local/timeout|timeout|devkit-src (local)|Apache-2.0
|
||
"
|
||
|
||
FAIL=0
|
||
collect_and_verify() {
|
||
log "收集产物并验证"
|
||
rm -rf "$BIN_DIR"; mkdir -p "$BIN_DIR"
|
||
printf '%-8s %10s %s\n' "工具" "体积(B)" "静态/架构"
|
||
printf '%-8s %10s %s\n' "--------" "----------" "--------------------"
|
||
while IFS='|' read -r src name origin lic; do
|
||
[ -n "$src" ] || continue
|
||
[ -f "$src" ] || die "缺产物: $src(编译没成功?)"
|
||
cp "$src" "$BIN_DIR/$name"
|
||
chmod 755 "$BIN_DIR/$name"
|
||
"$STRIP" --strip-all "$BIN_DIR/$name"
|
||
if verify_binary "$BIN_DIR/$name"; then
|
||
printf '%-8s %10s %s\n' "$name" "$(wc -c < "$BIN_DIR/$name")" "OK: static/ELF32/MIPS32r2/0 NEEDED"
|
||
else
|
||
FAIL=$((FAIL + 1))
|
||
fi
|
||
done <<EOF
|
||
$(printf '%s\n' "$TOOLS" | sed '/^$/d')
|
||
EOF
|
||
}
|
||
|
||
verify_binary() {
|
||
local f="$1" desc cls mach flags needed
|
||
desc="$(file -b "$f")"
|
||
cls="$(readelf -h "$f" | sed -n 's/^ *Class: *//p')"
|
||
mach="$(readelf -h "$f" | sed -n 's/^ *Machine: *//p')"
|
||
flags="$(readelf -h "$f" | sed -n 's/^ *Flags: *//p')"
|
||
needed="$(readelf -d "$f" 2>/dev/null | grep -c NEEDED || true)"
|
||
case "$desc" in
|
||
*"statically linked"*) ;;
|
||
*) info "FAIL $(basename "$f"): 非静态 → $desc"; return 1 ;;
|
||
esac
|
||
[ "$cls" = "ELF32" ] || { info "FAIL $(basename "$f"): Class=$cls"; return 1; }
|
||
case "$mach" in *MIPS*) ;; *) info "FAIL $(basename "$f"): Machine=$mach"; return 1 ;; esac
|
||
case "$flags" in *mips32r2*) ;; *) info "FAIL $(basename "$f"): Flags=$flags"; return 1 ;; esac
|
||
[ "$needed" = "0" ] || { info "FAIL $(basename "$f"): 有 $needed 条 NEEDED"; return 1; }
|
||
case "$desc" in *"not stripped"*) info "警告 $(basename "$f"): 未 strip";; esac
|
||
return 0
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 6) MANIFEST / README / 许可证 / (可选)源码
|
||
# ---------------------------------------------------------------------------
|
||
write_meta() {
|
||
log "写 MANIFEST / README / 许可证"
|
||
mkdir -p "$OUT_DIR/licenses"
|
||
|
||
# 许可证文件是分发合规的一部分:复制失败必须报错,不能 `|| true` 静默吞掉
|
||
cp "$BUILD_DIR/procps-src/COPYING" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING" \
|
||
|| die "复制许可证失败: procps COPYING(分发合规必需)"
|
||
cp "$BUILD_DIR/procps-src/COPYING.LIB" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING.LIB" \
|
||
|| die "复制许可证失败: procps COPYING.LIB(LGPL-2.1 静态链接义务用)"
|
||
cp "$BUILD_DIR/ncurses-src/COPYING" "$OUT_DIR/licenses/ncurses-${NCURSES_VER}-COPYING" \
|
||
|| die "复制许可证失败: ncurses COPYING"
|
||
cp "$BUILD_DIR/lsof/COPYING" "$OUT_DIR/licenses/lsof-${LSOF_VER}-COPYING" \
|
||
|| die "复制许可证失败: lsof COPYING"
|
||
# musl:七个 procps/lsof 产物都静态链入了 zig 自带 musl(MIT),必须随副本留声明
|
||
cp "$MUSL_COPYRIGHT" "$OUT_DIR/licenses/musl-COPYRIGHT" \
|
||
|| die "复制许可证失败: musl COPYRIGHT($MUSL_COPYRIGHT)"
|
||
# timeout 是本仓自有 Apache-2.0 实现:Apache-2.0 §4 要求分发时附许可证原文
|
||
cp "$ROOT/LICENSE" "$OUT_DIR/licenses/meowos-timeout-LICENSE" \
|
||
|| die "复制许可证失败: $ROOT/LICENSE(timeout 的 Apache-2.0 原文)"
|
||
|
||
{
|
||
echo "MeowOS devkit — 静态 MIPS32r2 调试工具集"
|
||
# zig 版本用 `zig version`(不是 zigcc --version 的 clang 版本号)
|
||
echo "生成时间: $(date -u '+%Y-%m-%dT%H:%M:%SZ') zig: $ZIG_VERSION target: $ZIG_TARGET"
|
||
echo
|
||
printf '%-8s %-18s %-20s %10s %s\n' "tool" "source" "license" "size(B)" "sha256"
|
||
while IFS='|' read -r src name origin lic; do
|
||
[ -n "$src" ] || continue
|
||
printf '%-8s %-18s %-20s %10s %s\n' "$name" "$origin" "$lic" \
|
||
"$(wc -c < "$BIN_DIR/$name")" "$(sha256sum "$BIN_DIR/$name" | cut -d' ' -f1)"
|
||
done <<EOF
|
||
$(printf '%s\n' "$TOOLS" | sed '/^$/d')
|
||
EOF
|
||
echo
|
||
echo "源码(可复现性 + GPL/LGPL 合规):"
|
||
echo " ncurses $NCURSES_VER MIT-style sha256 $(printf '%s' "$SRC_NCURSES" | awk -F'|' '{print $3}')"
|
||
echo " procps-ng $PROCPS_VER GPL-2.0+ / library=LGPL-2.1+ sha256 $(printf '%s' "$SRC_PROCPS" | awk -F'|' '{print $3}')"
|
||
echo " lsof $LSOF_VER Purdue/BSD sha256 $(printf '%s' "$SRC_LSOF" | awk -F'|' '{print $3}')"
|
||
echo " timeout Apache-2.0 本仓 scripts/build/devkit-src/timeout.c(独立实现,未拷贝 GPL 代码)"
|
||
echo " sha256 $(sha256sum "$LOCAL_SRC_DIR/timeout.c" | cut -d' ' -f1)"
|
||
echo " musl zig $ZIG_VERSION 自带(MIT) 许可证原文见 licenses/musl-COPYRIGHT"
|
||
echo " (--with-sources 时)src/ 内还有 build_devkit.sh + DEVKIT.md + timeout.c(构建控制脚本与本地源码)"
|
||
echo " 重编方法: src/build_devkit.sh(卡上路径;仓库里是 scripts/build/build_devkit.sh)"
|
||
echo " 许可证原文: licenses/*-COPYING / licenses/*-COPYRIGHT / licenses/meowos-timeout-LICENSE"
|
||
echo " 说明见 src/DEVKIT.md"
|
||
echo
|
||
echo "注意: procps-ng 是 GPL-2.0-or-later。把本目录随卡交付给第三方时,"
|
||
echo " GPLv2 §3 要求提供对应源码(含构建脚本)—— 请用 --with-sources 带上 src/,"
|
||
echo " 或随卡附书面要约(有效期 ≥3 年)。仅给出下载 URL 不构成合规。"
|
||
echo " 另外 ps/vmstat/slabtop/free/pmap/sysctl 静态链接了 LGPL-2.1+ 的 libproc2:"
|
||
echo " LGPL-2.1 §6 要求「源码(§6a) 或 共享库(§6b) 或 书面要约(§6c)」三选一,详见 DEVKIT.md §6。"
|
||
echo " timeout 是 Apache-2.0(本仓自有),与上述 GPL 组件无源码混合。"
|
||
} > "$OUT_DIR/MANIFEST.txt"
|
||
|
||
cat > "$OUT_DIR/README.txt" <<'EOS'
|
||
MeowOS devkit — 设备端调试工具(静态 MIPS32r2)
|
||
================================================
|
||
|
||
加到 PATH(SSH 会话,临时):
|
||
export PATH=/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin:$PATH
|
||
|
||
持久化(rootfs 的 /etc/profile.d/meow_card_path.sh 已把 MeowOS/opt/bin 前插,
|
||
但 devkit 是独立目录,需要再加一行):
|
||
if [ -d /usr/data/mnt/sd_0/MeowOS/opt/devkit/bin ]; then
|
||
export PATH="/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin:$PATH"
|
||
fi
|
||
|
||
⚠ SSH 默认是**非登录 shell**:`ssh root@设备 'cmd'` 的 PATH 只有
|
||
/usr/sbin:/usr/bin:/sbin:/bin,**不含 MeowOS/opt/bin,也不含本 devkit 目录**。
|
||
所以卡上直接 `timeout 1 sleep 5` 会报 not found。两种正确写法:
|
||
sh -lc 'timeout 1 sleep 5' # 走登录 shell → 带上 opt/bin
|
||
/usr/data/mnt/sd_0/MeowOS/opt/devkit/bin/timeout 1 sleep 5 # 绝对路径
|
||
(2026-11-11 实测:ssh 非登录 shell 下 `command -v timeout` 为空。)
|
||
|
||
目录内容(本文件所在目录就是 devkit 根):
|
||
bin/ 可执行文件(实体文件,非软链)
|
||
MANIFEST.txt 版本 / 许可证 / 体积 / sha256
|
||
licenses/ 许可证原文(procps GPLv2 + LGPL-2.1、ncurses、lsof、musl、
|
||
meowos-timeout-LICENSE = Apache-2.0)
|
||
src/ 仅当随卡附带了源码(--with-sources)时存在:三份源码包 +
|
||
build_devkit.sh + DEVKIT.md + timeout.c。
|
||
**src/ 是生成目录,请勿手放文件。**
|
||
说明文档:src/DEVKIT.md(若本源码未随附,见仓库 scripts/build/DEVKIT.md)
|
||
|
||
exFAT 没有 Unix 权限位(挂载 fmask=0022 → 新文件一律 0755),也没有符号链接。
|
||
本目录里都是实体可执行文件,拷到卡上即可执行,无需 chmod,也不能用软链。
|
||
若拷入后无法执行,请检查挂载参数(不要有 noexec;`mount | grep sd_0`),
|
||
而不是反复 chmod —— fmask=0022 下 chmod 本身是 no-op。
|
||
|
||
与 busybox 同名命令的关系(devkit/bin 前插 PATH 时生效):
|
||
free ps lsof → 会被本目录版本覆盖(procps/lsof 字段更全)
|
||
vmstat slabtop pmap sysctl → busybox 1.31 没有这些 applet,直接新增
|
||
timeout → busybox 1.31 同样没有,直接新增
|
||
要临时用回 busybox 版:busybox free / busybox ps / busybox lsof
|
||
注意:/bin/ps -> busybox,/usr/bin/{free,lsof,top} -> ../../bin/busybox,
|
||
所以“前插 devkit/bin”是唯一让新版本生效的方式(不要覆盖这些软链:
|
||
rootfs 是 ubifs,随固件回刷会被还原,而且 busybox 软链是基线的一部分)。
|
||
|
||
timeout 用法(本仓 Apache-2.0 独立实现,非 busybox 代码):
|
||
timeout [-s SIGNAL] [-k DURATION] DURATION COMMAND [ARGS...]
|
||
timeout 2 sleep 30 # 2 秒后返回,退出码 124
|
||
timeout -s KILL 2 sleep 30 # 退出码 137
|
||
timeout -k 1 5 bluetoothctl connect XX:XX... # TERM 后 1 秒仍在 → KILL
|
||
timeout 0.2 sleep 5 # 支持小数秒(约 200ms 返回)
|
||
timeout 0.5m sleep 60 # 小数+后缀叠加:0.5m = 30 秒(1.5m = 90 秒)
|
||
超时信号发给 COMMAND 的**整个进程组**:命令自己 fork 的孙进程一起死
|
||
(`timeout 5 sh -c 'a & b & wait'` 不会留下 a/b)。退出码:
|
||
124=超时 137=超时且最终发了 KILL 128+N=命令死于信号 N
|
||
125=timeout 自身参数/系统调用错 126=命令无法执行 127=命令不存在
|
||
杀不动时不装作成功:进程组不可用会告警并退化为只杀直接子进程;
|
||
连兜底 kill 都失败时退出码 125(不是 124),不会谎报"已处理"。
|
||
不支持 --preserve-status / -v / d(天)后缀,给了会明确报 125(不静默忽略)。
|
||
不跟踪 setsid() 逃出进程组的守护进程(coreutils 也不跟踪)。
|
||
精度是毫秒级够用:计时交给内核 setitimer(ITIMER_REAL)+SIGALRM,不读时钟,
|
||
误差主要来自调度延迟(单核负载高时数十毫秒)。
|
||
|
||
已知限制:
|
||
- pmap 只能读 /proc/<pid>/maps;本机 CONFIG_PROC_PAGE_MONITOR=n,
|
||
/proc/<pid>/smaps 不存在,所以 pmap -x 拿不到 PSS/私有脏页。
|
||
- slabtop -o(一次性输出)不需要 terminfo,也不要求 TTY;
|
||
交互模式走 /usr/share/terminfo(设备已有 xterm 等条目;已编进 ncurses)。
|
||
- 本工具链(zig 0.17 musl)在 4.4 内核上是 time64 ABI 错配:
|
||
* `vmstat -t` 的时间戳列会是**空**(localtime 返回 NULL)
|
||
* `free -s 0.5`(小数间隔)会**立即返回**(亚秒等待被抹平);
|
||
`free -s 1` 与 `vmstat 1 N` 的整秒间隔正常
|
||
* ps 的 lstart、slabtop/pmap/sysctl 不受影响
|
||
原因与后续修法见 src/DEVKIT.md「已知限制:工具链 time64」。
|
||
timeout 不受影响:它不调用 clock_gettime/time,也不用 timerfd
|
||
(musl 的 timerfd_settime 在这台机上是静默失效的),超时由内核
|
||
setitimer(ITIMER_REAL)+SIGALRM 计时。
|
||
- lsof 不带 -n 时会尝试反向解析(编译进 musl 的 resolv.conf 路径是构建机的,
|
||
设备上不存在)→ 实测 `lsof -i` 约 3s 超时;请用 `lsof -nP`。
|
||
- 不提供 top:见 src/DEVKIT.md(同样是 time64 问题:表头时钟不可信)。
|
||
EOS
|
||
}
|
||
# ---------------------------------------------------------------------------
|
||
# 主流程
|
||
# ---------------------------------------------------------------------------
|
||
stamp_value() {
|
||
{
|
||
sha256sum "$SCRIPT_PATH"
|
||
# 带 zig 的完整版本(0.17.0 → 0.17.1 也要触发重建:工具链 patch 会改产物)
|
||
echo "zig=$ZIG_VERSION target=$ZIG_TARGET kernel=$TARGET_KERNEL_VSTR"
|
||
echo "ncurses=$NCURSES_VER procps=$PROCPS_VER lsof=$LSOF_VER"
|
||
# 本地源码(timeout 等):内容变就必须重编,否则 stamp 命中会漏掉新代码
|
||
( cd "$LOCAL_SRC_DIR" && sha256sum *.c ) 2>/dev/null || true
|
||
} | sha256sum | cut -d' ' -f1
|
||
}
|
||
|
||
main() {
|
||
local t0 t1
|
||
|
||
if [ "$VERIFY_ONLY" = 1 ]; then
|
||
# 只校验现有产物:不要求 zig/make/date,评审者用 file/readelf/sha256sum 即可
|
||
collect_verify_existing
|
||
[ "$FAIL" = 0 ] || die "$FAIL 项未通过自检"
|
||
return 0
|
||
fi
|
||
|
||
t0=$(date +%s)
|
||
mkdir -p "$SRC_DIR" "$BUILD_DIR" "$PREFIX"
|
||
check_toolchain
|
||
|
||
local cur_stamp old_stamp=""
|
||
cur_stamp="$(stamp_value)"
|
||
[ -f "$WORK/stamp" ] && old_stamp="$(cat "$WORK/stamp")"
|
||
|
||
if [ "$CLEAN" = 1 ]; then
|
||
log "清理工作区 $WORK 与输出目录的 src/"
|
||
rm -rf "$BUILD_DIR" "$PREFIX"
|
||
mkdir -p "$BUILD_DIR" "$PREFIX"
|
||
if [ -d "$OUT_DIR/src" ]; then
|
||
rm -rf "$OUT_DIR/src"
|
||
fi
|
||
old_stamp=""
|
||
fi
|
||
|
||
local bin_ok=1 mf_ok=0 meta_ok=0 n
|
||
for n in $TOOL_NAMES; do
|
||
[ -f "$BIN_DIR/$n" ] && verify_binary "$BIN_DIR/$n" >/dev/null 2>&1 || bin_ok=0
|
||
done
|
||
# 交付完整性也要一并作为 skip 条件:MANIFEST/许可证/README 缺失或与 bin 不符时
|
||
# 宁可重建(与 P1 同类——不能因为“跳过编译”就让合规材料静默消失)。
|
||
if [ -f "$OUT_DIR/MANIFEST.txt" ] && verify_manifest >/dev/null 2>&1; then
|
||
mf_ok=1
|
||
fi
|
||
meta_complete && meta_ok=1
|
||
|
||
# 产物本身没问题、只是 MANIFEST/README/licenses 被删:就地补,不必重编
|
||
if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ "$bin_ok" = 1 ] \
|
||
&& { [ "$mf_ok" = 0 ] || [ "$meta_ok" = 0 ]; } \
|
||
&& [ -d "$BUILD_DIR/procps-src" ] && [ -d "$BUILD_DIR/ncurses-src" ] && [ -d "$BUILD_DIR/lsof" ]; then
|
||
log "产物未变,但 MANIFEST/README/licenses 不全 → 就地补全"
|
||
write_meta
|
||
if verify_manifest >/dev/null 2>&1; then mf_ok=1; else mf_ok=0; fi
|
||
if meta_complete; then meta_ok=1; else meta_ok=0; fi
|
||
fi
|
||
|
||
if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ "$bin_ok" = 1 ] \
|
||
&& [ "$mf_ok" = 1 ] && [ "$meta_ok" = 1 ]; then
|
||
# P1 回归点:--with-sources 必须在这里就兑现,不能因为“跳过编译”而漏掉源码。
|
||
# (曾经把这段放在 return 之后,结果 stamp 命中时静默不产出 src/,退出码仍是 0。)
|
||
if [ "$WITH_SOURCES" = 1 ]; then
|
||
if sources_complete; then
|
||
info "源码包已就绪: $OUT_DIR/src"
|
||
else
|
||
stage_sources
|
||
fi
|
||
elif [ -d "$OUT_DIR/src" ] && ! sources_complete; then
|
||
info "注意: $OUT_DIR/src 与当前版本/脚本不一致;若要分发请用 --with-sources 重建"
|
||
fi
|
||
t1=$(date +%s)
|
||
log "产物已是最新(脚本与版本未变),跳过编译"
|
||
info "输出目录: $BIN_DIR"
|
||
info "用时 $((t1 - t0))s,联网 $DOWNLOADS 次"
|
||
return 0
|
||
fi
|
||
|
||
log "取源码(固定版本 + sha256 校验)"
|
||
fetch_src "$SRC_NCURSES"
|
||
fetch_src "$SRC_PROCPS"
|
||
fetch_src "$SRC_LSOF"
|
||
|
||
build_ncurses
|
||
build_procps
|
||
build_lsof
|
||
build_local
|
||
|
||
collect_and_verify
|
||
[ "$FAIL" = 0 ] || die "$FAIL 个产物未通过静态/架构自检(见上)"
|
||
|
||
write_meta
|
||
|
||
if [ "$WITH_SOURCES" = 1 ]; then
|
||
stage_sources
|
||
elif [ -d "$OUT_DIR/src" ]; then
|
||
# 上次 --with-sources 的残留可能与本次二进制不对应;留着会被误当“合规源码”
|
||
info "删除上次残留的 $OUT_DIR/src(本次未请求源码,避免与二进制错配)"
|
||
rm -rf "$OUT_DIR/src"
|
||
fi
|
||
|
||
printf '%s\n' "$cur_stamp" > "$WORK/stamp"
|
||
t1=$(date +%s)
|
||
log "完成"
|
||
info "输出: $OUT_DIR"
|
||
info "体积合计: $(du -sh "$BIN_DIR" | cut -f1)"
|
||
info "用时 $((t1 - t0))s,本次联网下载 $DOWNLOADS 次"
|
||
if [ "$WITH_SOURCES" = 1 ]; then
|
||
info "源码包: $OUT_DIR/src($(du -sh "$OUT_DIR/src" | cut -f1))"
|
||
fi
|
||
}
|
||
|
||
collect_verify_existing() {
|
||
local n bad=0
|
||
for n in $TOOL_NAMES; do
|
||
if [ ! -f "$BIN_DIR/$n" ]; then
|
||
info "缺 $BIN_DIR/$n"; bad=$((bad + 1)); continue
|
||
fi
|
||
verify_binary "$BIN_DIR/$n" && info "OK $n $(wc -c < "$BIN_DIR/$n") B" || bad=$((bad + 1))
|
||
done
|
||
verify_manifest || bad=$((bad + 1))
|
||
verify_meta || bad=$((bad + 1))
|
||
FAIL=$bad
|
||
}
|
||
|
||
# MANIFEST.txt 的逐条 sha256/体积比对:只查“是不是静态 MIPS”看不出内容漂移
|
||
# (旧版脚本/旧工具链编出来的同样是 static/MIPS)。这里把 MANIFEST 当权威对账。
|
||
verify_manifest() {
|
||
local n line got_sha got_size bad=0
|
||
local mf="$OUT_DIR/MANIFEST.txt"
|
||
if [ ! -f "$mf" ]; then
|
||
info "FAIL 缺 $mf(无法核对产物内容)"
|
||
return 1
|
||
fi
|
||
for n in $TOOL_NAMES; do
|
||
# 只认表格行:$1=工具名,$(NF-1)=纯数字体积,$NF=64 位 sha。
|
||
# (MANIFEST 末尾「源码」小节里也有以 lsof 开头的行,不加这两个约束会把两行都收进来。)
|
||
line="$(awk -v k="$n" '$1 == k && $(NF-1) ~ /^[0-9]+$/ && length($NF) == 64' "$mf")"
|
||
if [ -z "$line" ]; then
|
||
info "FAIL MANIFEST 缺 $n 的表格行"; bad=$((bad + 1)); continue
|
||
fi
|
||
got_sha="$(printf '%s\n' "$line" | awk '{print $NF}')"
|
||
got_size="$(printf '%s\n' "$line" | awk '{print $(NF-1)}')"
|
||
if [ "$got_sha" != "$(sha256sum "$BIN_DIR/$n" | cut -d' ' -f1)" ]; then
|
||
info "FAIL $n sha256 与 MANIFEST 不一致(产物被替换/旧版残留?)"; bad=$((bad + 1))
|
||
elif [ "$got_size" != "$(wc -c < "$BIN_DIR/$n")" ]; then
|
||
info "FAIL $n 体积与 MANIFEST 不一致($got_size vs $(wc -c < "$BIN_DIR/$n"))"; bad=$((bad + 1))
|
||
fi
|
||
done
|
||
[ "$bad" = 0 ] && info "OK MANIFEST.txt 与 bin/ 逐条 sha256/体积一致"
|
||
return $((bad > 0))
|
||
}
|
||
|
||
# 交付完整性:MANIFEST + README + 五份许可证副本,缺一个都算不完整
|
||
# (和 P1 同类:这些是合规/可用性的一部分,不能因为“跳过编译”就静默缺失)
|
||
meta_complete() {
|
||
local f
|
||
[ -s "$OUT_DIR/MANIFEST.txt" ] || return 1
|
||
[ -s "$OUT_DIR/README.txt" ] || return 1
|
||
for f in $LICENSE_FILES; do
|
||
[ -s "$OUT_DIR/licenses/$f" ] || return 1
|
||
done
|
||
return 0
|
||
}
|
||
|
||
verify_meta() {
|
||
local f bad=0
|
||
[ -s "$OUT_DIR/MANIFEST.txt" ] || { info "FAIL 缺 MANIFEST.txt(版本/许可证/sha 无从核对)"; bad=$((bad + 1)); }
|
||
[ -s "$OUT_DIR/README.txt" ] || { info "FAIL 缺 README.txt(上卡说明)"; bad=$((bad + 1)); }
|
||
for f in $LICENSE_FILES; do
|
||
[ -s "$OUT_DIR/licenses/$f" ] || { info "FAIL 缺 licenses/$f(许可证留痕)"; bad=$((bad + 1)); }
|
||
done
|
||
[ "$bad" = 0 ] && info "OK MANIFEST/README/licenses($(printf '%s' "$LICENSE_FILES" | wc -w) 份)齐全"
|
||
return $((bad > 0))
|
||
}
|
||
|
||
main "$@"
|