pipeline-app/scripts/build_binary.sh

162 lines
5.9 KiB
Bash
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
# 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