fix:二值化红点.采纳#299的两条评审建议. #1523
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: install | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| branches: | |
| - "**" | |
| paths: | |
| - ".github/workflows/install.yml" | |
| - "assets/**" | |
| - "**.py" | |
| pull_request: | |
| branches: | |
| - "**" | |
| paths: | |
| - ".github/workflows/install.yml" | |
| - "assets/**" | |
| - "**.py" | |
| workflow_dispatch: | |
| inputs: | |
| deploy_beta: | |
| type: boolean | |
| description: "发布公测版" | |
| default: false # 默认不勾选,避免误触 | |
| deploy_alpha: | |
| type: boolean | |
| description: "发布内测版" | |
| default: false | |
| ci_as_stable: | |
| type: boolean | |
| description: "CI版本作为正式发布(非预发布)" | |
| default: false | |
| notice_text: | |
| type: string | |
| description: "⚠️ 说明 / Note" | |
| required: false | |
| default: "不勾选任何选项,手动触发即为CI发版" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.deploy_alpha || 'false' }}-${{ inputs.deploy_beta || 'false' }} | |
| cancel-in-progress: true | |
| jobs: | |
| meta: | |
| runs-on: ubuntu-latest | |
| if: "github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') || !contains(github.event.head_commit.message, '[deploy-sync]')" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - id: set_tag | |
| # 通过环境变量传递 Commit Message,防止反引号注入攻击 | |
| env: | |
| HEAD_COMMIT_MSG: ${{ github.event.head_commit.message }} | |
| run: | | |
| # 使用环境变量获取引用信息 | |
| is_tag_push=false | |
| if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
| is_tag_push=true | |
| fi | |
| # 获取提交信息(用于检测 [deploy-beta]) | |
| commit_message="$HEAD_COMMIT_MSG" | |
| if [ "$is_tag_push" = true ]; then | |
| # 正式标签:直接使用标签名 | |
| tag="${GITHUB_REF#refs/tags/}" | |
| version_type="release" | |
| else | |
| # 获取最新的正式版本标签 | |
| base_tag=$(git tag -l "v*" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1) | |
| # 如果没有找到纯版本号标签,使用默认值 | |
| if [ -z "$base_tag" ] || [[ "$base_tag" != v*.*.* ]]; then | |
| base_tag="v0.0.0" | |
| fi | |
| commit_short=$(git rev-parse --short HEAD) | |
| date_suffix=$(date +"%y%m%d") | |
| # 判断版本类型 | |
| # 优先判断 Alpha | |
| if ([ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "${{ github.event.inputs.deploy_alpha }}" = "true" ]) || echo "$commit_message" | tail -n1 | grep -q "\[deploy-alpha\]"; then | |
| # 内测版:在基础版本上增加 2 个补丁版本号 (Base + 2) | |
| # 修改点:将 $3+1 改为 $3+2 | |
| incremented_tag=$(echo "$base_tag" | awk -F. '{printf "%s.%s.%s", $1, $2, $3+2}') | |
| tag="${incremented_tag}-alpha.${date_suffix}.${commit_short}" | |
| version_type="alpha" | |
| # 原有的 Beta 判断 | |
| elif ([ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "${{ github.event.inputs.deploy_beta }}" = "true" ]) || echo "$commit_message" | tail -n1 | grep -q "\[deploy-beta\]"; then | |
| # 公测版:在基础版本上增加 1 个补丁版本号 (Base + 1) | |
| incremented_tag=$(echo "$base_tag" | awk -F. '{printf "%s.%s.%s", $1, $2, $3+1}') | |
| tag="${incremented_tag}-beta.${date_suffix}.${commit_short}" | |
| version_type="beta" | |
| # 原有的 CI 判断 (else 分支) | |
| else | |
| tag="${base_tag}-ci.${date_suffix}.${commit_short}" | |
| version_type="ci" | |
| fi | |
| fi | |
| # 添加调试信息 | |
| echo "Base tag: $base_tag" | |
| echo "Generated tag: $tag" | |
| echo "Commit short: $commit_short" | |
| echo "Date suffix: $date_suffix" | |
| echo "Version type: $version_type" | |
| echo "tag=$tag" | tee -a $GITHUB_OUTPUT | |
| echo "is_tag_push=$is_tag_push" | tee -a $GITHUB_OUTPUT | |
| echo "is_dev_version=$([ "$version_type" = "ci" ] && echo "true" || echo "false")" | tee -a $GITHUB_OUTPUT | |
| echo "version_type=$version_type" | tee -a $GITHUB_OUTPUT | |
| # 2. 下载资源 (用于探测) | |
| - name: Parse Configuration | |
| id: parse_config | |
| run: | | |
| python3 -c " | |
| import re, os, sys | |
| req_file = 'requirements.txt' | |
| mfaa_tag = '' | |
| if os.path.exists(req_file): | |
| with open(req_file, 'r', encoding='utf-8') as f: | |
| for line in f: | |
| m = re.match(r'^#\s*MFAA_TAG=(v[\w\.\-]+)', line.strip()) | |
| if m: mfaa_tag = m.group(1); break | |
| if not mfaa_tag: | |
| print('::error::未找到 # MFAA_TAG 配置'); sys.exit(1) | |
| print(f'✅ 目标资源版本: {mfaa_tag}') | |
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | |
| f.write(f'mfaa_tag={mfaa_tag}\n') | |
| " | |
| # [修改] 下载资源 (改用 steps.parse_config.outputs.mfaa_tag) | |
| - name: Download MFAA for Detection | |
| uses: robinraju/release-downloader@v1 | |
| with: | |
| repository: MaaXYZ/MFAAvalonia | |
| latest: false | |
| tag: ${{ steps.parse_config.outputs.mfaa_tag }} | |
| fileName: "MFAAvalonia-*-linux-x64*" | |
| out-file-path: "temp_detect" | |
| extract: true | |
| # 3. 探测内核版本 | |
| - name: Detect Core Version | |
| id: detect_maa | |
| run: | | |
| # 1. 使用 find 查找文件的【绝对路径】(加上 $(pwd)) | |
| SO_FILE=$(find "$(pwd)/temp_detect" -name "libMaaFramework.so" | head -n 1) | |
| if [ -z "$SO_FILE" ]; then | |
| echo "::error::根本找不到 libMaaFramework.so 文件!" | |
| exit 1 | |
| fi | |
| SO_DIR=$(dirname "$SO_FILE") | |
| echo "📍 定位到库文件: $SO_FILE" | |
| echo "🔧 库目录: $SO_DIR" | |
| # 2. 【关键】设置 LD_LIBRARY_PATH 为绝对路径 | |
| # 这告诉 Linux:"加载库的时候,去这个目录里找依赖!" | |
| export LD_LIBRARY_PATH="$SO_DIR:$LD_LIBRARY_PATH" | |
| # 3. 【调试神器】运行 ldd | |
| # 如果后面 Python 报错,看这部分日志就知道缺哪个库了 | |
| echo "=== 🕵️♂️ 依赖检查 (ldd) ===" | |
| ldd "$SO_FILE" || true | |
| echo "=========================" | |
| # 4. 生成探测脚本 | |
| cat << 'EOF' > detect.py | |
| import sys, ctypes, os | |
| # 从环境变量获取刚才找到的绝对路径 | |
| so_path = os.environ.get("TARGET_SO_PATH") | |
| print(f"🐍 Python loading: {so_path}") | |
| try: | |
| # mode=ctypes.RTLD_GLOBAL 有时能解决跨库符号依赖问题 | |
| lib = ctypes.CDLL(so_path, mode=ctypes.RTLD_GLOBAL) | |
| lib.MaaVersion.restype = ctypes.c_char_p | |
| ver = lib.MaaVersion().decode('utf-8') | |
| print(f"✅ Detected Core: {ver}") | |
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | |
| f.write(f"version={ver}\n") | |
| except Exception as e: | |
| print(f"::error::加载失败: {e}") | |
| sys.exit(1) | |
| EOF | |
| # 5. 运行脚本 (传入路径变量) | |
| export TARGET_SO_PATH="$SO_FILE" | |
| python3 detect.py | |
| outputs: | |
| tag: ${{ steps.set_tag.outputs.tag }} | |
| is_tag_push: ${{ steps.set_tag.outputs.is_tag_push }} | |
| is_dev_version: ${{ steps.set_tag.outputs.is_dev_version }} | |
| version_type: ${{ steps.set_tag.outputs.version_type }} | |
| maa_version: ${{ steps.detect_maa.outputs.version }} | |
| mfaa_tag: ${{ steps.parse_config.outputs.mfaa_tag }} | |
| resource_check: | |
| name: Check Resources | |
| needs: meta | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.10" | |
| - name: Install maafw | |
| env: | |
| TARGET_VERSION: ${{ needs.meta.outputs.maa_version }} | |
| run: | | |
| python -m pip install --upgrade pip | |
| echo "📦 Installing maafw==$TARGET_VERSION (Linux)..." | |
| python -m pip install maafw=="$TARGET_VERSION" | |
| - name: Restore Assets (Clone from Resource Repo) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: sunyink/MFABD2-Assets | |
| ref: master | |
| path: temp_assets | |
| fetch-depth: 1 | |
| - name: Merge Assets | |
| shell: bash | |
| run: | | |
| mkdir -p assets/MaaCommonAssets | |
| cp -r temp_assets/MaaCommonAssets/* assets/MaaCommonAssets/ | |
| rm -rf temp_assets | |
| - name: Check Resource | |
| run: | | |
| # 赋予执行权限,防止 Linux 下报错 | |
| chmod +x ./check_resource.py | |
| python ./check_resource.py ./assets/resource/ | |
| install: | |
| needs: [meta, resource_check] | |
| # PR 只跑资源检查,不跑完整构建 | |
| if: github.event_name != 'pull_request' | |
| # 动态选择运行环境:Win跑Win,Mac跑Mac,Linux/安卓跑Ubuntu | |
| runs-on: ${{ matrix.runner }} | |
| env: | |
| MAA_VERSION: ${{ needs.meta.outputs.maa_version }} | |
| MFAA_TAG: ${{ needs.meta.outputs.mfaa_tag }} | |
| # 解决 Windows 乱码问题 | |
| PYTHONUTF8: 1 | |
| PYTHONIOENCODING: utf-8 | |
| # 解决部分 Windows 终端不认 ANSI 颜色的问题(可选) | |
| TERM: xterm | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # ------------------------------------------------------------------- | |
| # 🪟 Windows (x86_64) - 使用 Python 3.10 | |
| # ------------------------------------------------------------------- | |
| - os: win | |
| arch: x86_64 | |
| runner: windows-latest | |
| mfa_os: "win" | |
| mfa_arch: "x64" | |
| # Windows 专用配置 | |
| embed_version: "3.10.11" # Python 嵌入包版本 | |
| py_ver_short: "3.10" # 用于 pip 参数 | |
| py_arch: "amd64" # Python 官网文件名后缀 | |
| pip_plat: "win_amd64" # pip 平台标签 | |
| numpy_req: "numpy<2" | |
| is_android: false | |
| # ------------------------------------------------------------------- | |
| # 🪟 Windows (ARM64) - 使用 Python 3.11 (原生支持更好) | |
| # ------------------------------------------------------------------- | |
| - os: win | |
| arch: aarch64 | |
| runner: windows-latest | |
| mfa_os: "win" | |
| mfa_arch: "arm64" | |
| # Windows 专用配置 (切到 3.11) | |
| embed_version: "3.11.9" | |
| py_ver_short: "3.11" | |
| py_arch: "arm64" | |
| pip_plat: "win_arm64" | |
| numpy_req: "numpy>=2" # winarm64 没有<2的预编译包. | |
| is_android: false | |
| # ------------------------------------------------------------------- | |
| # 🍎 macOS (x64) | |
| # ------------------------------------------------------------------- | |
| - os: macos | |
| arch: x86_64 | |
| runner: macos-latest | |
| mfa_os: "osx" | |
| mfa_arch: "x64" | |
| numpy_req: "numpy<2" | |
| is_android: false | |
| standalone_url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13+20240107-x86_64-apple-darwin-install_only.tar.gz" | |
| standalone_sha256: "47da6831e269ad6af603f59bbd4767636c5749bd70a24363c9cf003c32c8ecfc" | |
| # ------------------------------------------------------------------- | |
| # 🍎 macOS (ARM64) | |
| # ------------------------------------------------------------------- | |
| - os: macos | |
| arch: aarch64 | |
| runner: macos-latest | |
| mfa_os: "osx" | |
| mfa_arch: "arm64" | |
| numpy_req: "numpy<2" | |
| is_android: false | |
| standalone_url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13+20240107-aarch64-apple-darwin-install_only.tar.gz" | |
| standalone_sha256: "4d19a0509c274f360a5c250d71a2af7263ebaf898ecdb71427f3532cbc6b2cf7" | |
| # ------------------------------------------------------------------- | |
| # 🐧 Linux (x64) | |
| # ------------------------------------------------------------------- | |
| - os: linux | |
| arch: x86_64 | |
| runner: ubuntu-latest | |
| mfa_os: "linux" | |
| mfa_arch: "x64" | |
| numpy_req: "numpy<2" | |
| is_android: false | |
| # ------------------------------------------------------------------- | |
| # 🐧 Linux (ARM64) | |
| # ------------------------------------------------------------------- | |
| - os: linux | |
| arch: aarch64 | |
| runner: ubuntu-latest | |
| mfa_os: "linux" | |
| mfa_arch: "arm64" | |
| numpy_req: "numpy<2" | |
| is_android: false | |
| # ------------------------------------------------------------------- | |
| # 🤖 Android (ARM64) - 特殊逻辑 | |
| # ------------------------------------------------------------------- | |
| - os: android | |
| arch: aarch64 | |
| runner: ubuntu-latest | |
| is_android: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| # 设置构建环境的 Python (用于运行 install.py 等脚本) | |
| # 这里的版本不影响 Windows 打包进去的嵌入式 Python 版本 | |
| - name: Set up Build Environment | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.10" | |
| # 显式指定 bash,确保在 Windows 上也能用 cp/rm/mkdir 等命令 | |
| - name: Install Build Dependencies | |
| shell: bash | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install requests json-with-comments | |
| # ---------------------------------------------------------------- | |
| # 📦 资源下载 (根据 Matrix 变量动态处理) | |
| # ---------------------------------------------------------------- | |
| # [桌面端] 下载 MFAAvalonia | |
| - name: Download MFAAvalonia | |
| if: matrix.is_android == false | |
| uses: robinraju/release-downloader@v1 | |
| with: | |
| repository: MaaXYZ/MFAAvalonia | |
| latest: false | |
| tag: ${{ env.MFAA_TAG }} | |
| # 直接使用 Matrix 里的变量,无需复杂判断 | |
| fileName: "MFAAvalonia-*-${{ matrix.mfa_os }}-${{ matrix.mfa_arch }}*" | |
| out-file-path: "MFA" | |
| extract: true | |
| # [安卓端] 下载 MaaFramework Core | |
| - name: Download MaaFramework (Android) | |
| if: matrix.is_android == true | |
| uses: robinraju/release-downloader@v1 | |
| with: | |
| repository: MaaXYZ/MaaFramework | |
| latest: false | |
| tag: "${{ env.MAA_VERSION }}" | |
| # Android 文件名格式通常是 MAA-android-arm64-xxxx | |
| fileName: "MAA-android-${{ matrix.arch }}*" | |
| out-file-path: "AndroidCore" | |
| extract: true | |
| - name: Restore Assets (Clone from Resource Repo) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: sunyink/MFABD2-Assets | |
| ref: master | |
| path: temp_assets | |
| fetch-depth: 1 | |
| - name: Merge Assets | |
| shell: bash | |
| run: | | |
| # 创建目标目录结构(如果不存在) | |
| mkdir -p assets/MaaCommonAssets | |
| # 解开 MaaCommonAssets/MaaCommonAssets 套娃 | |
| cp -r temp_assets/MaaCommonAssets/* assets/MaaCommonAssets/ | |
| rm -rf temp_assets | |
| echo "✅ 资源合并完成" | |
| # ---------------------------------------------------------------- | |
| # Windows 专属:嵌入式 Python 打包 | |
| # ---------------------------------------------------------------- | |
| - name: Setup Embed Python (Windows) | |
| if: matrix.os == 'win' | |
| shell: bash | |
| run: | | |
| echo "⬇️ Downloading Python Embed ${{ matrix.embed_version }} for ${{ matrix.py_arch }}..." | |
| # 1. 下载:利用 Matrix 变量 (支持 3.10 和 3.11) | |
| curl -L -o python_embed.zip "https://www.python.org/ftp/python/${{ matrix.embed_version }}/python-${{ matrix.embed_version }}-embed-${{ matrix.py_arch }}.zip" | |
| # 2. 解压 (Windows Runner 自带 7z) | |
| mkdir -p install/python | |
| 7z x python_embed.zip -oinstall/python | |
| # 开启 site 支持 | |
| python3 -c "import pathlib; p = next(pathlib.Path('install/python').glob('*._pth')); p.write_text(p.read_text().replace('#import site', 'import site'))" | |
| # 3. 开启 site 支持 | |
| # 注意:这里我们构造 python3x._pth 文件名,因为 3.10 是 python310._pth,3.11 是 python311._pth | |
| # 简单的做法是通配符,或者用 Python 脚本找 | |
| python3 -c " | |
| import pathlib | |
| import re | |
| pth_file = next(pathlib.Path('install/python').glob('python*._pth')) | |
| print(f'Modifying {pth_file}') | |
| pth_file.write_text(pth_file.read_text().replace('#import site', 'import site')) | |
| " | |
| echo "📦 Installing Dependencies for ${{ matrix.pip_plat }} (Python ${{ matrix.py_ver_short }})..." | |
| # 4. 重点:安装依赖 | |
| # 使用 --only-binary=:all: 强制下载 Wheel 包 (这对 Pillow/Numpy 在 Windows 上至关重要) | |
| # 使用 Python 精准剔除 numpy,防止误伤注释或其他包 | |
| python3 -c " | |
| import re | |
| with open('requirements.txt', 'r') as f_in, open('requirements_no_numpy.txt', 'w') as f_out: | |
| for line in f_in: | |
| # 正则解释: | |
| # ^\s* : 行首允许有空格 | |
| # numpy : 必须是 numpy | |
| # \s* : 允许 numpy 后面有空格 | |
| # ([<>=!~;]|$) : 后面必须跟着版本比较符、分号,或者是行尾 | |
| if not re.match(r'^\s*numpy\s*([<>=!~;]|$)', line, re.IGNORECASE): | |
| f_out.write(line) | |
| " | |
| python3 -m pip install \ | |
| --platform ${{ matrix.pip_plat }} \ | |
| --python-version ${{ matrix.py_ver_short }} \ | |
| --only-binary=:all: \ | |
| --no-cache-dir \ | |
| --target install/python/Lib/site-packages \ | |
| "${{ matrix.numpy_req }}" \ | |
| -r requirements_no_numpy.txt \ | |
| maafw==$MAA_VERSION | |
| # ---------------------------------------------------------------- | |
| # [新增] macOS 专属:Portable Python 打包 | |
| # ---------------------------------------------------------------- | |
| - name: Setup Portable Python (macOS) | |
| if: matrix.os == 'macos' | |
| shell: bash | |
| run: | | |
| echo "⬇️ Downloading Portable Python for macOS..." | |
| # 失败重试和SHA校验 | |
| curl -fL --retry 3 --retry-delay 2 -o python_mac.tar.gz "${{ matrix.standalone_url }}" | |
| echo "${{ matrix.standalone_sha256 }} python_mac.tar.gz" | shasum -a 256 -c - | |
| # 解压 (standalone 包结构通常是 python/install/...) | |
| mkdir -p temp_python | |
| tar -xzf python_mac.tar.gz -C temp_python | |
| # 💡 调试输出一下目录结构,防止以后包结构又变 | |
| echo "📂 预计解压后的目录结构如下:" | |
| ls -la temp_python/python/ | |
| # 移动到 install/python | |
| mkdir -p install/python | |
| cp -r temp_python/python/* install/python/ | |
| rm -rf temp_python python_mac.tar.gz | |
| # 设置权限 (非常重要) | |
| chmod +x install/python/bin/python3 | |
| echo "📦 Installing Dependencies (macOS)..." | |
| PY_EXEC="install/python/bin/python3" | |
| # 剔除 numpy | |
| "$PY_EXEC" -c " | |
| import re | |
| with open('requirements.txt', 'r') as f_in, open('requirements_no_numpy.txt', 'w') as f_out: | |
| for line in f_in: | |
| if not re.match(r'^\s*numpy\s*([<>=!~;]|$)', line, re.IGNORECASE): | |
| f_out.write(line) | |
| " | |
| # 安装依赖到便携版环境 | |
| "$PY_EXEC" -m pip install --upgrade pip | |
| "$PY_EXEC" -m pip install "${{ matrix.numpy_req }}" -r requirements_no_numpy.txt maafw==$MAA_VERSION | |
| # 清理缓存 | |
| "$PY_EXEC" -m pip cache purge | |
| find install/python -name "__pycache__" -type d -exec rm -rf {} + | |
| # 🔧 macOS 便携 Python 的 pip 安装时可能丢失 wheel 内的 .dylibs 目录 | |
| # (如 numpy/.dylibs/libopenblas64_.0.dylib, PIL/.dylibs/libtiff.6.dylib 等) | |
| # 此处扫描所有 .so 文件的 @loader_path 依赖,缺失则从对应 wheel 中提取补回 | |
| echo "🔍 Verifying native dylibs..." | |
| "$PY_EXEC" -c " | |
| import os, sys, subprocess, tempfile, zipfile, shutil, re, importlib.metadata, sysconfig | |
| from pathlib import Path | |
| site = Path(sysconfig.get_path('purelib')) | |
| if not site.exists(): | |
| sys.exit(0) | |
| # 导入名 -> PyPI 分发名的映射 (部分包的目录名与分发名不一致) | |
| DIS_NAME = { | |
| 'PIL': 'Pillow', | |
| 'cv2': 'opencv-python-headless', | |
| } | |
| try: | |
| for dist, names in importlib.metadata.packages_distributions().items(): | |
| for n in names: | |
| DIS_NAME.setdefault(n, dist) | |
| except Exception: | |
| pass | |
| def resolve_dist(pkg): | |
| '''目录名 -> PyPI 分发名, 多级回退''' | |
| for k, v in DIS_NAME.items(): | |
| if k.lower() == pkg.lower(): | |
| return v | |
| return pkg | |
| broken = {} # pkg_dir -> {'dist': dist_name, 'dylibs': set()} | |
| for so in site.rglob('*.so'): | |
| try: | |
| out = subprocess.check_output(['otool', '-L', str(so)], text=True) | |
| except Exception: | |
| continue | |
| for line in out.split('\n'): | |
| m = re.match(r'\s+(@loader_path/\S+)', line) | |
| if not m: | |
| continue | |
| ref = m.group(1) | |
| resolved = (so.parent / ref.replace('@loader_path/', '')).resolve() | |
| if not resolved.exists(): | |
| pkg = so.relative_to(site).parts[0] | |
| broken.setdefault(pkg, {'dist': resolve_dist(pkg), 'dylibs': set()}) | |
| broken[pkg]['dylibs'].add(os.path.basename(ref)) | |
| if not broken: | |
| print('✅ All native dylibs present') | |
| sys.exit(0) | |
| print(f'⚠️ {len(broken)} package(s) with missing dylibs, repairing...') | |
| plat = sysconfig.get_platform().replace('-', '_').replace('.', '_') | |
| py_ver = f'{sys.version_info.major}.{sys.version_info.minor}' | |
| for pkg, info in sorted(broken.items()): | |
| dist_name = info['dist'] | |
| try: | |
| ver = importlib.metadata.version(dist_name) | |
| except Exception: | |
| print(f' ⚠️ Skip {pkg} (dist: {dist_name}): cannot determine version') | |
| continue | |
| print(f' 📦 {pkg} -> {dist_name}=={ver}') | |
| with tempfile.TemporaryDirectory() as tmp: | |
| result = subprocess.run([ | |
| sys.executable, '-m', 'pip', 'download', | |
| f'{dist_name}=={ver}', '--no-deps', '--dest', tmp, | |
| '--platform', plat, '--python-version', py_ver, | |
| '--only-binary=:all:', '--no-cache-dir', | |
| ], capture_output=True, text=True) | |
| if result.returncode != 0: | |
| print(f' ⚠️ pip download failed: {result.stderr.strip()}') | |
| continue | |
| whls = [f for f in os.listdir(tmp) if f.endswith('.whl')] | |
| if not whls: | |
| print(f' ⚠️ No wheel found') | |
| continue | |
| with zipfile.ZipFile(os.path.join(tmp, whls[0])) as zf: | |
| # 提取 wheel 内所有 .dylib (而非仅缺失的), | |
| # 确保同包的多个 dylib 版本一致, 避免部分替换导致 ABI 不匹配 | |
| names = [n for n in zf.namelist() if n.endswith('.dylib')] | |
| if not names: | |
| print(f' ℹ️ No dylibs in wheel') | |
| continue | |
| dst = site / pkg / '.dylibs' | |
| os.makedirs(dst, exist_ok=True) | |
| for n in names: | |
| zf.extract(n, tmp) | |
| shutil.copy2(os.path.join(tmp, n), dst / os.path.basename(n)) | |
| os.chmod(dst / os.path.basename(n), 0o755) | |
| print(f' ✅ {os.path.basename(n)}') | |
| print('✅ Dylib repair complete') | |
| " | |
| # ---------------------------------------------------------------- | |
| # 🏗️ 构建与组装 | |
| # ---------------------------------------------------------------- | |
| - name: Install & Merge | |
| shell: bash | |
| run: | | |
| # 1. 运行 install.py (生成配置、版本号等) | |
| # install.py 需要处理好路径分隔符 | |
| python ./install.py ${{ needs.meta.outputs.tag }} ${{ matrix.os }} ${{ env.MAA_VERSION }} | |
| # 2. [桌面端逻辑] 合并 MFAAvalonia | |
| if [[ "${{ matrix.is_android }}" == "false" ]]; then | |
| if [ -d "MFA" ]; then | |
| mkdir -p install | |
| # 这里的 cleanup 移到专门的步骤里做,防止 bash 前没删干净 | |
| # 为了稳健,先在源目录删一次 | |
| find MFA -name "*.zip" -delete 2>/dev/null || true | |
| find MFA -name "*.tar.gz" -delete 2>/dev/null || true | |
| # 使用 bash 覆盖 | |
| cp -rf MFA/* install/ | |
| fi | |
| # 3. [安卓逻辑] 合并 MaaFramework Core | |
| else | |
| echo "🤖 处理 Android 内核合并..." | |
| if [ -d "AndroidCore" ]; then | |
| cp -r AndroidCore/* install/ | |
| echo "✅ Android 内核已合并" | |
| else | |
| echo "❌ 未找到 AndroidCore 目录" | |
| exit 1 | |
| fi | |
| fi | |
| # [核心修复 1] 强力清理残留的压缩包 | |
| - name: Cleanup Archives | |
| shell: bash | |
| run: | | |
| echo "🧹 Cleaning up zip/tar.gz files..." | |
| rm -f *.zip *.tar.gz | |
| rm -rf MFA AndroidCore | |
| # 清理 install 目录里可能被拷进去的压缩包 | |
| find install -name "*.zip" ! -name "python*.zip" -delete 2>/dev/null || true | |
| find install -name "*.tar.gz" -delete 2>/dev/null || true | |
| echo "✅ Cleanup done." | |
| # [核心修复 2] 原版逻辑:利用 Python 脚本清理不匹配的 Runtimes | |
| - name: Prune Runtimes (Architecture Slimming) | |
| if: matrix.is_android == false | |
| shell: bash | |
| run: | | |
| python3 -c " | |
| import shutil, os | |
| from pathlib import Path | |
| # 从 Matrix 获取目标特征 | |
| target_os = '${{ matrix.mfa_os }}' # win, osx, linux | |
| target_arch = '${{ matrix.mfa_arch }}' # x64, arm64 | |
| print(f'🎯 Target Runtime: {target_os}-{target_arch}') | |
| # 定义需要保留的关键词 | |
| keep_keywords = [] | |
| keep_keywords.append(target_os) | |
| if target_arch == 'x64': keep_keywords.extend(['x64', 'amd64']) | |
| else: keep_keywords.extend(['arm64', 'aarch64']) | |
| runtimes_dir = Path('install/runtimes') | |
| if runtimes_dir.exists(): | |
| for p in runtimes_dir.iterdir(): | |
| if not p.is_dir(): continue | |
| # 逻辑:必须包含 OS,且必须包含架构 | |
| # 1. 检查是否匹配 OS (如果不包含 target_os 且包含其他os名字 -> 删) | |
| name = p.name.lower() | |
| # 这里做一个简化的强匹配逻辑: | |
| # 只要文件夹名字里包含了 'win','linux','osx' 中的任意一个,它就必须包含 target_os | |
| has_os_tag = any(k in name for k in ['win', 'linux', 'osx']) | |
| if has_os_tag and target_os not in name: | |
| shutil.rmtree(p); continue | |
| # 2. 检查架构 | |
| has_arch_tag = any(k in name for k in ['x64', 'amd64', 'arm64', 'aarch64']) | |
| # 检查是否包含我们要的架构 | |
| match_target_arch = any(k in name for k in keep_keywords if k in ['x64', 'amd64', 'arm64', 'aarch64']) | |
| if has_arch_tag and not match_target_arch: | |
| print(f'🗑️ Pruning mismatched arch: {name}') | |
| shutil.rmtree(p) | |
| " | |
| - name: Inject Announcement & Verify | |
| shell: bash | |
| run: | | |
| echo "::group::🚀 开始注入公告" | |
| python scripts/inject_announcement.py "${{ needs.meta.outputs.tag }}" | |
| echo "::endgroup::" | |
| echo "::group::👀 验证注入结果" | |
| TARGET="install/resource/Announcement/1.公告.md" | |
| if [ -f "$TARGET" ]; then | |
| AFTER_ANCHOR=$(grep -A 3 "Msg-Anch" "$TARGET" | tail -n +2 | tr -d '[:space:]') | |
| if [ -n "$AFTER_ANCHOR" ]; then | |
| echo "✅ 已注入通知" | |
| echo "--- 注入内容预览 ---" | |
| grep -A 20 "Msg-Anch" "$TARGET" | head -25 | |
| else | |
| echo "ℹ️ 本次未注入(草稿为空 / 不在目标版本范围 / 等)" | |
| fi | |
| else | |
| echo "::error::目标文件不存在: $TARGET" | |
| exit 1 | |
| fi | |
| echo "::endgroup::" | |
| - name: Remove redundant MAA binaries (Win Only) | |
| if: matrix.os == 'win' | |
| shell: bash | |
| run: | | |
| # 仅针对 Windows,因为只有 Windows 下载了 Python 包 | |
| python3 -c " | |
| import shutil | |
| from pathlib import Path | |
| root = Path('install') | |
| # 1. 寻找源 plugins 目录 (在 python 包里) | |
| src_plugins = next(root.rglob('**/site-packages/maa/bin/plugins'), None) | |
| # 2. 寻找目标 native 目录 (在 runtimes 里) | |
| # 假设 install/runtimes 下只有一个平台文件夹,或者我们只关心 native 层级 | |
| target_native = next(root.rglob('runtimes/*/native'), None) | |
| # 3. 如果两者都存在,执行移动 | |
| if src_plugins and target_native and src_plugins.exists(): | |
| dest_plugins = target_native / 'plugins' | |
| if not dest_plugins.exists(): | |
| print(f'Moving plugins from {src_plugins} to {dest_plugins}') | |
| shutil.move(str(src_plugins), str(dest_plugins)) | |
| else: | |
| print(f'Target plugins dir already exists: {dest_plugins}') | |
| # 4. 安全删除所有冗余的 bin 目录 | |
| # 删除 install/python/.../maa/bin,因为 runtimes 里已经有了 | |
| for p in Path('install').rglob('**/site-packages/maa/bin'): | |
| if p.is_dir(): | |
| print(f'Removing redundant bin: {p}') | |
| shutil.rmtree(p) | |
| " | |
| - uses: actions/upload-artifact@v5 | |
| with: | |
| name: MFABD2-${{ needs.meta.outputs.tag }}-${{ matrix.os }}-${{ matrix.is_android && 'arm64' || matrix.arch }} | |
| path: "install" | |
| include-hidden-files: true | |
| changelog: | |
| name: Generate changelog | |
| runs-on: ubuntu-latest | |
| needs: meta | |
| outputs: | |
| release_body: ${{ steps.set_release_body.outputs.content }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.x" | |
| - name: Install Python dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install requests | |
| # ✅ 使用新系统替换git-cliff | |
| - name: Generate comprehensive changelog | |
| id: merge_changelog | |
| run: | | |
| cd scripts | |
| python changelog_generator.py | |
| env: | |
| CURRENT_TAG: ${{ needs.meta.outputs.tag }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} | |
| # 上传 CHANGES.md 到构建物 | |
| # ======================================================= | |
| - name: Upload Changelog Artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: CHANGES | |
| path: CHANGES.md | |
| # ======================================================= | |
| - name: Set release body | |
| id: set_release_body | |
| run: | | |
| # 使用随机定界符防止内容包含 EOF 导致截断 | |
| EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) | |
| echo "content<<$EOF" >> $GITHUB_OUTPUT | |
| cat CHANGES.md >> $GITHUB_OUTPUT | |
| echo "$EOF" >> $GITHUB_OUTPUT | |
| release: | |
| if: (needs.meta.outputs.is_tag_push == 'true') || contains(needs.meta.outputs.tag, '-beta') || contains(needs.meta.outputs.tag, '-alpha') || (github.event_name == 'workflow_dispatch' && contains(needs.meta.outputs.tag, '-ci')) | |
| needs: [meta, install, changelog] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Get current timestamp | |
| id: get_time | |
| run: echo "time=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| path: artifacts | |
| - name: Package release assets | |
| run: | | |
| cd artifacts | |
| mkdir -p release_assets | |
| for dir in *; do | |
| # 直接使用目录名作为 zip 文件名 | |
| zip_name="${dir}.zip" | |
| # 创建压缩包 | |
| (cd "$dir" && zip -r "../release_assets/$zip_name" .) | |
| done | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: artifacts/release_assets/*.zip | |
| tag_name: ${{ needs.meta.outputs.tag }} | |
| name: "MFABD2 ${{ needs.meta.outputs.tag }}" | |
| target_commitish: ${{ github.sha }} | |
| body: | | |
| ${{ contains(needs.changelog.outputs.release_body, '## ') && needs.changelog.outputs.release_body || '### 无显著变更' }} | |
| draft: false | |
| prerelease: ${{ contains(needs.meta.outputs.tag, '-beta') || contains(needs.meta.outputs.tag, '-alpha') || (contains(needs.meta.outputs.tag, '-ci') && !(github.event_name == 'workflow_dispatch' && github.event.inputs.ci_as_stable == 'true')) }} | |
| env: # 添加环境变量以支持高限额API调用 | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Trigger MirrorChyanUploading | |
| if: github.repository_owner == 'sunyink' && ((needs.meta.outputs.is_tag_push == 'true') || contains(needs.meta.outputs.tag, '-beta') || contains(needs.meta.outputs.tag, '-alpha')) | |
| shell: bash | |
| env: | |
| tag: ${{ needs.meta.outputs.tag }} | |
| CHANGELOG_BODY: ${{ needs.changelog.outputs.release_body }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "安全触发镜像服务: 版本=$tag" | |
| # 将 " 替换为 \" | |
| ESCAPED_BODY="${CHANGELOG_BODY//\"/\\\"}" | |
| # 触发包发布流程 | |
| gh workflow run --repo $GITHUB_REPOSITORY --ref ${{ github.ref }} mirrorchyan.yml -f tag=$tag | |
| # 触发更新信息推送流程 (现在可以安全处理带引号的日志了) | |
| gh workflow run --repo $GITHUB_REPOSITORY --ref ${{ github.ref }} mirrorchyan_release_note.yml -f tag=$tag -f body="$ESCAPED_BODY" | |
| notify: | |
| needs: [meta, install] | |
| if: always() && needs.install.result == 'success' && (github.event_name == 'workflow_dispatch' || needs.meta.outputs.version_type != 'ci') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Extract commit info | |
| id: commit-info | |
| env: | |
| RAW_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} | |
| RAW_COMMIT_TIMESTAMP: ${{ github.event.head_commit.timestamp }} | |
| run: | | |
| # 调试:显示所有可用参数 | |
| echo "=== 调试信息 ===" | |
| echo "GITHUB_EVENT_NAME: $GITHUB_EVENT_NAME" | |
| echo "HEAD_COMMIT_MESSAGE: $RAW_COMMIT_MESSAGE" | |
| echo "HEAD_COMMIT_TIMESTAMP: $RAW_COMMIT_TIMESTAMP" | |
| echo "GITHUB_SHA: $GITHUB_SHA" | |
| # 设置默认值,直接引用环境变量 | |
| COMMIT_MSG="$RAW_COMMIT_MESSAGE" | |
| if [ -z "$COMMIT_MSG" ]; then | |
| COMMIT_MSG="手动触发工作流" | |
| fi | |
| COMMIT_TIMESTAMP="$RAW_COMMIT_TIMESTAMP" | |
| if [ -z "$COMMIT_TIMESTAMP" ]; then | |
| COMMIT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") | |
| fi | |
| COMMIT_SHA="$GITHUB_SHA" | |
| if [ -z "$COMMIT_SHA" ]; then | |
| COMMIT_SHA="unknown" | |
| fi | |
| # 提取第一行作为摘要 | |
| SUBJECT=$(echo "$COMMIT_MSG" | head -n 1) | |
| # 简化描述处理 | |
| if [ $(echo "$COMMIT_MSG" | wc -l) -gt 1 ]; then | |
| BODY=$(echo "$COMMIT_MSG" | tail -n +2 | head -c 100 | tr -d '\n') | |
| if [ ${#BODY} -eq 100 ]; then | |
| BODY="$BODY..." | |
| fi | |
| else | |
| BODY="" | |
| fi | |
| # 格式化时间戳为东八区时间 | |
| TIME=$(TZ='Asia/Shanghai' date -d "$COMMIT_TIMESTAMP" +"%Y-%m-%d %H:%M:%S") | |
| SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7) | |
| # 输出变量 | |
| echo "subject=$SUBJECT" >> $GITHUB_OUTPUT | |
| echo "body=$BODY" >> $GITHUB_OUTPUT | |
| echo "time=$TIME" >> $GITHUB_OUTPUT | |
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| echo "=== 处理结果 ===" | |
| echo "Subject: $SUBJECT" | |
| echo "Body: $BODY" | |
| echo "Time: $TIME" | |
| echo "Short SHA: $SHORT_SHA" | |
| - name: Send Notification | |
| uses: Y2Nk4/qmsg-action@master | |
| with: | |
| # qq: ${{ needs.meta.outputs.version_type == 'ci' && secrets.QMSG_QQ_DEV || secrets.QMSG_QQ_PUB }} | |
| groups: ${{ needs.meta.outputs.version_type == 'ci' && secrets.QMSG_GROUPS_DEV || secrets.QMSG_GROUPS_PUB }} | |
| # groups: ${{ secrets.QMSG_NOTIFY_GROUPS }} | |
| key: ${{ secrets.QMSG_KEY }} | |
| message: | | |
| 🏗️ [${{ github.repository }}] 构建完成 | |
| 👤 提交者: ${{ github.actor }} | |
| 🌿 分支: ${{ github.ref_name }} | |
| 🏷️ 版本: ${{ needs.meta.outputs.tag }} | |
| 📦 类型: ${{ contains(needs.meta.outputs.tag, '-alpha') && '内测版' || (contains(needs.meta.outputs.tag, '-beta') && '公测版' || (contains(needs.meta.outputs.tag, '-ci') && '开发版' || '正式版')) }} | |
| 🆔 提交哈希: ${{ steps.commit-info.outputs.short_sha }} | |
| ⏱️ 提交时间: ${{ steps.commit-info.outputs.time }} | |
| 📝 摘要: ${{ steps.commit-info.outputs.subject }} | |
| ${{ steps.commit-info.outputs.body != '' && format('📖 描述: {0}', steps.commit-info.outputs.body) || '📖 描述: 无' }} | |
| 🔗 查看提交: https://github.com/${{ github.repository }}/commit/${{ github.sha }} | |
| 🔨 构建下载: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} |