Files
MeowOS-For-R3P2/patch_v2/reader_app/build_reader.sh
T
AskaEth 58f2d9bff4 perf(reader): M32 r2 书架索引改 level-1 校验 — 真机冷缓存首开 3.47s→0.13–0.16s(r1 全目录校验被真机证伪)
r1 用宿主 1.1µs/stat + M28 的**热** stat 150µs 外推「434 次≈65ms」,真机 drop_caches 后
实测是 3.15s(7.26ms/次),与 readdir 的 3.0s 同量级:全目录校验的索引命中冷启动仍要
3.06s,只比全扫快 0.2s。「3.0s→0.15s」在冷启动不成立。按任务 1 自己定的判据
(stat 接近 1s 就退回 level-1)改为只校验 Books 根 + 每个一级目录。

真机冷缓存实测(exFAT /usr/data/mnt/sd_0,434 目录/646 文件/595 本;sync + drop_caches;
探针走 /proc/uptime,UI 走 meow_ui_time_ms):
- 索引命中首开:**127 / 138 / 157ms**(复测 147ms),热 dentry 107–120ms;
- 全量扫描首开:**3428 / 3477ms**(复测 3711ms),热 dentry 不变(readdir 不吃 page cache);
- 探针冷分项:索引载入 20–30ms;载入+rdprog×595 70–120ms;4 个目录 stat 0–30ms;
- 从阅读页返回书架 29–31ms(M29 语义未变)。
→ 冷启动 22–26 倍提升,方案值得做;索引实测 **63,579B**(v2;r1 全目录 97,490B)。

实现(格式 v2,magic MEOWIDX2 / version 2):
- writer 回调过滤 rel 含 '/' 的目录:只落 root + 一级目录的 mtime;书/大小/格式记录仍全量;
  level-2+ 不落盘、载入不 stat。载入仍然 0 次 opendir/readdir(strace:0 getdents64,
  真实库 4 次 stat;宿主模拟库 11 次)。
- 校验记录里出现 level-2+ rel 视为坏档;sum(books_here) 改为「≤ nbooks」合理性上界。
- fail-open/原子写/CRC/安全加固/ts 不缓存全部保留(r1 已过评审的部分未动)。

失效矩阵(报文逐条写清):
自动(根 mtime):Books 根下直接增/删/改名书;一级目录新增/删除/改名。
自动(一级目录 mtime):一级目录里直接增/删/改名书、新建子目录。
必须刷新:level-2 及更深处的任何变化(只改深层目录 mtime)。刷新=删索引+全扫。
真机 exFAT mtime 实测 **1 秒**(tm_nsec=0;同秒连续增删 mtime 不变)。我复测 10 轮
「目录刚改过就扫描、同秒再改」→ 9/10 仍命中(陈旧),与评审 29/30 一致;sleep 跨秒后
全部正常失效。头注释已把「10ms」改为「1 秒」,并写明「不在同一秒刻度的变更靠索引失效,
同秒二次变更需刷新」。

bench 修复(P3-3):MEOW_BENCH_IDX=only —— **不预先 load/scan**,直接进书架量生产首开;
miss —— 先删索引再进书架。两者配合 drop_caches 即冷启动口径。旧行为(先 bench_index/
bench_scan 焐热)保留为默认模式。新增 test/shelf_index_probe_mipsel(write/scan/load/
loadts/statpaths/mtime/size)用于真机分项与 mtime 粒度验证,不进发行包。

测试:shelf_index_smoke 170 pass(新增:level-3/level-2 内变更「索引仍命中且先看到旧
列表 → 刷新后可见」、新建 level-2 目录自动失效、一级目录直接删书自动失效、v2 ndirs=
根+一级数);ASan+UBSan 干净;build_reader.sh 0 warning,core 6846 / seg 19 / chapter 71 /
shelf_group 63 / index 170 / font 14 / loaders PASS。真机 6 场景逐项验证:一级直接增/
新建子目录/一级改名/根下删书/一级直接删书 = 自动失效;level-3 深处新增 = 仍命中(需刷新)。

交付:app.json 0.1.5/code6 → **0.1.6/code7**;reader md5 5c848c99… 1,711,988B;
mpk md5 10d676f5… 814,574B;未签名不 push。设备验证后已清理 /tmp 与 m32probe,
真实 MeowData 的 shelf.idx 已删(app 自己的章节 .idx 未动),Books 434/646 不变,
已装 0.1.4 md5 4036b889… 未变。
2026-11-10 18:49:48 +08:00

204 lines
13 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
# build_reader.sh — os.meow.reader R2 可复现构建:
# vendor: freetype(静态)/miniz/libmobi/stb/zlib 头 → 交叉 mipsel 编 reader 二进制
# + 宿主 core 单测(R1+loaders) + qemu-mipsel 冒烟(rdft/book/formats)。
#
# 产物:
# reader mipsel 静态 ELF(链 meow_ui.a + freetype + miniz + libmobi + zlib)
# test/test_reader_core_host 宿主 core 单测(enc/layout/txt)
# test/shelf_group_smoke_host 宿主书架分组单测(M27/M31 副题)
# test/shelf_index_smoke_host 宿主书架索引单测(M32:命中/失效/fail-open/ts 不缓存)
# test/shelf_index_bench_host 宿主索引耗时基准(M32,只编不跑;bench/indexonly)
# test/font_resolve_smoke_host 宿主字体路径记忆化单测(M31)
# test/font_resolve_bench_host 宿主字体 walk/命中耗时对比(M31,只编不跑)
# test/loaders_smoke_host 宿主四格式 loader 单测
# test/rdft_smoke_mipsel qemu-mipsel FreeType 冒烟
# test/book_smoke_mipsel qemu-mipsel TXT 书桥冒烟
# test/formats_smoke_mipsel qemu-mipsel 四格式书桥冒烟
#
# 约束:只写本目录(patch_v2/reader_app/);不改 sdk/;-Wall -Wextra 0 warning(自有代码)。
# vendor/(第三方源码+构建产物)gitignore,不入仓;build 脚本可复现。
set -e
cd "$(dirname "$0")"
ROOT="$HOME/Project/R3ProII"
CC="$ROOT/.tools/zig-mipsel-cc"
SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
HOST_CC="${HOST_CC:-gcc}"
VENDOR="$PWD/vendor"
OBJ="$VENDOR/build"
# qemu-mipsel 运行时冒烟:宿主没装 qemu(如 Termux 无 mipsel user-mode 包)时,
# 仍编译 mipsel 冒烟二进制,只跳过执行;设 QEMU_BIN=<路径> 可指定其它模拟器。
QEMU_BIN="${QEMU_BIN:-qemu-mipsel}"
run_qemu() {
if [ "${SKIP_QEMU:-0}" = 1 ] || ! command -v "$QEMU_BIN" >/dev/null 2>&1; then
shift
echo " SKIP qemu 执行(未找到 $QEMU_BIN):$*"
return 0
fi
timeout "$@"
}
mkdir -p "$OBJ"
# ---------- 1) vendor 拉取/对齐 ----------
FT_VER="2.14.3"; MINIZ_URL="https://codeload.github.com/richgel999/miniz/tar.gz/refs/heads/master"
# libmobi 版本锁定:见 NOTICE「Locked version (commit)」。LGPL-3.0 §4(d) 要求「已分发二进制
# 对应的 libmobi 源码」与公开源码一致可复现,故不用 refs/heads/master(移动分支),改锁到精确
# commit SHA(2024-10-29 的 9062742...,与上级代理 agent-332 复验/NOTICE 声明一致,已验证可拉取)。
LM_URL="https://codeload.github.com/bfabiszewski/libmobi/tar.gz/906274205c11944b628da1c553b255acb1af7c55"
mkdir -p "$VENDOR"
fetch_freetype() {
if [ -n "$FREETYPE_SRC" ] && [ -f "$FREETYPE_SRC/CMakeLists.txt" ]; then cp -r "$FREETYPE_SRC" vendor/freetype-src;
elif [ ! -d "$VENDOR/freetype-src" ] && [ -f /tmp/freetype/CMakeLists.txt ]; then cp -r /tmp/freetype "$VENDOR/freetype-src";
elif [ ! -d "$VENDOR/freetype-src" ]; then
( cd "$VENDOR" && curl -fsSL -o ft.tar.xz "https://download.savannah.gnu.org/releases/freetype/freetype-$FT_VER.tar.xz" \
&& mkdir -p freetype-src && tar xf ft.tar.xz -C freetype-src --strip-components=1 && rm ft.tar.xz )
fi
[ -f "$VENDOR/freetype-src/CMakeLists.txt" ] || { echo "freetype 不可用"; exit 1; }
}
fetch_miniz() {
if [ ! -f "$VENDOR/miniz/miniz.c" ]; then
( cd /tmp && curl -fsSL -o miniz.tgz "$MINIZ_URL" && mkdir -p miniz_m && tar xzf miniz.tgz -C miniz_m --strip-components=1 \
&& cp miniz_m/miniz.c miniz_m/miniz.h miniz_m/miniz_common.h miniz_m/miniz_tdef.c miniz_m/miniz_tdef.h \
miniz_m/miniz_tinfl.c miniz_m/miniz_tinfl.h miniz_m/miniz_zip.c miniz_m/miniz_zip.h "$VENDOR/miniz/" )
fi
[ -f "$VENDOR/miniz/miniz.c" ] || { echo "miniz 不可用"; exit 1; }
}
fetch_libmobi() {
# 若 vendor/libmobi 来自旧版(LM_URL 改 pin 前抓的 master),请先删 vendor/libmobi 再重跑,
# 以确保取到下方锁定的 commit。此处仅在其缺失时拉取。
if [ ! -f "$VENDOR/libmobi/src/mobi.h" ]; then
( cd /tmp && curl -fsSL -o lm.tgz "$LM_URL" && mkdir -p lm_m && tar xzf lm.tgz -C lm_m --strip-components=1 \
&& mkdir -p "$VENDOR/libmobi" && cp -r lm_m/src "$VENDOR/libmobi/src" && cp lm_m/COPYING "$VENDOR/libmobi/COPYING" )
fi
[ -f "$VENDOR/libmobi/src/mobi.h" ] || { echo "libmobi 不可用"; exit 1; }
}
fetch_stb() { [ -f "$VENDOR/stb/stb_image.h" ] || { mkdir -p "$VENDOR/stb"; cp "$ROOT/patch_v2/sdk/stb_image.h" "$VENDOR/stb/stb_image.h"; }; }
fetch_freetype; fetch_miniz; fetch_libmobi; fetch_stb
if [ ! -f "$VENDOR/zlib/zlib.h" ]; then mkdir -p "$VENDOR/zlib"; cp /usr/include/zlib.h /usr/include/zconf.h "$VENDOR/zlib/" 2>/dev/null || true; fi
# ---------- 2) FreeType mipsel 静态构建 ----------
FT_SRC="$VENDOR/freetype-src"; FT_BUILD="$VENDOR/freetype-build"
if [ ! -f "$FT_BUILD/libfreetype.a" ] || [ -n "$FORCE_FT" ]; then
rm -rf "$FT_BUILD"
cmake -S "$FT_SRC" -B "$FT_BUILD" -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_C_COMPILER="$CC" \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DFT_DISABLE_ZLIB=TRUE -DFT_DISABLE_BZIP2=TRUE -DFT_DISABLE_PNG=TRUE -DFT_DISABLE_HARFBUZZ=TRUE -DFT_DISABLE_BROTLI=TRUE >/dev/null
cmake --build "$FT_BUILD" -j"$(nproc)" >/dev/null 2>&1
fi
FT_INCS="-I$FT_BUILD -I$FT_BUILD/include -I$FT_SRC/include"
COREINC="-Icore -Ivendor/miniz -Ivendor/stb -Ivendor/libmobi/src -Ivendor/zlib"
MZFLAGS="-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES -DMINIZ_NO_TIME"
# ---------- 3) miniz + libmobi 对象(mipsel) ----------
build_mipsel_libs() {
for f in vendor/miniz/miniz_tinfl.c vendor/miniz/miniz_tdef.c vendor/miniz/miniz_zip.c vendor/miniz/miniz.c; do
b="$(basename "$f" .c)"; [ -f "$OBJ/mz_$b.o" ] || $CC -Os -static -Wall -Wextra -Ivendor/miniz $MZFLAGS -c "$f" -o "$OBJ/mz_$b.o"
done
for f in vendor/libmobi/src/*.c; do b="$(basename "$f" .c)"; [ "$b" = "miniz" ] && continue
[ -f "$OBJ/lm_$b.o" ] || $CC -Os -static -w -Ivendor/libmobi/src -Ivendor/zlib -c "$f" -o "$OBJ/lm_$b.o"
done
# stb_image 的实现由 meow_ui.a 提供(SDK 自 M6.45 收编 stb_image.c.o);此处只编封装,
# 否则链接 meow_ui.a 时 stbi_* 会与归档内 stb_image.o 重复定义。
[ -f "$OBJ/rdimg.o" ] || $CC -Os -static -Wall -Wextra -Ivendor/stb -c engine/rdimg.c -o "$OBJ/rdimg.o"
}
build_mipsel_libs
# ---------- 4) 宿主 loader 单测(miniz/libmobi 用宿主编一遍) ----------
echo "==> host core test (R1)"
$HOST_CC -O2 -Wall -Wextra -Icore -I. test/test_reader_core.c core/rdenc.c core/rdlayout.c core/rdtxt.c -o test/test_reader_core_host -lm
./test/test_reader_core_host
echo "==> host seg test (R3.2)"
$HOST_CC -O2 -Wall -Wextra -Icore test/seg_smoke.c core/seg.c core/rdenc.c -o test/seg_smoke_host -lm
./test/seg_smoke_host
echo "==> host chapter test (R3.3)"
$HOST_CC -O2 -Wall -Wextra -Icore test/chapter_smoke.c core/rdchapter.c core/seg.c core/rdenc.c -o test/chapter_smoke_host -lm
./test/chapter_smoke_host
echo "==> host shelf group test (M27)"
$HOST_CC -O2 -Wall -Wextra -Iapp -I../sdk test/shelf_group_smoke.c app/shelf_model.c app/progress.c app/reader_conf.c -o test/shelf_group_smoke_host
./test/shelf_group_smoke_host
echo "==> host shelf index test (M32)"
$HOST_CC -O2 -Wall -Wextra -Iapp -I../sdk test/shelf_index_smoke.c app/shelf_model.c app/shelf_index.c app/progress.c -o test/shelf_index_smoke_host
./test/shelf_index_smoke_host
$HOST_CC -O2 -Wall -Wextra -Iapp -I../sdk test/shelf_index_bench.c app/shelf_model.c app/shelf_index.c -o test/shelf_index_bench_host
echo "==> host font resolve test (M31)"
$HOST_CC -O2 -Wall -Wextra -Iapp test/font_resolve_smoke.c app/font_resolve.c -o test/font_resolve_smoke_host
./test/font_resolve_smoke_host
$HOST_CC -O2 -Wall -Wextra -Iapp test/font_resolve_bench.c app/font_resolve.c -o test/font_resolve_bench_host
echo "==> host shelf scan bench (M27,只编不跑;用法 ./test/shelf_scan_bench_host <Books目录> [progress|noop] [轮数])"
$HOST_CC -O2 -Wall -Wextra -Iapp -I../sdk test/shelf_scan_bench.c app/shelf_model.c app/progress.c -o test/shelf_scan_bench_host
echo "==> host loaders test"
HOBJ="$OBJ/host"; mkdir -p "$HOBJ"
for f in vendor/miniz/miniz_tinfl.c vendor/miniz/miniz_tdef.c vendor/miniz/miniz_zip.c vendor/miniz/miniz.c; do
b="$(basename "$f" .c)"; [ -f "$HOBJ/mz_$b.o" ] || $HOST_CC -O2 -Ivendor/miniz $MZFLAGS -c "$f" -o "$HOBJ/mz_$b.o"
done
for f in vendor/libmobi/src/*.c; do b="$(basename "$f" .c)"; [ "$b" = "miniz" ] && continue
[ -f "$HOBJ/lm_$b.o" ] || $HOST_CC -O2 -w -Ivendor/libmobi/src -Ivendor/zlib -c "$f" -o "$HOBJ/lm_$b.o"
done
HCORE=""
for f in rdxml rdhtml rdsb rdzip rdsrc rdepub rdfb2 rdmobi rdcbz rdenc rdtxt; do
$HOST_CC -O2 -Wall -Wextra $COREINC -c core/$f.c -o "$HOBJ/core_$f.o"
done
python3 tools/gen_samples.py >/dev/null
$HOST_CC -O2 -Wall -Wextra $COREINC test/loaders_smoke.c "$HOBJ"/*.o -lz -lm -o test/loaders_smoke_host
./test/loaders_smoke_host /tmp/rd_samples
# ---------- 5) qemu-mipsel FreeType 冒烟 ----------
echo "==> mipsel ft smoke"
$CC -Os -static -Wall -Wextra -Iengine $FT_INCS test/rdft_smoke.c engine/rdft.c "$FT_BUILD/libfreetype.a" -o test/rdft_smoke_mipsel
run_qemu 120 "$QEMU_BIN" ./test/rdft_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-Regular.otf}"
# ---------- 6) qemu-mipsel 书桥冒烟(TXT + 四格式) ----------
# rdimg.o 引用 stbi_*,实现只在 sdk/meow_ui.a 里(见步骤 3 注释);冒烟不链 UI 主体,
# 但必须链该归档才能解析 stbi_*(归档按需只拉 stb_image.o)。
echo "==> mipsel book smoke (TXT)"
$CC -Os -static -Wall -Wextra -Iapp -Icore -Iengine $FT_INCS $COREINC test/book_smoke.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c core/seg.c core/rdchapter.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c \
core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c engine/rdft.c \
"$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o ../sdk/meow_ui.a "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -o test/book_smoke_mipsel
run_qemu 120 "$QEMU_BIN" ./test/book_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-Regular.otf}"
echo "==> mipsel formats smoke"
$CC -Os -static -Wall -Wextra -Iapp -Icore -Iengine $FT_INCS $COREINC test/formats_smoke.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c core/seg.c core/rdchapter.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c \
core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c engine/rdft.c \
"$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o ../sdk/meow_ui.a "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -o test/formats_smoke_mipsel
run_qemu 150 "$QEMU_BIN" ./test/formats_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-Regular.otf}" /tmp/rd_samples
# ---------- 7) reader 二进制(mipsel 静态) ----------
echo "==> reader binary"
CORE_L="core/rdenc.c core/rdlayout.c core/rdtxt.c core/seg.c core/rdchapter.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c"
ENGINE_L="engine/rdft.c"
APP_L="app.c app/shelf.c app/shelf_model.c app/shelf_index.c app/font_resolve.c app/reader.c app/book.c app/progress.c app/settings.c app/reader_conf.c app/reader_ipc.c"
$CC -Os -static -Wall -Wextra -I../sdk -I../meow_launcherd -I. -Icore -Iapp -Iengine $FT_INCS $COREINC \
$CORE_L $APP_L $ENGINE_L "$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o \
../sdk/meow_ui.a "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -lpthread -o reader
ls -la reader
file reader
md5sum reader
echo " 运行(qemu): qemu-mipsel ./reader"
# ---------- 8) 真机耗时基准(不进发行包:与 reader 同源,只把 app.c 的 main 换成 bench main) ----------
# 用法(设备):MEOW_FONT_* 等 env 与 App 一致时执行 ./shelf_bench_ondev_mipsel [书...]
echo "==> ondev shelf bench (not shipped)"
APP_BENCH_L="app/shelf.c app/shelf_model.c app/shelf_index.c app/font_resolve.c app/reader.c app/book.c app/progress.c app/settings.c app/reader_conf.c app/reader_ipc.c"
$CC -Os -static -Wall -Wextra -I../sdk -I../meow_launcherd -I. -Icore -Iapp -Iengine $FT_INCS $COREINC \
test/shelf_bench_ondev.c $CORE_L $APP_BENCH_L $ENGINE_L "$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o \
../sdk/meow_ui.a "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -lpthread -o test/shelf_bench_ondev_mipsel
ls -la test/shelf_bench_ondev_mipsel
echo "build_reader OK"
# ---------- 9) 真机索引探针(不进发行包;直接调 model/index,无 UI) ----------
# 用法(设备):MEOW_PROBE_CFG=<TF>/MeowData/m32probe/data ./shelf_index_probe_mipsel load <Books>
echo "==> mipsel index probe (not shipped)"
$CC -Os -static -Wall -Wextra -I../sdk -Iapp test/shelf_index_probe.c \
app/shelf_model.c app/shelf_index.c app/progress.c -o test/shelf_index_probe_mipsel
ls -la test/shelf_index_probe_mipsel