pipeline-app/scripts/build_binary.sh

181 lines
7.6 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 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 形态,编译成顶层模块进 _appbin5 字段与 worker 对齐)
mkdir -p "$OUT/_appbin"
for f in "$ROOT"/app/*.py; do
stem=$(basename "$f" .py)
printf "%s\t%s\tmod\t%s\t%s\n" "$f" "$OUT/_appbin" "$stem" "$OUT/_appbin" >> "$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"
# 清理 .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工具脚本非业务代码保留源码形态
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