Files

122 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# build.sh —— BIOFLOW 服务编译硬化(复制自 tooling/compile/compile_sdk.sh, 白名单改造)。
# 白名单(明文):app/main.py(uvicorn 入口)、app/config.py(运维可读面)、
# app/schemas.py(pydantic 模型——Cython 转 cyfunction 会触发 PydanticUserError,
# 同 sdk 侧实测教训)、app/db.py(aiosqlite 协程取消热区——Cython 协程断流断言
# abort 是 docgen 既有教训, 明文保留)+ 全部 __init__.py。
# 用法:build.sh <source-root> <output-dir> [python]
set -euo pipefail
umask 027
SOURCE_ROOT="${1:?Usage: $0 <source-root> <output-dir> [python]}"
OUTPUT_DIR="${2:?Usage: $0 <source-root> <output-dir> [python]}"
PYTHON="${3:-python3}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VISIBILITY_TOOL="${ELF_VISIBILITY_TOOL:-$SCRIPT_DIR/../tooling/compile/tools/elf_visibility.py}"
SOURCE_ROOT="$(realpath "$SOURCE_ROOT")"
OUTPUT_DIR="$(realpath -m "$OUTPUT_DIR")"
[[ -d "$SOURCE_ROOT" ]] || { echo "Bioflow service source missing: $SOURCE_ROOT" >&2; exit 2; }
[[ -x "$PYTHON" ]] || { echo "Python is not executable: $PYTHON" >&2; exit 2; }
[[ -f "$VISIBILITY_TOOL" ]] || { echo "ELF visibility tool missing: $VISIBILITY_TOOL" >&2; exit 2; }
for command_name in gcc strip rsync; do command -v "$command_name" >/dev/null; done
case "$OUTPUT_DIR" in
"/"|"$(realpath -m "$HOME")"|"$SOURCE_ROOT")
echo "Unsafe output directory: $OUTPUT_DIR" >&2; exit 2 ;;
esac
[[ ! -e "$OUTPUT_DIR" ]] || { echo "Output exists; refusing to overwrite: $OUTPUT_DIR" >&2; exit 2; }
echo "[BIOFLOW BUILD 1/5] Copying source (excluding tests)"
mkdir -p "$OUTPUT_DIR"
rsync -a --exclude '__pycache__' --exclude 'test_*.py' --exclude 'data' --exclude '.env' \
--exclude 'build.sh' --exclude 'restart.sh' --exclude 'pytest.ini' --exclude 'tests' \
"$SOURCE_ROOT/" "$OUTPUT_DIR/"
# 白名单明文:main.py + config.py + schemas.py + db.py + __init__.py(编译前从发现集排除)
# 白名单理由:main.py=uvicorn 入口;config.py=运维可读面;
# schemas.py=pydantic v2 模型(validator 方法被 Cython 转 cyfunction 触发
# PydanticUserError,实测编译后无法导入——纯数据契约面,无敏感逻辑,明文保留);
# db.py=断流崩溃根治(同 sdk 侧 2026-09-03 教训):aiosqlite 协程取消热点,
# Cython 协程 __Pyx_Coroutine_SendEx 断言会 abort 整个进程,明文走 CPython 原生协程
WHITELIST_PY=(
"app/main.py"
"app/config.py"
"app/schemas.py"
"app/db.py"
# SSE 流式路由(TL 2026-09-06 补)——与 sdk 侧断流崩溃根治同款教训:
# 客户端中途断开时 Cython 协程取消断言会 abort 整个进程,明文走 CPython 原生协程
"app/routers/bioflow.py"
"app/routers/biojobs.py"
)
_is_whitelisted() {
local rel="$1"
[[ "$(basename "$rel")" == "__init__.py" ]] && return 0
for w in "${WHITELIST_PY[@]}"; do
case "$rel" in
$w) return 0 ;;
esac
done
return 1
}
echo "[BIOFLOW BUILD 2/5] Discovering Python modules"
PY_FILES=()
while IFS= read -r -d '' path; do PY_FILES+=("$path"); done \
< <(find "$OUTPUT_DIR" -type f -name '*.py' -print0)
FILTERED=()
for py_file in "${PY_FILES[@]}"; do
rel="${py_file#"$OUTPUT_DIR"/}"
if _is_whitelisted "$rel"; then
echo " [whitelist] $rel (明文保留)"
continue
fi
FILTERED+=("$py_file")
done
PY_FILES=("${FILTERED[@]}")
TOTAL="${#PY_FILES[@]}"
(( TOTAL > 0 )) || { echo "No bioflow service modules selected" >&2; exit 3; }
INCLUDE_PY="$("$PYTHON" -c 'import sysconfig; print(sysconfig.get_config_var("INCLUDEPY") or "")')"
EXT_SUFFIX="$("$PYTHON" -c 'import sysconfig; print(sysconfig.get_config_var("EXT_SUFFIX") or ".so")')"
echo "[BIOFLOW BUILD 3/5] Cython preflight ($TOTAL modules)"
COUNT=0; FAILURES=0
for py_file in "${PY_FILES[@]}"; do
COUNT=$((COUNT+1))
directory="$(dirname "$py_file")"
module="$(basename "$py_file" .py)"
c_file="$directory/$module.c"
echo " [$COUNT/$TOTAL] ${py_file#"$OUTPUT_DIR"/}"
if ! "$PYTHON" -m cython -3 \
--directive always_allow_keywords=true,annotation_typing=False \
--no-docstrings "$py_file" -o "$c_file" 2>/dev/null; then
FAILURES=$((FAILURES+1))
fi
done
(( FAILURES == 0 )) || { echo "Bioflow service Cython failed: $FAILURES" >&2; exit 3; }
echo "[BIOFLOW BUILD 4/5] GCC hidden visibility + strip"
COUNT=0
for py_file in "${PY_FILES[@]}"; do
COUNT=$((COUNT+1))
directory="$(dirname "$py_file")"
module="$(basename "$py_file" .py)"
c_file="$directory/$module.c"
so_file="$directory/$module$EXT_SUFFIX"
gcc -shared -pthread -fPIC -O2 -fvisibility=hidden \
-I"$INCLUDE_PY" -o "$so_file" "$c_file"
strip --strip-unneeded "$so_file"
"$PYTHON" -B "$VISIBILITY_TOOL" --hide-non-pyinit --check "$so_file" >/dev/null
rm -f -- "$c_file" "$py_file"
done
echo "[BIOFLOW BUILD 5/5] Final checks"
SO_COUNT="$(find "$OUTPUT_DIR" -type f -name '*.so' | wc -l)"
[[ "$SO_COUNT" -eq "$TOTAL" ]] || { echo "count mismatch: $TOTAL vs $SO_COUNT" >&2; exit 4; }
if find "$OUTPUT_DIR" -type f -name '*.c' -print -quit | grep -q .; then
echo "Generated C source remains" >&2; exit 4
fi
echo "Bioflow service compile+harden complete: modules=$SO_COUNT output=$OUTPUT_DIR"