#!/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,lsof} # fw_apps/devkit/MANIFEST.txt 版本/许可证/体积/校验摘要 # fw_apps/devkit/licenses/ 各上游 COPYING(分发留痕) # fw_apps/devkit/README.txt 上卡后怎么加 PATH、和 busybox 的同名关系 # (--with-sources 时另有 fw_apps/devkit/src/ 源码包,GPL 合规用) # # 源码固定为「版本 + sha256 + 固定 URL」,全部校验后才编译;缓存命中则零联网。 # # 工具链:zig cc --target=mipsel-linux-musleabi(需 zig 0.17.x) # 不依赖 ~/.tools 下的私有脚本,自己生成 wrapper($WORK/bin/zigcc)。 # 不需要内核头(musl 头足够;只有 lsof 缺 11 个 TCP 状态宏,本脚本 -D 补上)。 # # 许可证(二进制将来随卡分发时请保留此节): # procps-ng 4.0.4 GPL-2.0-or-later(COPYING=GPLv2;library/ 为 LGPL-2.1+) # *** 分发二进制必须提供对应源码(GPLv2 §3)*** # → 用 --with-sources 把源码包一起放上卡,或随卡附书面要约。 # ncurses 6.5 MIT 风格(X11 许可),保留版权声明即可 # lsof 4.99.4 Purdue Research Foundation 许可(BSD 风格 4 条), # 要求文档与源码中保留作者与 Purdue 的署名(COPYING 第 2 条) # 本脚本链接的 ncurses 只作为 slabtop 的静态依赖进入产物,不单独分发。 # # 用法: # bash scripts/build/build_devkit.sh # 增量:缓存命中则不联网 # bash scripts/build/build_devkit.sh --force # 强制重编 # bash scripts/build/build_devkit.sh --clean # 清空工作区后重编 # bash scripts/build/build_devkit.sh --offline # 只用已缓存的源码包(CI/离线) # bash scripts/build/build_devkit.sh --with-sources # bash scripts/build/build_devkit.sh --verify-only # 只对现有产物做静态/架构自检 # # 环境变量: # MEOW_DEVKIT_WORK 工作区(源码包/编译树/prefix),默认 ~/.cache/meow-devkit # MEOW_DEVKIT_JOBS 并行度,默认 nproc # ============================================================================ set -eu ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" 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" # 设备内核版本:写进 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" 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,40p' "${BASH_SOURCE[0]}" | 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 zig_ver major_minor zig_bin="$(command -v zig || true)" [ -n "$zig_bin" ] || die "找不到 zig。装法:pkg install zig(需 0.17.x)" zig_ver="$("$zig_bin" version)" major_minor="${zig_ver%.*}" [ "$major_minor" = "$ZIG_WANT" ] || \ die "zig 版本是 $zig_ver,本脚本只在 $ZIG_WANT.x 上验证过(target/静态链接行为可能变)" mkdir -p "$WRAP_DIR" cat > "$WRAP_DIR/zigcc" </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 rm -rf "$2"; mkdir -p "$2" tar xf "$SRC_DIR/$1" -C "$2" --strip-components=1 } # --------------------------------------------------------------------------- # 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 失败"; } ) } # --------------------------------------------------------------------------- # 5) 收集 + strip + 逐项验证 # --------------------------------------------------------------------------- # 产出清单:<源路径>|<目标名>|<出处>|<许可证> # 只放「真机实测能跑且真有价值」的:vmstat/slabtop(busybox 没有)、 # free/ps/pmap(busybox 版字段残缺)、lsof(busybox 版只有 pid/fd)。 # 不交付 top:本机(32 位 MIPS + 4.4.94)上 procps 4.0.4 的 top 表头/内存单位 # 输出明显错误(时长字段溢出、Mem 显示 59.4 GiB 而 MemTotal 只有 55 MB), # 且卡上已有 htop 3.5.3(673KB),top 属重复且不可信 → 见 DEVKIT.md「未交付清单」。 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/lsof/lsof|lsof|lsof $LSOF_VER|Purdue/BSD-style " 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 </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" cp "$BUILD_DIR/procps-src/COPYING" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING" 2>/dev/null || true cp "$BUILD_DIR/procps-src/COPYING.LIB" "$OUT_DIR/licenses/procps-ng-${PROCPS_VER}-COPYING.LIB" 2>/dev/null || true cp "$BUILD_DIR/ncurses-src/COPYING" "$OUT_DIR/licenses/ncurses-${NCURSES_VER}-COPYING" 2>/dev/null || true cp "$BUILD_DIR/lsof/COPYING" "$OUT_DIR/licenses/lsof-${LSOF_VER}-COPYING" 2>/dev/null || true { echo "MeowOS devkit — 静态 MIPS32r2 调试工具集" echo "生成时间: $(date -u '+%Y-%m-%dT%H:%M:%SZ') zig: $("${ZIGCC}" --version 2>/dev/null | head -1) 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 < "$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 exFAT 没有 Unix 权限位(挂载 fmask=0022 → 文件一律 0755),也没有符号链接。 本目录里都是实体可执行文件,拷到卡上即可执行,不需要 chmod,也不能用软链。 若手工从其他文件系统拷入后发现不可执行,执行一次: chmod 755 /usr/data/mnt/sd_0/MeowOS/opt/devkit/bin/* 与 busybox 同名命令的关系(devkit/bin 前插 PATH 时生效): free ps lsof → 会被本目录版本覆盖(procps/lsof 字段更全) vmstat slabtop pmap → busybox 没有,直接新增 要临时用回 busybox 版:/bin/busybox free / busybox ps / busybox lsof 注意:/usr/bin/{free,ps,lsof,top} 目前都是指向 /bin/busybox 的软链, 所以“前插 devkit/bin”是唯一让新版本生效的方式(不要覆盖 /usr/bin 里的软链: rootfs 是 ubifs,随固件回刷会被还原,而且 busybox 软链是基线的一部分)。 已知限制: - pmap 只能读 /proc//maps;本机 CONFIG_PROC_PAGE_MONITOR=n, /proc//smaps 不存在,所以 pmap -x 拿不到 PSS/私有脏页。 - slabtop -o(一次性输出)不需要 terminfo,也不要求 TTY; 交互模式走 /usr/share/terminfo(设备已有 xterm 等条目;已编进 ncurses)。 - 不提供 top:procps 4.0.4 的 top 在本机输出不可信(见仓库 scripts/build/DEVKIT.md)。 EOS } # --------------------------------------------------------------------------- # 主流程 # --------------------------------------------------------------------------- stamp_value() { { sha256sum "${BASH_SOURCE[0]}" echo "$NCURSES_VER $PROCPS_VER $LSOF_VER $ZIG_TARGET $TARGET_KERNEL_VSTR" } | sha256sum | cut -d' ' -f1 } main() { local t0 t1 t0=$(date +%s) mkdir -p "$SRC_DIR" "$BUILD_DIR" "$PREFIX" if [ "$VERIFY_ONLY" = 1 ]; then # 只校验现有产物:不要求 zig/make,评审者用系统自带 file/readelf 即可 collect_verify_existing [ "$FAIL" = 0 ] || die "$FAIL 个产物未通过自检" return 0 fi 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" rm -rf "$BUILD_DIR" "$PREFIX" mkdir -p "$BUILD_DIR" "$PREFIX" old_stamp="" fi if [ "$FORCE" = 0 ] && [ "$cur_stamp" = "$old_stamp" ] && [ -d "$BIN_DIR" ]; then local ok=1 for n in vmstat slabtop free ps pmap lsof; do [ -f "$BIN_DIR/$n" ] && verify_binary "$BIN_DIR/$n" >/dev/null 2>&1 || ok=0 done if [ "$ok" = 1 ]; then t1=$(date +%s) log "产物已是最新(脚本与版本未变),跳过编译" info "输出目录: $BIN_DIR" info "用时 $((t1 - t0))s,联网 $DOWNLOADS 次" return 0 fi fi log "取源码(固定版本 + sha256 校验)" fetch_src "$SRC_NCURSES" fetch_src "$SRC_PROCPS" fetch_src "$SRC_LSOF" build_ncurses build_procps build_lsof collect_and_verify [ "$FAIL" = 0 ] || die "$FAIL 个产物未通过静态/架构自检(见上)" write_meta if [ "$WITH_SOURCES" = 1 ]; then log "复制源码包到 $OUT_DIR/src(GPL 合规用)" mkdir -p "$OUT_DIR/src" cp "$SRC_DIR/ncurses-${NCURSES_VER}.tar.gz" "$SRC_DIR/procps-ng-${PROCPS_VER}.tar.xz" \ "$SRC_DIR/lsof-${LSOF_VER}.tar.xz" "$OUT_DIR/src/" info "共 $(du -sh "$OUT_DIR/src" | cut -f1)" 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 次" } collect_verify_existing() { local n bad=0 for n in vmstat slabtop free ps pmap lsof; 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 FAIL=$bad } main "$@"