build_binary.sh 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #!/usr/bin/env bash
  2. # ============================================================
  3. # build_binary.sh — 用 Nuitka 把整个 agentpaas 打成单个原生可执行文件
  4. # (standalone + onefile)。用户拿到后【无需安装 Python】,直接 ./agentpaas serve。
  5. #
  6. # 与 --build-secure(.so wheel)的区别:
  7. # --build-secure : 仍是 wheel,用户要有匹配的 Python;保护的是源码
  8. # --build-binary : 单文件二进制,内嵌 Python + 依赖 + 前端 + 内置包;保护源码 + 免装环境
  9. #
  10. # 产物:dist-binary/agentpaas(绑定「平台 + 架构」,如 macOS arm64)。
  11. #
  12. # 用法: scripts/build_binary.sh [PYTHON]
  13. # PYTHON 默认 python3,须为原生架构、3.10+、带头文件(macOS 推荐 Homebrew python3.11)。
  14. # 环境:
  15. # PROVIDERS 要打进二进制的 LLM SDK,空格分隔,默认 "anthropic openai"
  16. # (dashscope 走 openai 兼容/HTTP、ollama 走 HTTP,均无需 SDK)
  17. # ONEFILE 1=单文件(默认,体验最好但打包耗资源);0=standalone 目录
  18. # (产出一个文件夹,更省内存、更稳,适合 CI/资源受限机器)
  19. #
  20. # 前置: C 编译器(macOS: xcode-select --install)
  21. # ============================================================
  22. set -e
  23. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  24. PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
  25. cd "$PROJECT_DIR"
  26. RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
  27. info() { echo -e "${BLUE}[INFO]${NC} $1"; }
  28. ok() { echo -e "${GREEN}[OK]${NC} $1"; }
  29. warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
  30. fail() { echo -e "${RED}[FAIL]${NC} $1"; exit 1; }
  31. PYTHON="${1:-python3}"
  32. PROVIDERS="${PROVIDERS:-anthropic openai}"
  33. ONEFILE="${ONEFILE:-1}"
  34. command -v "$PYTHON" >/dev/null 2>&1 || fail "未找到 Python: $PYTHON"
  35. # ── 架构/版本/头文件检查(与 build_secure 一致)──────────────
  36. read -r PYARCH HAVE_H <<EOF
  37. $("$PYTHON" - <<'PY'
  38. import platform, sysconfig, os
  39. inc = sysconfig.get_path('include')
  40. print(f"{platform.machine()} {int(os.path.exists(os.path.join(inc,'Python.h')))}")
  41. PY
  42. )
  43. EOF
  44. HOSTARCH="$(uname -m)"
  45. [ "$PYARCH" = "$HOSTARCH" ] || fail "Python 架构($PYARCH) ≠ 主机($HOSTARCH)。请用原生 $HOSTARCH 的 Python(如 Homebrew python3.11),不要用 Rosetta 下的 x86_64 Python。"
  46. [ "$HAVE_H" = "1" ] || fail "缺 Python.h 头文件。请装带头文件的 Python(如 Homebrew)。"
  47. # ── 构建专用 venv:装 nuitka + 应用 + provider SDK ─────────
  48. BUILD_VENV="$PROJECT_DIR/.nuitka-venv"
  49. [ -x "$BUILD_VENV/bin/python" ] || "$PYTHON" -m venv "$BUILD_VENV"
  50. BPY="$BUILD_VENV/bin/python"
  51. info "准备构建环境(装 nuitka + 应用 + provider)..."
  52. "$BPY" -m pip install -q --upgrade pip nuitka >/dev/null
  53. # onefile 是 standalone:依赖必须真实安装进构建环境,Nuitka 才能跟进打包
  54. "$BPY" -m pip install -q ./lambdagent ./agentpaas $PROVIDERS
  55. # 仅对真正装上的 provider 生成 --include-module
  56. PROVIDER_FLAGS=()
  57. for p in $PROVIDERS; do
  58. if "$BPY" -c "import $p" >/dev/null 2>&1; then
  59. PROVIDER_FLAGS+=("--include-module=$p")
  60. ok "provider 已纳入: $p"
  61. else
  62. warn "provider 未装上,跳过: $p"
  63. fi
  64. done
  65. # ── Nuitka standalone + onefile ───────────────────────────
  66. OUT="$PROJECT_DIR/build_binary_out"
  67. rm -rf "$OUT"; mkdir -p "$OUT"
  68. ENTRY="agentpaas/src/agentpaas/__main__.py"
  69. MODE_FLAGS=(--standalone)
  70. [ "$ONEFILE" = "1" ] && MODE_FLAGS+=(--onefile)
  71. info "Nuitka 打包二进制(mode: $([ "$ONEFILE" = 1 ] && echo onefile单文件 || echo standalone目录),首次可能 10+ 分钟)..."
  72. "$BPY" -m nuitka \
  73. "${MODE_FLAGS[@]}" \
  74. --output-filename=agentpaas \
  75. --output-dir="$OUT" \
  76. --include-package=uvicorn \
  77. --include-module=uvloop --include-module=httptools \
  78. --include-module=websockets --include-module=h11 \
  79. --include-package=agentpaas --include-package=lambdagent \
  80. "${PROVIDER_FLAGS[@]}" \
  81. --include-data-dir=webui-dist=webui-dist \
  82. --include-data-dir=agentexample/agentpacks=agentexample/agentpacks \
  83. --include-data-dir=agentexample/skills=agentexample/skills \
  84. --python-flag=no_docstrings \
  85. --assume-yes-for-downloads \
  86. "$ENTRY"
  87. # 平台标签 + 可执行后缀(Windows 走 git-bash,uname 形如 MINGW64_NT)
  88. case "$(uname -s)" in
  89. MINGW*|MSYS*|CYGWIN*) OSLABEL=windows; EXE=".exe" ;;
  90. Darwin) OSLABEL=darwin; EXE="" ;;
  91. *) OSLABEL=$(uname -s | tr 'A-Z' 'a-z'); EXE="" ;;
  92. esac
  93. PLATSUFFIX="$OSLABEL-$HOSTARCH"
  94. mkdir -p dist-binary
  95. if [ "$ONEFILE" = "1" ]; then
  96. BIN="$OUT/agentpaas$EXE"
  97. [ -x "$BIN" ] || fail "未生成二进制"
  98. cp "$BIN" "dist-binary/agentpaas-$PLATSUFFIX$EXE"
  99. cp "$BIN" "dist-binary/agentpaas$EXE"
  100. echo ""
  101. ok "单文件二进制已生成 → dist-binary/agentpaas$EXE ($(du -h "$BIN" | cut -f1))"
  102. echo " 用户用法(无需 Python): ./agentpaas$EXE serve --port 8000"
  103. else
  104. # standalone:产出一个目录,整目录即发行件
  105. DISTDIR="$OUT/__main__.dist"
  106. [ -x "$DISTDIR/agentpaas$EXE" ] || fail "未生成 standalone 目录二进制"
  107. rm -rf "dist-binary/agentpaas-$PLATSUFFIX.app"
  108. cp -R "$DISTDIR" "dist-binary/agentpaas-$PLATSUFFIX.app"
  109. echo ""
  110. ok "standalone 目录已生成 → dist-binary/agentpaas-$PLATSUFFIX.app/ ($(du -sh "$DISTDIR" | cut -f1))"
  111. echo " 用户用法(无需 Python): ./agentpaas-$PLATSUFFIX.app/agentpaas$EXE serve --port 8000"
  112. fi
  113. echo ""
  114. warn "提醒: 二进制绑定 $PLATSUFFIX —— 换平台/架构需在对应机器重新构建。"
  115. warn " 未打包: chromadb 向量库、PDF 外部工具(pandoc/xelatex)——用到这些功能仍需系统侧依赖。"