feat(deploy): Nuitka二进制化构建脚本+入口main()——逐模块编译保包结构(__init__改名trick),数据文件随overlay,runner透传argv;2026-09-17立项实验定型

This commit is contained in:
yumoqing 2026-09-17 16:22:00 +08:00
parent 66996c693d
commit d86facda22
2 changed files with 166 additions and 1 deletions

View File

@ -181,5 +181,9 @@ def init():
# Permission cache warms up on first request
if __name__ == '__main__':
def main():
webapp(init)
if __name__ == '__main__':
main()

161
scripts/build_binary.sh Normal file
View File

@ -0,0 +1,161 @@
#!/bin/bash
# Nuitka 二进制化构建2026-09-17 立项):把自有 Python 包逐模块编译成 .so
# 产出 site-packages 覆盖层 binpkg_overlay/(保留包目录结构 + 包内数据文件)。
#
# 实测定型的编译形态(测试机 2026-09-17 实验 out1-out5
# - 逐模块 --module 编译,保留包目录结构(整包单 .so 会断 importlib 动态导入子模块,弃)
# - __init__.py 不能直接 --moduleNuitka 拒),用改名 trickcp 成 <目录名>.py 编译
# PyInit 符号=文件 stem=目录名天然正确objcopy 改动态符号无效,弃)
# - 数据文件(.json/.tmpl 等)不进编译,原样拷进覆盖层;运行时 __file__ 是虚拟 .py 路径
# (与 .so 同目录dirname(__file__) 定位数据目录的模式成立
# - 相对导入/绝对导入/inspect.signature/importlib 动态导入 编译后全验证通过
#
# 用法bash scripts/build_binary.sh [输出目录] (在应用根目录或任意位置跑,自定位)
set -uo pipefail
ROOT=$(cd "$(dirname "$0")/.." && pwd)
PY=$ROOT/py3/bin/python
OUT=${1:-$ROOT/binpkg_overlay}
TAG="cpython-310-x86_64-linux-gnu"
WORK=$(mktemp -d /tmp/nuitka_build.XXXXXX)
JOBS=$WORK/jobs.tsv
FAILLOG=$WORK/fail.log
OKLOG=$WORK/ok.log
: > "$JOBS"; : > "$FAILLOG"; : > "$OKLOG"
mkdir -p "$OUT"
# repo目录:包目录(测试机 pkgs/ 布局;包目录名以仓库实查为准,勿凭记忆)
MANIFEST="
pipeline-service:pipeline_service
pipeline-core:pipeline_core
pipeline-ops:pipeline_ops
pipeline-sdlc:pipeline_sdlc
pipeline-bidding:pipeline_bidding
pipeline-opportunity:pipeline_opportunity
pipeline-platform:pipeline_platform
pipeline-task:pipeline_task
pipeline-llm:pipeline_llm
apppublic:appPublic
sqlor:sqlor
ahserver:ahserver
rbac:rbac
appbase:appbase
app_audit:app_audit
xls2ddl:xls2ddl
bricks-for-python:bricks_for_python
accounting:accounting
account_resource:account_resource
storage_resource:storage_resource
supplychain:supplychain
discount:discount
pricing:pricing
product_management:product_management
unipay:unipay
smssend:smssend
filemgr:filemgr
dapi:dapi
uapi:uapi
rag:rag
dingdingflow:dingdingflow
showcase:showcase
ticket:ticket
voucher:voucher
tenant:tenant
"
# 生成编译任务src dest mode stem
gen_jobs() {
local pkgdir=$1 outpkg=$2
find "$pkgdir" -name "*.py" -not -path "*__pycache__*" -not -name "test_*" \
-not -path "*/tests/*" -not -path "*/.git/*" | while read -r f; do
local rel=${f#$pkgdir/} dir stem src mode
dir=$(dirname "$rel"); stem=$(basename "$f" .py)
local destdir="$OUT/$outpkg"; [ "$dir" != "." ] && destdir="$OUT/$outpkg/$dir"
if [ "$stem" = "__init__" ]; then
# 改名 trick副本名=该包目录最后一段(顶层=包名,子包=子包名)
local comp; comp=$(basename "$dir"); [ "$dir" = "." ] && comp=$outpkg
src="$WORK/init_${outpkg}_${dir//\//_}.py"
cp "$f" "$src"
mode=init; stem=$comp
else
src=$f; mode=mod
fi
printf "%s\t%s\t%s\t%s\n" "$src" "$destdir" "$mode" "$stem" >> "$JOBS"
done
# 包内数据文件原样进覆盖层(不用 rsync目标机可能没装
mkdir -p "$OUT/$outpkg"
(cd "$pkgdir" && find . -type f -not -name "*.py" -not -name "*.pyc" \
-not -path "*__pycache__*" -not -path "./.git/*") | while read -r rel; do
mkdir -p "$OUT/$outpkg/$(dirname "$rel")"
cp "$pkgdir/$rel" "$OUT/$outpkg/$rel"
done
}
echo "$MANIFEST" | grep -v '^$' | while read -r entry; do
repo=${entry%%:*}; pkg=${entry#*:}
pkgdir=$ROOT/pkgs/$repo/$pkg
[ -d "$pkgdir" ] || { echo "SKIP(目录不存在): $pkgdir"; continue; }
gen_jobs "$pkgdir" "$pkg"
done
# 入口与 app 顶层模块script 形态,编译成顶层模块进 _appbin
mkdir -p "$OUT/_appbin"
for f in "$ROOT"/app/*.py; do
stem=$(basename "$f" .py)
printf "%s\t%s\tmod\t%s\n" "$f" "$OUT/_appbin" "$stem" >> "$JOBS"
done
cat > "$WORK/worker.sh" <<'EOS'
src=$1; dest=$2; mode=$3; stem=$4
tag="cpython-310-x86_64-linux-gnu"
mkdir -p "$dest"
out=$("$PYBIN" -m nuitka --module "$src" --output-dir="$dest" --remove-output 2>&1)
rc=$?
if [ $rc -ne 0 ]; then echo "FAIL $src :: $(echo "$out" | tail -2)" >> "$FAILLOG"; exit 0; fi
if [ "$mode" = init ]; then
mv "$dest/$stem.$tag.so" "$dest/__init__.$tag.so" 2>/dev/null || { echo "FAIL(mv) $src" >> "$FAILLOG"; exit 0; }
fi
echo "OK $src" >> "$OKLOG"
EOS
export PYBIN=$PY
export FAILLOG OKLOG
total=$(wc -l < "$JOBS")
echo "编译任务 $total 个,并行 4 …"
xargs -a "$JOBS" -P 4 -n 4 bash "$WORK/worker.sh"
ok=$(wc -l < "$OKLOG"); fail=$(wc -l < "$FAILLOG")
echo "完成: OK=$ok FAIL=$fail"
if [ "$fail" -gt 0 ]; then echo "== 失败清单 =="; cat "$FAILLOG"; exit 1; fi
# runner工具脚本非业务代码保留源码形态
cat > "$OUT/_appbin/run_compiled.py" <<'EOS'
#!/usr/bin/env python3
"""编译态入口 runnersys.argv 原样透传给 ahserver webapp 的参数解析。
sys.path 顺序_appbin(入口编译模块) > overlay 根(编译业务包,遮蔽 site-packages 源码) > 其余。"""
import os, sys
_here = os.path.dirname(os.path.abspath(__file__))
_overlay = os.path.dirname(_here)
sys.path.insert(0, _overlay)
sys.path.insert(0, _here)
import pipeline_app
pipeline_app.main()
EOS
# BUILD_INFO逐仓哈希部署对账/回滚点用)
HASHF=$WORK/hashes.jsonl
: > "$HASHF"
echo "$MANIFEST" | grep -v '^$' | while read -r entry; do
repo=${entry%%:*}
h=$(git -C "$ROOT/pkgs/$repo" rev-parse HEAD 2>/dev/null || echo unknown)
printf ' "%s": "%s"\n' "$repo" "$h" >> "$HASHF"
done
printf ' "pipeline-app": "%s"\n' "$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || echo unknown)" >> "$HASHF"
{
echo "{"
echo " \"built_at\": \"$(date -Is)\","
echo " \"python\": \"$($PY --version 2>&1)\","
awk 'NR>1{printf ",\n"} {printf "%s", $0} END{print ""}' "$HASHF"
echo "}"
} > "$OUT/BUILD_INFO.json"
so=$(find "$OUT" -name "*.$TAG.so" | wc -l)
echo "覆盖层: $OUT (.so=$so)"
du -sh "$OUT" | cut -f1