diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml new file mode 100644 index 000000000..b9bfa3deb --- /dev/null +++ b/.github/workflows/android-release.yml @@ -0,0 +1,151 @@ +name: Build Release APK + +on: + push: + tags: + - "android-v*" + workflow_dispatch: + inputs: + maafw_tag: + description: "MaaFramework release tag" + required: false + default: "latest" + +jobs: + build: + runs-on: macos-latest + timeout-minutes: 90 + env: + PI_PROFILE: ${{ github.workspace }}/Android/profile.yaml + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: "25" + distribution: temurin + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install NDK and CMake + run: sdkmanager --install "ndk;29.0.13113456" "cmake;3.22.1" + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('Android/MaaFwApp/**/*.gradle.kts', 'Android/MaaFwApp/**/gradle-wrapper.properties', 'Android/MaaFwApp/**/libs.versions.toml') }} + restore-keys: | + gradle-${{ runner.os }}- + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Cache MaaFramework zips + uses: actions/cache@v4 + with: + path: Android/MaaFwApp/.maa-cache + key: maa-fw-${{ runner.os }}-${{ hashFiles('Android/MaaFwApp/scripts/setup_maa_framework.py') }} + + - name: Cache agent core + uses: actions/cache@v4 + with: + path: Android/MaaFwApp/.maafw + key: maa-agent-core-${{ runner.os }}-${{ hashFiles('Android/MaaFwApp/scripts/build_agent_bundle.py') }} + + - name: Download and deploy MaaFramework + working-directory: Android/MaaFwApp + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_ARG="" + MAAFW_TAG="${{ github.event.inputs.maafw_tag }}" + if [ -n "$MAAFW_TAG" ] && [ "$MAAFW_TAG" != "latest" ]; then + TAG_ARG="--tag $MAAFW_TAG" + fi + python scripts/setup_maa_framework.py $TAG_ARG + + - name: Build agent runtime + working-directory: Android/MaaFwApp + run: | + python scripts/build_agent_bundle.py \ + --out "${GITHUB_WORKSPACE}/Android/agent-dist" \ + --abi arm64-v8a \ + --requirements "${GITHUB_WORKSPACE}/requirements.txt" \ + --exclude pillow --require pillow==11.0.0 \ + --extra-index-url https://chaquo.com/pypi-13.1/ + + - name: Decode keystore + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + run: echo "$KEYSTORE_BASE64" | base64 -d > ${{ github.workspace }}/release.jks + + - name: Write local.properties + working-directory: Android/MaaFwApp + run: | + echo "sdk.dir=${ANDROID_HOME}" > local.properties + echo "pi.profile=${PI_PROFILE}" >> local.properties + + - name: Build Release APK + working-directory: Android/MaaFwApp + env: + KEYSTORE_PATH: ${{ github.workspace }}/release.jks + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: | + chmod +x ./gradlew + ./gradlew clean assembleRelease + + - name: Rename APK + run: | + cd Android/MaaFwApp/app/build/outputs/apk/release + TAG="${GITHUB_REF_NAME:-dev}" + for f in *.apk; do + mv "$f" "M9A-Meow-${TAG}-arm64-v8a.apk" + done + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: release-apk-arm64-v8a + path: Android/MaaFwApp/app/build/outputs/apk/release/*.apk + + - name: Upload R8 mapping + uses: actions/upload-artifact@v4 + with: + name: release-mapping-arm64-v8a + path: Android/MaaFwApp/app/build/outputs/mapping/release/mapping.txt + if-no-files-found: error + + release: + if: startsWith(github.ref, 'refs/tags/android-v') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all APK artifacts + uses: actions/download-artifact@v4 + with: + pattern: release-apk-* + merge-multiple: true + path: apks + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + draft: false + prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }} + files: apks/*.apk + generate_release_notes: true diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 000000000..445862f11 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,191 @@ +name: Build Dev APK + +on: + push: + branches: [main, master] + paths: + - "Android/**" + - "agent/**" + - "requirements.txt" + - "interface.json" + - ".github/workflows/android.yml" + pull_request: + paths: + - "Android/**" + - "agent/**" + - "requirements.txt" + - "interface.json" + - ".github/workflows/android.yml" + workflow_dispatch: + inputs: + assemble: + description: "debug 或 release" + type: choice + options: + - debug + - release + default: debug + maafw_tag: + description: "MaaFramework release tag" + required: false + default: "latest" + +concurrency: + group: android-dev-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + runs-on: macos-latest + timeout-minutes: 60 + env: + PI_PROFILE: ${{ github.workspace }}/Android/profile.yaml + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: "25" + distribution: temurin + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install NDK and CMake + run: sdkmanager --install "ndk;29.0.13113456" "cmake;3.22.1" + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('Android/MaaFwApp/**/*.gradle.kts', 'Android/MaaFwApp/**/gradle-wrapper.properties', 'Android/MaaFwApp/**/libs.versions.toml') }} + restore-keys: | + gradle-${{ runner.os }}- + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Cache MaaFramework zips + uses: actions/cache@v4 + with: + path: Android/MaaFwApp/.maa-cache + key: maa-fw-${{ runner.os }}-${{ hashFiles('Android/MaaFwApp/scripts/setup_maa_framework.py') }} + + - name: Cache agent core + uses: actions/cache@v4 + with: + path: Android/MaaFwApp/.maafw + key: maa-agent-core-${{ runner.os }}-${{ hashFiles('Android/MaaFwApp/scripts/build_agent_bundle.py') }} + + - name: Cache agent bundle + uses: actions/cache@v4 + with: + path: Android/agent-dist + key: m9a-agent-${{ runner.os }}-${{ hashFiles('requirements.txt', 'Android/MaaFwApp/scripts/build_agent_bundle.py') }} + + - name: Download and deploy MaaFramework + working-directory: Android/MaaFwApp + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_ARG="" + MAAFW_TAG="${{ github.event.inputs.maafw_tag }}" + if [ -n "$MAAFW_TAG" ] && [ "$MAAFW_TAG" != "latest" ]; then + TAG_ARG="--tag $MAAFW_TAG" + fi + python scripts/setup_maa_framework.py --abi arm64-v8a $TAG_ARG + + - name: Build agent runtime + working-directory: Android/MaaFwApp + run: | + if [ -x "${GITHUB_WORKSPACE}/Android/agent-dist/arm64-v8a/bundle/bin/python3" ]; then + echo "agent bundle cache hit" + exit 0 + fi + python scripts/build_agent_bundle.py \ + --out "${GITHUB_WORKSPACE}/Android/agent-dist" \ + --abi arm64-v8a \ + --requirements "${GITHUB_WORKSPACE}/requirements.txt" \ + --exclude pillow --require pillow==11.0.0 \ + --extra-index-url https://chaquo.com/pypi-13.1/ + + - name: Write local.properties + working-directory: Android/MaaFwApp + run: | + { + echo "sdk.dir=${ANDROID_HOME}" + echo "pi.profile=${PI_PROFILE}" + if [ "${{ github.event.inputs.assemble || 'debug' }}" != "release" ]; then + echo "build.debugAbi=arm64-v8a" + fi + } > local.properties + + - name: Decode keystore + if: ${{ github.event.inputs.assemble == 'release' }} + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + run: echo "$KEYSTORE_BASE64" | base64 -d > ${{ github.workspace }}/release.jks + + - name: Build Debug APK + if: ${{ github.event.inputs.assemble != 'release' }} + working-directory: Android/MaaFwApp + run: | + chmod +x ./gradlew + ./gradlew clean assembleDebug + + - name: Build Release APK + if: ${{ github.event.inputs.assemble == 'release' }} + working-directory: Android/MaaFwApp + env: + KEYSTORE_PATH: ${{ github.workspace }}/release.jks + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: | + chmod +x ./gradlew + ./gradlew clean assembleRelease + + - name: Rename Release APK + if: ${{ github.event.inputs.assemble == 'release' }} + run: | + cd Android/MaaFwApp/app/build/outputs/apk/release + TAG="${GITHUB_REF_NAME:-dev}" + for f in *.apk; do + mv "$f" "M9A-Meow-${TAG}-arm64-v8a.apk" + done + + - name: Upload Debug APK + if: ${{ github.event.inputs.assemble != 'release' }} + uses: actions/upload-artifact@v4 + with: + name: debug-apk + path: Android/MaaFwApp/app/build/outputs/apk/debug/*.apk + if-no-files-found: error + + - name: Upload Release APK + if: ${{ github.event.inputs.assemble == 'release' }} + uses: actions/upload-artifact@v4 + with: + name: release-apk-arm64-v8a + path: Android/MaaFwApp/app/build/outputs/apk/release/*.apk + if-no-files-found: error + + - name: Upload R8 mapping + if: ${{ github.event.inputs.assemble == 'release' }} + uses: actions/upload-artifact@v4 + with: + name: release-mapping-arm64-v8a + path: Android/MaaFwApp/app/build/outputs/mapping/release/mapping.txt + if-no-files-found: error diff --git a/.gitignore b/.gitignore index e2d3ac760..70947b0eb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,8 @@ maafw.log # node 相关 .pnpm-store/ +.idea +# Android 客户端:agent 产物在子模块外,必须在这里忽略 +Android/agent-dist/ +*.jks +*.jks.b64 diff --git a/.gitmodules b/.gitmodules index b667a0ac7..d9c90d09b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "MaaCommonAssets"] path = MaaCommonAssets url = https://github.com/MaaXYZ/MaaCommonAssets.git +[submodule "Android/MaaFwApp"] + path = Android/MaaFwApp + url = https://github.com/Aliothmoon/MaaFwApp.git + branch = main diff --git a/Android/MaaFwApp b/Android/MaaFwApp new file mode 160000 index 000000000..e4d5a2c93 --- /dev/null +++ b/Android/MaaFwApp @@ -0,0 +1 @@ +Subproject commit e4d5a2c93fc29f1b3d7f89c7d2609f948c48353d diff --git a/Android/README.md b/Android/README.md new file mode 100644 index 000000000..3a0590246 --- /dev/null +++ b/Android/README.md @@ -0,0 +1,47 @@ +# Android 客户端 + +外壳是 [MaaFwApp](https://github.com/Aliothmoon/MaaFwApp) 子模块,资源和 agent 用本仓库的树,改完直接出包。 + +## 首次 + +```bash +git submodule update --init --recursive Android/MaaFwApp +python Android/MaaFwApp/scripts/setup_maa_framework.py --abi arm64-v8a +python Android/MaaFwApp/scripts/build_agent_bundle.py \ + --out Android/agent-dist \ + --requirements requirements.txt \ + --exclude pillow --require pillow==11.0.0 \ + --extra-index-url https://chaquo.com/pypi-13.1/ +``` + +在 `Android/MaaFwApp/local.properties` 里写(不进 git): + +```properties +sdk.dir= +pi.profile=../profile.yaml +build.debugAbi=arm64-v8a +``` + +## 出包 + +```bash +./Android/MaaFwApp/gradlew -p Android/MaaFwApp :app:installDebug +``` + +改 `interface.json`、`tasks/`、`agent/` 后重新 `installDebug` 即可,不必再指外部路径。换了 `requirements.txt` 再跑一遍 `build_agent_bundle.py`。 + +升外壳: + +```bash +git -C Android/MaaFwApp fetch +git -C Android/MaaFwApp checkout origin/main +git add Android/MaaFwApp +``` + +## CI + +debug 走 **Build Dev APK**(`macos-latest` + JDK 25 + NDK 29),正式包走 **Build Release APK**。 + +改 `Android/`、`agent/`、`requirements.txt` 或 `interface.json` 会打 arm64 debug APK。打 `android-v*` tag(或手动跑 Build Release APK)出签名包。 + +Release 需要仓库 Secrets:`KEYSTORE_BASE64`、`KEYSTORE_PASSWORD`、`KEY_ALIAS`、`KEY_PASSWORD`。手动跑时可以指定 MaaFramework 的 tag,默认 latest。 diff --git a/Android/logo.png b/Android/logo.png new file mode 100644 index 000000000..74307fa87 Binary files /dev/null and b/Android/logo.png differ diff --git a/Android/profile.yaml b/Android/profile.yaml new file mode 100644 index 000000000..b2c1d7398 --- /dev/null +++ b/Android/profile.yaml @@ -0,0 +1,46 @@ +# Android 打包配方。相对路径相对本文件。 +# assets 指向上一级,也就是本仓库根;改任务 / agent 不用再设环境变量。 + +assets: .. + +include: + - interface.json + - tasks/** + - resource/** + - data/** + - agent/** + - config/** + - logo.ico + - CONTACT + - LICENSE + +# images/ 不能整目录挖:银行等 option 的 icon 指在这里 +exclude: + - resource/announcement/*.md + - resource/announcement/images/CCMain.png + - resource/announcement/images/mojing_tjb.jpg + - resource/announcement/images/uttu.png + - resource/announcement/images/SOSMain.png + - resource/announcement/images/newbie-main-interface.png + +# 出运行时(在 android/MaaFwApp 下执行): +# python scripts/build_agent_bundle.py --out ../agent-dist \ +# --requirements ../../requirements.txt \ +# --exclude pillow --require pillow==11.0.0 --extra-index-url https://chaquo.com/pypi-13.1/ +agent: + sourceDir: agent-dist + abi: [arm64-v8a] + runtimes: + - location: bundle + executable: bin/python3 + args: [-u, agent/main.py] + env: + PYTHONHOME: "{bundle}/prefix" + PYTHONPATH: "{bundle}/site-packages/pure.zip:{bundle}/site-packages" + LD_LIBRARY_PATH: "{bundle}/prefix/lib:{bundle}/site-packages/chaquopy/lib:{nativeLibs}" + MAAFW_BINARY_PATH: "{nativeLibs}" + +app: + id: m9a + label: M9A + icon: logo.png diff --git a/docs/zh_cn/develop/structure.md b/docs/zh_cn/develop/structure.md index b2b1d396f..8f5276512 100644 --- a/docs/zh_cn/develop/structure.md +++ b/docs/zh_cn/develop/structure.md @@ -19,6 +19,10 @@ icon: hugeicons:structure-01 - launch.json # 调试配置 - settings.json # 项目设置 - tasks.json # 任务配置 +- Android/ # Android 客户端(MaaFwApp 子模块 + 打包配方) + - MaaFwApp/ # 外壳子模块 + - profile.yaml # 打包配方,资源指向上一级本仓库 + - README.md # 出包说明 - agent/ # Agent模块代码 - custom/ # 自定义识别和任务 - utils/ # 工具函数