#!/bin/bash # ============================================================== # bundle-python.sh — 将 Python 运行时 + akshare-fetcher 打包进 .app # ============================================================== set -e APP_DIR="${1:-.build/FinDataHub.app}" PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" # 数据源项目路径可通过环境变量覆盖 AKSHARE_SRC="${AKSHARE_SRC:-${PROJECT_ROOT}/Services/akshare_fetcher}" FRAMEWORKS_DIR="${APP_DIR}/Contents/Frameworks" RESOURCES_DIR="${APP_DIR}/Contents/Resources" VENV_DIR="${FRAMEWORKS_DIR}/akshare-venv" AKSHARE_DST="${RESOURCES_DIR}/akshare_fetcher" echo "=== 打包 Python 运行时 ===" echo " .app: ${APP_DIR}" echo "" # ---------- 1. 创建 venv ---------- if [ -d "${VENV_DIR}/bin" ]; then echo "[1/3] 虚拟环境已存在,跳过创建" else echo "[1/3] 创建 Python 虚拟环境..." mkdir -p "${FRAMEWORKS_DIR}" cd "${PROJECT_ROOT}" # uv venv 创建独立虚拟环境(与 bundle 内实际运行时保持一致) uv venv --python 3.14 "${VENV_DIR}" 2>&1 | tail -3 echo " ✅ 虚拟环境已创建" fi source "${VENV_DIR}/bin/activate" echo " Python: $(python3 --version)" # ---------- 2. 安装依赖 ---------- echo "[2/3] 安装 akshare-fetcher 依赖..." cd "${AKSHARE_SRC}" # 从 pyproject.toml 安装依赖(版本约束由 pyproject.toml 管理,避免无版本锁定) pip install --quiet --upgrade pip 2>/dev/null || true install_time=$(date +%s) # 以可编辑模式安装项目,读取 pyproject.toml 中声明的依赖及版本约束 pip install --quiet -e "${AKSHARE_SRC}" 2>&1 | tail -3 # 验证 python3 -c "import akshare; print(f' ✅ akshare {akshare.__version__} 可导入')" 2>&1 python3 -c "import pandas; print(f' ✅ pandas {pandas.__version__}')" 2>&1 python3 -c "import numpy; print(f' ✅ numpy {numpy.__version__}')" 2>&1 du -sh "${VENV_DIR}" echo "" # ---------- 3. 复制项目代码 ---------- echo "[3/3] 部署 akshare-fetcher 项目代码..." mkdir -p "${AKSHARE_DST}" rsync -a --delete \ --exclude='.venv' \ --exclude='.git' \ --exclude='__pycache__' \ --exclude='*.pyc' \ --exclude='*.pyo' \ --exclude='.DS_Store' \ --exclude='uv.lock' \ "${AKSHARE_SRC}/" "${AKSHARE_DST}/" echo " ✅ 项目代码已部署 (${AKSHARE_DST})" # ---------- 4. 创建入口脚本 ---------- echo " 创建入口脚本..." mkdir -p "${RESOURCES_DIR}/bin" cat > "${RESOURCES_DIR}/bin/akshare-fetcher.sh" << 'SCRIPT' #!/bin/bash # FinDataHub 内置 akshare-fetcher 入口 # 使用捆绑的虚拟环境 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" APP_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" VENV_PYTHON="${APP_DIR}/Contents/Frameworks/akshare-venv/bin/python3" PROJECT_DIR="${APP_DIR}/Contents/Resources/akshare_fetcher" export PYTHONUNBUFFERED=1 export AKSHARE_HOME="${PROJECT_DIR}" export PYTHONPATH="${PROJECT_DIR}/src" cd "${PROJECT_DIR}" exec "${VENV_PYTHON}" -m akshare_fetcher "$@" SCRIPT chmod +x "${RESOURCES_DIR}/bin/akshare-fetcher.sh" # 同时创建快捷入口: akshare-fetcher → 直接调项目源码 cat > "${AKSHARE_DST}/run_bundled.sh" << 'SCRIPT' #!/bin/bash # 捆绑环境入口 — 使用 FinDataHub 内建的 venv SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" VENV_PYTHON="${SCRIPT_DIR}/../../Frameworks/akshare-venv/bin/python3" export PYTHONPATH="${SCRIPT_DIR}/src" cd "${SCRIPT_DIR}" exec "${VENV_PYTHON}" -m akshare_fetcher "$@" SCRIPT chmod +x "${AKSHARE_DST}/run_bundled.sh" echo "" echo "=== Python 运行时打包完成 ===" du -sh "${VENV_DIR}" "${AKSHARE_DST}" 2>/dev/null echo "" echo "总大小:" du -sh "${FRAMEWORKS_DIR}" "${RESOURCES_DIR}" 2>/dev/null