feat(deploy): Nuitka二进制化构建脚本+入口main()——逐模块编译保包结构(__init__改名trick),数据文件随overlay,runner透传argv;2026-09-17立项实验定型
This commit is contained in:
parent
66996c693d
commit
d86facda22
@ -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
161
scripts/build_binary.sh
Normal 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 不能直接 --module(Nuitka 拒),用改名 trick:cp 成 <目录名>.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
|
||||
"""编译态入口 runner:sys.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
|
||||
Loading…
x
Reference in New Issue
Block a user