pipeline-app/scripts/build_binary.sh

202 lines
8.7 KiB
Bash
Raw Permalink 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 cout
# init 分支2026-09-17 首跑实抓两处 bug 的修正):
# ① 副本名必须**恰好等于目录名**(顶层=包名/子包=子包名)——曾拼成 init_<pkg>_<dir>.py
# dir="." 时点号进模块名触发 Nuitka AssertionError子包副本名带前缀则产物 .so 名与
# mv 目标不符 FAIL(mv)。实验定型形态cp 成 <目录名>.py。
# ② init 的编译输出目录用独立 $WORK/initout/<pkg>(不能直接进最终 destNuitka 按
# 副本 stem 命名产物,需 mv 成 __init__.<tag>.so
gen_jobs() {
local pkgdir=$1 outpkg=$2
find "$pkgdir" -name "*.py" -not -path "*__pycache__*" -not -name "test_*" \
-not -path "*/tests/*" -not -path "*/.git/*" \
-not -name "*.bak.py" -not -name "*.old.py" | while read -r f; do
# py_compile 预门2026-09-17 实抓源码态就语法坏的死文件p2p_middleware/rsa_key_rw/
# clickhouseor全仓零引用、从未可 import直接跳过——Nuitka 对语法零容忍会整批 FAIL
"$PY" -m py_compile "$f" 2>/dev/null || { echo "SKIP(broken-syntax,源码态死文件): $f" >> "$FAILLOG.broken"; continue; }
local rel=${f#$pkgdir/} dir stem src mode cout
dir=$(dirname "$rel"); stem=$(basename "$f" .py)
local destdir="$OUT/$outpkg"; [ "$dir" != "." ] && destdir="$OUT/$outpkg/$dir"
if [ "$stem" = "__init__" ]; then
local comp; comp=$(basename "$dir"); [ "$dir" = "." ] && comp=$outpkg
src="$WORK/initcopy/$outpkg/$comp.py"
mkdir -p "$(dirname "$src")" "$WORK/initout/$outpkg"
cp "$f" "$src"
mode=init; stem=$comp; cout="$WORK/initout/$outpkg"
else
src=$f; mode=mod; cout=$destdir
fi
# 增量:产物 .so 比源码新则跳过(重跑只补缺/失败项)
local sofinal="$destdir/$stem.$TAG.so"; [ "$mode" = init ] && sofinal="$destdir/__init__.$TAG.so"
if [ -f "$sofinal" ] && [ "$sofinal" -nt "$f" ]; then continue; fi
printf "%s\t%s\t%s\t%s\t%s\n" "$src" "$destdir" "$mode" "$stem" "$cout" >> "$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 形态5 字段与 worker 对齐)。
# 入口 .so 放 **overlay 顶层**2026-09-17 实抓):入口模块 top-level 有
# root_dir = dirname(dirname(__file__)) 形态accounting worker 拿它 getConfig——虚拟
# __file__=overlay/<stem>.py 时上两级恰为应用根;放 _appbin/ 子目录则上两级=overlay 崩。
mkdir -p "$OUT"
for f in "$ROOT"/app/*.py; do
stem=$(basename "$f" .py)
printf "%s\t%s\tmod\t%s\t%s\n" "$f" "$OUT" "$stem" "$OUT" >> "$JOBS"
done
cat > "$WORK/worker.sh" <<'EOS'
src=$1; dest=$2; mode=$3; stem=$4; cout=$5
tag="cpython-310-x86_64-linux-gnu"
mkdir -p "$dest" "$cout"
out=$("$PYBIN" -m nuitka --module "$src" --output-dir="$cout" --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 "$cout/$stem.$tag.so" "$dest/__init__.$tag.so" 2>/dev/null || { echo "FAIL(mv) $src" >> "$FAILLOG"; exit 0; }
else
[ -f "$cout/$stem.$tag.so" ] || { echo "FAIL(no-so) $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 5 bash "$WORK/worker.sh"
# app 命名空间包2026-09-17 编译态起服务实抓):入口目录 app/ 无 __init__.py源码态靠
# sys.path 插 root_dir 以命名空间包形式被 `from app.quality_gate import ...` 引用;编译态
# 把入口模块 .so 复制一份进 overlay/app/(无 __init__ = 命名空间包py3 原生支持目录内
# .so 子模块。overlay 顶层副本服务入口自身 `from ktv_adapter import ...` 与 root_dir 语义。
mkdir -p "$OUT/app"
for f in "$ROOT"/app/*.py; do
stem=$(basename "$f" .py)
cp "$OUT/$stem.$TAG.so" "$OUT/app/" 2>/dev/null
done
# 清理 .build 残留NFS 上 rm -rf 偶发 Directory not empty--remove-output 只删自己那轮)
find "$OUT" -maxdepth 4 -type d -name "*.build" -exec rm -rf {} + 2>/dev/null
find "$OUT" -maxdepth 4 -type d -name "*.build" -exec rm -rf {} + 2>/dev/null
find "$OUT" -maxdepth 4 -type d -name "*.build" | while read -r d; do rm -rf "$d" 2>/dev/null; done
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工具脚本非业务代码保留源码形态放 _runners/ 不污染 overlay 顶层模块面)
mkdir -p "$OUT/_runners"
cat > "$OUT/_runners/run_compiled.py" <<'EOS'
#!/usr/bin/env python3
"""编译态入口 runnersys.argv 原样透传给 ahserver webapp 的参数解析。
sys.pathoverlay 根(入口编译模块+业务包,遮蔽 site-packages 源码)。"""
import os, sys
_overlay = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _overlay)
import pipeline_app
pipeline_app.main()
EOS
cat > "$OUT/_runners/run_accounting_compiled.py" <<'EOS'
#!/usr/bin/env python3
"""编译态记账 worker runnersys.argv 透传(-w workdir。"""
import os, sys
_overlay = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _overlay)
import pipeline_accounting_worker
pipeline_accounting_worker.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