#!/usr/bin/env bash # compile_sdk.sh —— SDK 中间层(BFF)编译硬化。 # 白名单(明文):app/main.py(uvicorn 入口)、app/config.py(运维可读面)+ __init__.py。 # 用法:compile_sdk.sh [python] set -euo pipefail umask 027 SOURCE_ROOT="${1:?Usage: $0 [python]}" OUTPUT_DIR="${2:?Usage: $0 [python]}" PYTHON="${3:-python3}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VISIBILITY_TOOL="${ELF_VISIBILITY_TOOL:-$SCRIPT_DIR/tools/elf_visibility.py}" SOURCE_ROOT="$(realpath "$SOURCE_ROOT")" OUTPUT_DIR="$(realpath -m "$OUTPUT_DIR")" [[ -d "$SOURCE_ROOT" ]] || { echo "SDK 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 "[SDK 1/5] Copying source (excluding tests)" mkdir -p "$OUTPUT_DIR" rsync -a --exclude '__pycache__' --exclude 'test_*.py' --exclude 'data' --exclude '.env' \ "$SOURCE_ROOT/" "$OUTPUT_DIR/" # 白名单明文:main.py + config.py + __init__.py(编译前从发现集排除) WHITELIST_PY=("app/main.py" "app/config.py" "app/schemas.py" "app/db.py" "app/routers/qa.py" "app/services/sse_relay.py" "app/routers/bioflow_proxy.py") # 白名单理由:main.py=uvicorn 入口;config.py=运维可读面; # schemas.py=pydantic v2 模型(validator 方法被 Cython 转 cyfunction 触发 # PydanticUserError,实测编译后无法导入——纯数据契约面,无敏感逻辑,明文保留); # db.py=2026-09-03 断流崩溃根治:客户端中途断开流时 Cython 协程 # bioflow_proxy.py=2026-09-06 生信流式反代(SSE 逐块 yield,同型断流高危点,TL 补) # db.py=2026-09-03 断流崩溃根治:客户端中途断开流时 Cython 协程 # __Pyx_Coroutine_SendEx 断言 abort 整个 BFF(Cython 3.2.8/3.3.0 均复现, # A/B 证据见 docs/team/deploy_plan.md 附档)。db 是崩溃协程热点, # 降级明文 Python 走 CPython 原生协程取消,根治断言崩溃 _is_whitelisted() { local rel="$1" for w in "${WHITELIST_PY[@]}"; do [[ "$rel" == "$w" ]] && return 0; done [[ "$(basename "$rel")" == "__init__.py" ]] && return 0 return 1 } echo "[SDK 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 SDK 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 "[SDK 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 "SDK Cython failed: $FAILURES" >&2; exit 3; } echo "[SDK 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 "[SDK 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 "SDK compile+harden complete: modules=$SO_COUNT output=$OUTPUT_DIR"