Files
op-packages/luci-app-openclaw/tests/test_packaging_parity.sh
T
github-actions[bot] 410d762592
Merge-upstream / merge (push) Canceled after 0s
🎄 Sync 2026-08-27 02:13:09
2026-08-27 02:13:09 +08:00

175 lines
6.9 KiB
Bash
Executable File

#!/bin/sh
# 打包清单一致性契约。
#
# 背景: 本项目有三条打包路径,各自独立维护安装清单:
# 1. Makefile — feeds / OpenWrt SDK 编译 (逐个 INSTALL_* 枚举)
# 2. scripts/build_ipk.sh — 本地 .ipk 构建 (大量使用 cp *.js / *.sh 通配)
# 3. scripts/build_run.sh — .run 自解压包构建 (同上)
#
# 实测漂移: Makefile 只安装 oc-config.sh + web-pty.js,漏掉
# oc-config-interactive.js 与 oc-menu-engine.js。Release 的 .ipk/.run 正常,
# 但走 feeds/SDK 编译装出来的包缺这两个文件,此时 oc-config.sh 的
# can_use_interactive() 因 [ -f "$OC_INTERACTIVE" ] 失败而静默回落到传统菜单
# —— 用户看不到任何报错,只会觉得界面和教程不一致。
#
# 本测试对每个应打包的源文件,校验三条路径都会安装它。
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
MK="$REPO_ROOT/Makefile"
IPK="$REPO_ROOT/scripts/build_ipk.sh"
RUN="$REPO_ROOT/scripts/build_run.sh"
fail() {
echo "FAIL: $1" >&2
exit 1
}
for f in "$MK" "$IPK" "$RUN"; do
[ -f "$f" ] || fail "missing $f"
done
# 判断某条打包脚本是否会安装指定源文件。
# 命中方式二选一:
# - 显式提到文件名
# - 存在覆盖其目录 + 扩展名的通配 (如 root/usr/share/openclaw/"*.js)
covered() {
_file="$1" # 相对仓库根的路径, 如 root/usr/share/openclaw/oc-menu-engine.js
_script="$2"
_base=$(basename "$_file")
_dir=$(dirname "$_file")
_ext=$(printf '%s' "$_base" | sed -n 's/.*\.\([A-Za-z0-9]\+\)$/\1/p')
# 显式文件名
if grep -Fq "$_base" "$_script"; then
return 0
fi
# 目录通配: 匹配形如 <dir>/"*.ext 或 <dir>/*.ext
if [ -n "$_ext" ] && grep -Fq "${_dir}/" "$_script" && grep -Fq "*.${_ext}" "$_script"; then
# 进一步确认同一行里既有该目录又有该扩展通配
if grep -F "${_dir}/" "$_script" | grep -Fq "*.${_ext}"; then
return 0
fi
fi
return 1
}
# ── 应打包的源文件集合 ──
# 只列运行时必需的文件; tests/ 与 docs/ 由 .gitattributes export-ignore 排除。
FILES=""
add_files() {
for p in "$@"; do
[ -e "$REPO_ROOT/$p" ] || continue
FILES="$FILES $p"
done
}
add_files root/etc/config/openclaw
add_files root/etc/uci-defaults/99-openclaw
add_files root/etc/init.d/openclaw
add_files root/etc/profile.d/openclaw.sh
add_files root/usr/bin/openclaw-env
for f in "$REPO_ROOT"/root/usr/libexec/*.sh; do
[ -f "$f" ] && add_files "root/usr/libexec/$(basename "$f")"
done
add_files luasrc/controller/openclaw.lua
for f in "$REPO_ROOT"/luasrc/openclaw/*.lua; do
[ -f "$f" ] && add_files "luasrc/openclaw/$(basename "$f")"
done
for f in "$REPO_ROOT"/luasrc/model/cbi/openclaw/*.lua; do
[ -f "$f" ] && add_files "luasrc/model/cbi/openclaw/$(basename "$f")"
done
for f in "$REPO_ROOT"/luasrc/view/openclaw/*.htm; do
[ -f "$f" ] && add_files "luasrc/view/openclaw/$(basename "$f")"
done
for f in "$REPO_ROOT"/root/usr/share/rpcd/acl.d/*.json; do
[ -f "$f" ] && add_files "root/usr/share/rpcd/acl.d/$(basename "$f")"
done
# /usr/share/openclaw 下的脚本与前端 (ui/ 目录整体复制, 单独校验)
for f in "$REPO_ROOT"/root/usr/share/openclaw/*.sh "$REPO_ROOT"/root/usr/share/openclaw/*.js; do
[ -f "$f" ] && add_files "root/usr/share/openclaw/$(basename "$f")"
done
[ -n "$FILES" ] || fail "no packageable source files detected (parser drift?)"
# ── 三条路径逐一校验 ──
missing=""
for rel in $FILES; do
covered "$rel" "$MK" || missing="${missing}\n Makefile 缺少 $rel"
covered "$rel" "$IPK" || missing="${missing}\n build_ipk.sh 缺少 $rel"
covered "$rel" "$RUN" || missing="${missing}\n build_run.sh 缺少 $rel"
done
if [ -n "$missing" ]; then
# shellcheck disable=SC2059
printf "打包清单不一致:$missing\n" >&2
fail "packaging paths disagree on which files to install"
fi
# ── ui/ 目录与 VERSION ──
for s in "$MK" "$IPK" "$RUN"; do
grep -Fq 'share/openclaw/ui' "$s" || fail "$(basename "$s") must install the Web PTY ui/ directory"
grep -Fq 'VERSION' "$s" || fail "$(basename "$s") must ship the VERSION file"
done
# ── 回归钉子 ──
# 这两个文件曾只在 build 脚本里、Makefile 漏装
grep -Fq 'oc-config-interactive.js' "$MK" || fail "Makefile must install oc-config-interactive.js (feeds/SDK builds fell back to the legacy menu without it)"
grep -Fq 'oc-menu-engine.js' "$MK" || fail "Makefile must install oc-menu-engine.js"
# 依赖声明三处必须一致, 且不得丢掉 libstdcpp6 (issue #28: 缺失导致 Node 无法运行)
for s in "$MK" "$IPK" "$RUN"; do
grep -Fq 'libstdcpp6' "$s" || fail "$(basename "$s") must keep the libstdcpp6 dependency (issue #28)"
done
# feeds 集成: 不得再依赖 luci.mk 的隐式 Package 生成 (issue #60)
# 只看真正的 include 语句, 注释里提到 luci.mk (说明历史原因) 是允许的。
if grep -E '^[[:space:]]*include[[:space:]].*feeds/luci/luci\.mk' "$MK" >/dev/null 2>&1; then
fail "Makefile must not depend on luci.mk implicit package generation (breaks OpenWrt 25.x feeds, issue #60)"
fi
if grep -E '^[[:space:]]*ifeq[[:space:]]*\(\$\(wildcard[[:space:]].*luci\.mk' "$MK" >/dev/null 2>&1; then
fail "Makefile must not branch on luci.mk availability (issue #60)"
fi
grep -Fq 'define Package/$(PKG_NAME)' "$MK" || fail "Makefile must define Package/... explicitly"
# 新增的共享数据文件必须显式安装: build 脚本只 cp *.js 通配,
# .json 不会被自动带上 (model-presets.json 曾因此漏装)。
for s in "$MK" "$IPK" "$RUN"; do
grep -Fq 'model-presets.json' "$s" \
|| fail "$(basename "$s") must install model-presets.json (the *.js glob does not cover it)"
done
# ── 真实构建产物校验 ──
# 前面的检查都是静态文本比对,抓不到"脚本已改但产物仍缺文件"的情况
# (例如同步失误)。这里实际构建一次 .ipk 并检查解包内容。
# 需要 ar/tar,缺失时跳过而不是误报。
if command -v ar >/dev/null 2>&1 && command -v tar >/dev/null 2>&1; then
BUILD_TMP=$(mktemp -d 2>/dev/null || echo "/tmp/oc-parity-build-$$")
mkdir -p "$BUILD_TMP"
cleanup_build() { rm -rf "$BUILD_TMP"; }
trap cleanup_build EXIT
if ( cd "$REPO_ROOT" && sh scripts/build_ipk.sh "$BUILD_TMP" ) >/dev/null 2>&1; then
IPK_FILE=$(ls "$BUILD_TMP"/*.ipk 2>/dev/null | head -1)
if [ -n "$IPK_FILE" ]; then
EXTRACT="$BUILD_TMP/extract"
mkdir -p "$EXTRACT"
( cd "$EXTRACT" && { ar x "$IPK_FILE" 2>/dev/null || tar xzf "$IPK_FILE" 2>/dev/null; } )
if [ -f "$EXTRACT/data.tar.gz" ]; then
mkdir -p "$EXTRACT/data"
tar xzf "$EXTRACT/data.tar.gz" -C "$EXTRACT/data" 2>/dev/null
SHARE="$EXTRACT/data/usr/share/openclaw"
for want in oc-config.sh oc-config-interactive.js oc-menu-engine.js web-pty.js model-presets.json VERSION; do
[ -f "$SHARE/$want" ] \
|| fail "built .ipk is missing /usr/share/openclaw/$want"
done
[ -d "$SHARE/ui" ] || fail "built .ipk is missing /usr/share/openclaw/ui/"
fi
fi
fi
fi
echo "ok"