From d8e557dc5bd3c824fc0ad9750f073b215c057f6f Mon Sep 17 00:00:00 2001 From: kong <135376906+3316891527@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:07:30 +0800 Subject: [PATCH 1/4] =?UTF-8?q?ci:=20=E5=B0=86=E6=9E=84=E5=BB=BA=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E6=98=BE=E7=A4=BA=E5=90=8D=E7=A7=B0=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=20Build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 96d56e3815..480291cdaa 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -1,4 +1,4 @@ -name: Android Build +name: Build on: workflow_dispatch: From fe31a30521ac5f3ab5133669e85516758a39f0c8 Mon Sep 17 00:00:00 2001 From: kong <135376906+3316891527@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:09:45 +0800 Subject: [PATCH 2/4] =?UTF-8?q?ci:=20=E6=B7=BB=E5=8A=A0=E6=94=B9=E5=90=8D?= =?UTF-8?q?=E5=90=8E=E7=9A=84=20build=20=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 419 ++++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..0c76e5f024 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,419 @@ +name: Build + +on: + workflow_dispatch: + inputs: + gradle_task: + description: Gradle task to run + required: true + default: ':app:verifyCiPreviewRelease' + type: choice + options: + - assembleDebug + - ':app:assembleNightly' + - ':app:assembleClone' + - ':app:verifyCiPreviewRelease' + - ':app:bundleRelease' + - publishOfflineResources + publish_release: + description: 发布独立发行 Release 并生成增量包 + required: true + default: false + type: boolean + complete_repository_migration: + description: +15 发布核对成功后清理已迁移的主仓库历史 Release,保留源码标签 + required: true + default: false + type: boolean + previous_tag: + description: 可选的基础 Release 标签;留空选择同一基础版本的最新正式包 + required: false + default: '' + type: string + resource_artifact_id: + description: 固定资源 ZIP 产物 ID,需属于所选提交;仅发布离线资源时填写 + required: false + default: '' + type: string + release_notes: + description: 本次发布说明 + required: false + default: '' + type: string + push: + branches: [main] + paths: + - .github/workflows/build.yml + - .gitmodules + - .npmrc + - app/** + - build.gradle.kts + - ci/distribution/** + - ci/script/download_android_dependencies.sh + - ci/script/prepare_android_dependencies.py + - cmake/** + - examples/** + - gradle/** + - gradle.properties + - gradlew + - gradlew.bat + - llm/** + - package.json + - package-lock.json + - npm-shrinkwrap.json + - pnpm-workspace.yaml + - quickjs/** + - settings.gradle.kts + - material-icons-subset/** + - tools/icons/** + - showerclient/** + - terminal/** + - terminal + - tools/native_ripgrep/** + - tools/packages_whitelist.txt + - tools/example_packages/sync_example_packages.py + - web-chat/** + +concurrency: + group: android-build-${{ github.event_name }}-${{ github.ref }}-${{ inputs.gradle_task }} + cancel-in-progress: true + +permissions: + contents: write + +env: + JAVA_VERSION: '21' + NODE_VERSION: '22' + ANDROID_NDK_VERSION: '25.1.8937393' + ANDROID_CMAKE_VERSION: '3.22.1' + ANDROID_API_LEVEL: '26' + RUST_VERSION: '1.88.0' + MANUAL_DEPS_DIR: manual-deps + +jobs: + build: + if: github.event_name != 'workflow_dispatch' || inputs.gradle_task != 'publishOfflineResources' + name: Build Android artifacts + runs-on: ubuntu-24.04 + timeout-minutes: 240 + + steps: + - name: Checkout source + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false + submodules: false + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.12' + + - name: Validate distribution publishing inputs + if: github.event_name == 'workflow_dispatch' && inputs.publish_release + env: + DISPATCH_GRADLE_TASK: ${{ inputs.gradle_task }} + shell: bash + run: | + if [ "$DISPATCH_GRADLE_TASK" != ':app:verifyCiPreviewRelease' ]; then + echo '发布独立发行版必须选择 :app:verifyCiPreviewRelease' >&2 + exit 1 + fi + - name: Validate migration completion inputs + if: github.event_name == 'workflow_dispatch' && inputs.complete_repository_migration + env: + PUBLISH_RELEASE: ${{ inputs.publish_release }} + DISPATCH_GRADLE_TASK: ${{ inputs.gradle_task }} + shell: bash + run: | + if [ "$PUBLISH_RELEASE" != 'true' ] || [ "$DISPATCH_GRADLE_TASK" != ':app:verifyCiPreviewRelease' ]; then + echo '完成迁移必须同时发布 +15 签名发行版' >&2 + exit 1 + fi + - name: Validate cross-repository release access + if: github.event_name == 'workflow_dispatch' && inputs.publish_release + env: + GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} + run: python3 ci/distribution/repositories/check_publish_access.py --mode release + - name: Check distribution patch publishing + run: python3 -m unittest discover -s ci/distribution/updates/tests -v + - name: Migrate and verify historical distribution releases + if: github.event_name == 'workflow_dispatch' && inputs.publish_release && inputs.complete_repository_migration + env: + GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} + run: python3 ci/distribution/repositories/migrate_releases.py --output .ci-repository-migration --apply + - name: Initialize terminal submodule + shell: bash + run: | + set -euo pipefail + git submodule sync -- terminal + git submodule update --init --recursive --depth 1 terminal + git submodule status -- terminal + + - name: Check Material icon references + run: python3 tools/icons/sync_material_icons.py --check + - name: Set up JDK + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} + cache: gradle + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Set up Android SDK + uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4 + with: + packages: '' + + - name: Install Android SDK, NDK and CMake + shell: bash + run: | + set -euo pipefail + sdkmanager --install \ + "platform-tools" \ + "platforms;android-34" \ + "platforms;android-36" \ + "build-tools;34.0.0" \ + "build-tools;35.0.0" \ + "ndk;$ANDROID_NDK_VERSION" \ + "cmake;$ANDROID_CMAKE_VERSION" + + - name: Restore Android CMake source cache + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: | + **/.cxx/operit_deps/*-src + key: android-cmake-sources-${{ runner.os }}-${{ runner.arch }}-arm64-v8a-ndk-${{ env.ANDROID_NDK_VERSION }}-cmake-${{ env.ANDROID_CMAKE_VERSION }}-${{ hashFiles('cmake/**', '**/CMakeLists.txt', '**/*.cmake', '**/build.gradle.kts', 'gradle.properties') }} + + - name: Restore Android dependency cache + id: cache-manual-deps + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: ${{ env.MANUAL_DEPS_DIR }} + key: android-full-deps-${{ runner.os }}-${{ hashFiles('ci/script/download_android_dependencies.sh', 'ci/script/prepare_android_dependencies.py') }} + + - name: Restore generated STT asset cache + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: app/build/generated/stt-model-assets + key: shared-vad-asset-v1-${{ runner.os }}-${{ hashFiles('app/config/stt-model-assets.properties') }} + + - name: Download Android dependencies + if: steps.cache-manual-deps.outputs.cache-hit != 'true' + run: bash ci/script/download_android_dependencies.sh full "$MANUAL_DEPS_DIR" + + - name: Prepare Android dependencies + run: | + python3 ci/script/prepare_android_dependencies.py \ + --profile full \ + --archives "$MANUAL_DEPS_DIR" \ + --repository "$GITHUB_WORKSPACE" + + - name: Restore native ripgrep cache + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + tools/native_ripgrep/target + key: native-ripgrep-arm64-${{ runner.os }}-${{ runner.arch }}-ndk-${{ env.ANDROID_NDK_VERSION }}-api-${{ env.ANDROID_API_LEVEL }}-rust-${{ env.RUST_VERSION }}-${{ hashFiles('tools/native_ripgrep/Cargo.toml', 'tools/native_ripgrep/Cargo.lock', 'tools/native_ripgrep/**/*.rs', 'tools/native_ripgrep/build_native_ripgrep.ps1') }} + + - name: Build native ripgrep + shell: bash + run: | + set -euo pipefail + export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$ANDROID_NDK_VERSION" + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android${ANDROID_API_LEVEL}-clang" + rustup toolchain install "$RUST_VERSION" --profile minimal + rustup target add --toolchain "$RUST_VERSION" aarch64-linux-android + cargo "+$RUST_VERSION" build \ + --manifest-path tools/native_ripgrep/Cargo.toml \ + --release \ + --target aarch64-linux-android \ + --locked + install -Dm755 \ + tools/native_ripgrep/target/aarch64-linux-android/release/liboperit_ripgrep.so \ + app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so + test -s app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so + + - name: Install JavaScript dependencies + shell: bash + run: | + set -euo pipefail + npm install -g pnpm@10.34.5 + npm ci --no-audit --no-fund + npm --prefix web-chat ci --no-audit --no-fund + + - name: Build WebChat assets + run: | + npm run build:webchat + + - name: Build bundled ToolPkg packages + run: | + npm run build:examples:github + python3 tools/example_packages/sync_example_packages.py --no-hot-reload + + - name: Configure Android SDK + shell: bash + run: | + set -euo pipefail + echo "sdk.dir=$ANDROID_HOME" > local.properties + + - name: Prepare distribution signing + if: github.event_name == 'workflow_dispatch' && (inputs.gradle_task == ':app:verifyCiPreviewRelease' || inputs.gradle_task == ':app:bundleRelease') + env: + OPERIT_DISTRIBUTION_KEYSTORE_BASE64: ${{ secrets.OPERIT_DISTRIBUTION_KEYSTORE_BASE64 }} + run: python3 ci/distribution/prepare_signing.py + + - name: Run Gradle build + env: + DISPATCH_GRADLE_TASK: ${{ inputs.gradle_task }} + EVENT_NAME: ${{ github.event_name }} + OPERIT_DISTRIBUTION_STORE_PASSWORD: ${{ secrets.OPERIT_DISTRIBUTION_STORE_PASSWORD }} + shell: bash + run: | + set -euo pipefail + case "$EVENT_NAME" in + push) gradle_task=assembleDebug ;; + workflow_dispatch) gradle_task="$DISPATCH_GRADLE_TASK" ;; + *) echo "Unsupported workflow event: $EVENT_NAME" >&2; exit 2 ;; + esac + chmod +x ./gradlew + ./gradlew "$gradle_task" --stacktrace --no-daemon + + - name: Verify external resource packaging and report APK size + if: github.event_name == 'push' || inputs.gradle_task != ':app:bundleRelease' + run: python3 ci/distribution/resources/verify_packaged_resources.py + + - name: Upload Android reports + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: operit-android-reports-${{ github.run_number }} + path: | + app/build/reports/** + build/reports/** + if-no-files-found: warn + retention-days: 14 + + - name: Upload successful Android outputs + if: success() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: operit-android-${{ github.run_number }} + path: | + app/build/outputs/apk/**/*.apk + if-no-files-found: error + retention-days: 14 + + - name: Publish distribution release and patch + if: github.event_name == 'workflow_dispatch' && inputs.publish_release + env: + GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} + PREVIOUS_TAG: ${{ inputs.previous_tag }} + RELEASE_NOTES: ${{ inputs.release_notes }} + shell: bash + run: | + set -euo pipefail + + python3 - <<'PY' + from pathlib import Path + + script = Path("ci/distribution/updates/publish_release.py") + source = script.read_text(encoding="utf-8") + + # 仅发布到统一发行仓库,避免无加号版本再次向源码仓库发布。 + # 移除源码标签创建,并避免发行摘要引用未创建的源码标签。 + replacements = { + " destinations = release_repositories(version)\n": + " destinations = (UPDATE_REPOSITORY,)\n", + " ensure_source_tag(tag, args.commit)\n": + "", + "**source_info(args.commit, tag)": + '**{key: value for key, value in source_info(args.commit, tag).items() if key != "sourceTag"}', + } + + # 脚本结构变化时明确报错,防止规则失效后继续创建源码标签。 + for old, new in replacements.items(): + count = source.count(old) + if count != 1: + raise RuntimeError( + f"发布脚本结构已变化,预期匹配一次,实际匹配 {count} 次:{old!r}" + ) + source = source.replace(old, new, 1) + + compile(source, str(script), "exec") + script.write_text(source, encoding="utf-8") + print("本次发布仅写入发行仓库,不创建源码仓库版本标签") + PY + + python3 ci/distribution/updates/publish_release.py \ + --apk app/build/outputs/apk/release/app-kong.apk \ + --build-tools "$ANDROID_HOME/build-tools/35.0.0" \ + --repository "$GITHUB_REPOSITORY" \ + --commit "$GITHUB_SHA" \ + --previous-tag "$PREVIOUS_TAG" \ + --notes "$RELEASE_NOTES" + + - name: Complete verified repository migration + if: github.event_name == 'workflow_dispatch' && inputs.publish_release && inputs.complete_repository_migration + env: + GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} + run: | + python3 ci/distribution/repositories/cleanup_main_releases.py \ + --receipts .ci-repository-migration/cleanup-receipts.json \ + --release-plan .ci-release/release-plan.json \ + --commit "$GITHUB_SHA" \ + --apply + - name: Upload distribution release and migration reports + if: always() && github.event_name == 'workflow_dispatch' && inputs.publish_release + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: operit-distribution-reports-${{ github.run_number }} + path: | + .ci-release/release-plan.json + .ci-release/distribution-release.json + .ci-release/cleanup-report.json + .ci-repository-migration/source-releases.json + .ci-repository-migration/migration-plan.json + .ci-repository-migration/verified-receipts.json + .ci-repository-migration/cleanup-receipts.json + if-no-files-found: warn + retention-days: 30 + include-hidden-files: true + + resources: + if: github.event_name == 'workflow_dispatch' && inputs.gradle_task == 'publishOfflineResources' + name: Publish fixed offline resources + runs-on: ubuntu-24.04 + timeout-minutes: 45 + permissions: + contents: write + actions: read + concurrency: + group: publish-offline-resources-v1 + cancel-in-progress: false + steps: + - name: Checkout source + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.12' + - name: Validate cross-repository resource access + env: + GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} + run: python3 ci/distribution/repositories/check_publish_access.py --mode resources + - name: Prepare, verify and publish resources + env: + GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} + SOURCE_ARTIFACT_ID: ${{ inputs.resource_artifact_id }} + run: python3 ci/distribution/resources/publish_resources.py --source-artifact-id "${SOURCE_ARTIFACT_ID:-0}" From 6f5df364420fd2ce403bd429c411779c2057dacd Mon Sep 17 00:00:00 2001 From: kong <135376906+3316891527@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:10:09 +0800 Subject: [PATCH 3/4] =?UTF-8?q?ci:=20=E7=A7=BB=E9=99=A4=E5=B7=B2=E6=94=B9?= =?UTF-8?q?=E5=90=8D=E7=9A=84=20android-build=20=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android-build.yml | 419 ---------------------------- 1 file changed, 419 deletions(-) delete mode 100644 .github/workflows/android-build.yml diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml deleted file mode 100644 index 480291cdaa..0000000000 --- a/.github/workflows/android-build.yml +++ /dev/null @@ -1,419 +0,0 @@ -name: Build - -on: - workflow_dispatch: - inputs: - gradle_task: - description: Gradle task to run - required: true - default: ':app:verifyCiPreviewRelease' - type: choice - options: - - assembleDebug - - ':app:assembleNightly' - - ':app:assembleClone' - - ':app:verifyCiPreviewRelease' - - ':app:bundleRelease' - - publishOfflineResources - publish_release: - description: 发布独立发行 Release 并生成增量包 - required: true - default: false - type: boolean - complete_repository_migration: - description: +15 发布核对成功后清理已迁移的主仓库历史 Release,保留源码标签 - required: true - default: false - type: boolean - previous_tag: - description: 可选的基础 Release 标签;留空选择同一基础版本的最新正式包 - required: false - default: '' - type: string - resource_artifact_id: - description: 固定资源 ZIP 产物 ID,需属于所选提交;仅发布离线资源时填写 - required: false - default: '' - type: string - release_notes: - description: 本次发布说明 - required: false - default: '' - type: string - push: - branches: [main] - paths: - - .github/workflows/android-build.yml - - .gitmodules - - .npmrc - - app/** - - build.gradle.kts - - ci/distribution/** - - ci/script/download_android_dependencies.sh - - ci/script/prepare_android_dependencies.py - - cmake/** - - examples/** - - gradle/** - - gradle.properties - - gradlew - - gradlew.bat - - llm/** - - package.json - - package-lock.json - - npm-shrinkwrap.json - - pnpm-workspace.yaml - - quickjs/** - - settings.gradle.kts - - material-icons-subset/** - - tools/icons/** - - showerclient/** - - terminal/** - - terminal - - tools/native_ripgrep/** - - tools/packages_whitelist.txt - - tools/example_packages/sync_example_packages.py - - web-chat/** - -concurrency: - group: android-build-${{ github.event_name }}-${{ github.ref }}-${{ inputs.gradle_task }} - cancel-in-progress: true - -permissions: - contents: write - -env: - JAVA_VERSION: '21' - NODE_VERSION: '22' - ANDROID_NDK_VERSION: '25.1.8937393' - ANDROID_CMAKE_VERSION: '3.22.1' - ANDROID_API_LEVEL: '26' - RUST_VERSION: '1.88.0' - MANUAL_DEPS_DIR: manual-deps - -jobs: - build: - if: github.event_name != 'workflow_dispatch' || inputs.gradle_task != 'publishOfflineResources' - name: Build Android artifacts - runs-on: ubuntu-24.04 - timeout-minutes: 240 - - steps: - - name: Checkout source - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - with: - ref: ${{ github.sha }} - fetch-depth: 1 - persist-credentials: false - submodules: false - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: '3.12' - - - name: Validate distribution publishing inputs - if: github.event_name == 'workflow_dispatch' && inputs.publish_release - env: - DISPATCH_GRADLE_TASK: ${{ inputs.gradle_task }} - shell: bash - run: | - if [ "$DISPATCH_GRADLE_TASK" != ':app:verifyCiPreviewRelease' ]; then - echo '发布独立发行版必须选择 :app:verifyCiPreviewRelease' >&2 - exit 1 - fi - - name: Validate migration completion inputs - if: github.event_name == 'workflow_dispatch' && inputs.complete_repository_migration - env: - PUBLISH_RELEASE: ${{ inputs.publish_release }} - DISPATCH_GRADLE_TASK: ${{ inputs.gradle_task }} - shell: bash - run: | - if [ "$PUBLISH_RELEASE" != 'true' ] || [ "$DISPATCH_GRADLE_TASK" != ':app:verifyCiPreviewRelease' ]; then - echo '完成迁移必须同时发布 +15 签名发行版' >&2 - exit 1 - fi - - name: Validate cross-repository release access - if: github.event_name == 'workflow_dispatch' && inputs.publish_release - env: - GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} - run: python3 ci/distribution/repositories/check_publish_access.py --mode release - - name: Check distribution patch publishing - run: python3 -m unittest discover -s ci/distribution/updates/tests -v - - name: Migrate and verify historical distribution releases - if: github.event_name == 'workflow_dispatch' && inputs.publish_release && inputs.complete_repository_migration - env: - GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} - run: python3 ci/distribution/repositories/migrate_releases.py --output .ci-repository-migration --apply - - name: Initialize terminal submodule - shell: bash - run: | - set -euo pipefail - git submodule sync -- terminal - git submodule update --init --recursive --depth 1 terminal - git submodule status -- terminal - - - name: Check Material icon references - run: python3 tools/icons/sync_material_icons.py --check - - name: Set up JDK - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5 - with: - distribution: temurin - java-version: ${{ env.JAVA_VERSION }} - cache: gradle - - - name: Set up Node.js - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Set up Android SDK - uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4 - with: - packages: '' - - - name: Install Android SDK, NDK and CMake - shell: bash - run: | - set -euo pipefail - sdkmanager --install \ - "platform-tools" \ - "platforms;android-34" \ - "platforms;android-36" \ - "build-tools;34.0.0" \ - "build-tools;35.0.0" \ - "ndk;$ANDROID_NDK_VERSION" \ - "cmake;$ANDROID_CMAKE_VERSION" - - - name: Restore Android CMake source cache - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 - with: - path: | - **/.cxx/operit_deps/*-src - key: android-cmake-sources-${{ runner.os }}-${{ runner.arch }}-arm64-v8a-ndk-${{ env.ANDROID_NDK_VERSION }}-cmake-${{ env.ANDROID_CMAKE_VERSION }}-${{ hashFiles('cmake/**', '**/CMakeLists.txt', '**/*.cmake', '**/build.gradle.kts', 'gradle.properties') }} - - - name: Restore Android dependency cache - id: cache-manual-deps - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 - with: - path: ${{ env.MANUAL_DEPS_DIR }} - key: android-full-deps-${{ runner.os }}-${{ hashFiles('ci/script/download_android_dependencies.sh', 'ci/script/prepare_android_dependencies.py') }} - - - name: Restore generated STT asset cache - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 - with: - path: app/build/generated/stt-model-assets - key: shared-vad-asset-v1-${{ runner.os }}-${{ hashFiles('app/config/stt-model-assets.properties') }} - - - name: Download Android dependencies - if: steps.cache-manual-deps.outputs.cache-hit != 'true' - run: bash ci/script/download_android_dependencies.sh full "$MANUAL_DEPS_DIR" - - - name: Prepare Android dependencies - run: | - python3 ci/script/prepare_android_dependencies.py \ - --profile full \ - --archives "$MANUAL_DEPS_DIR" \ - --repository "$GITHUB_WORKSPACE" - - - name: Restore native ripgrep cache - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - tools/native_ripgrep/target - key: native-ripgrep-arm64-${{ runner.os }}-${{ runner.arch }}-ndk-${{ env.ANDROID_NDK_VERSION }}-api-${{ env.ANDROID_API_LEVEL }}-rust-${{ env.RUST_VERSION }}-${{ hashFiles('tools/native_ripgrep/Cargo.toml', 'tools/native_ripgrep/Cargo.lock', 'tools/native_ripgrep/**/*.rs', 'tools/native_ripgrep/build_native_ripgrep.ps1') }} - - - name: Build native ripgrep - shell: bash - run: | - set -euo pipefail - export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$ANDROID_NDK_VERSION" - export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android${ANDROID_API_LEVEL}-clang" - rustup toolchain install "$RUST_VERSION" --profile minimal - rustup target add --toolchain "$RUST_VERSION" aarch64-linux-android - cargo "+$RUST_VERSION" build \ - --manifest-path tools/native_ripgrep/Cargo.toml \ - --release \ - --target aarch64-linux-android \ - --locked - install -Dm755 \ - tools/native_ripgrep/target/aarch64-linux-android/release/liboperit_ripgrep.so \ - app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so - test -s app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so - - - name: Install JavaScript dependencies - shell: bash - run: | - set -euo pipefail - npm install -g pnpm@10.34.5 - npm ci --no-audit --no-fund - npm --prefix web-chat ci --no-audit --no-fund - - - name: Build WebChat assets - run: | - npm run build:webchat - - - name: Build bundled ToolPkg packages - run: | - npm run build:examples:github - python3 tools/example_packages/sync_example_packages.py --no-hot-reload - - - name: Configure Android SDK - shell: bash - run: | - set -euo pipefail - echo "sdk.dir=$ANDROID_HOME" > local.properties - - - name: Prepare distribution signing - if: github.event_name == 'workflow_dispatch' && (inputs.gradle_task == ':app:verifyCiPreviewRelease' || inputs.gradle_task == ':app:bundleRelease') - env: - OPERIT_DISTRIBUTION_KEYSTORE_BASE64: ${{ secrets.OPERIT_DISTRIBUTION_KEYSTORE_BASE64 }} - run: python3 ci/distribution/prepare_signing.py - - - name: Run Gradle build - env: - DISPATCH_GRADLE_TASK: ${{ inputs.gradle_task }} - EVENT_NAME: ${{ github.event_name }} - OPERIT_DISTRIBUTION_STORE_PASSWORD: ${{ secrets.OPERIT_DISTRIBUTION_STORE_PASSWORD }} - shell: bash - run: | - set -euo pipefail - case "$EVENT_NAME" in - push) gradle_task=assembleDebug ;; - workflow_dispatch) gradle_task="$DISPATCH_GRADLE_TASK" ;; - *) echo "Unsupported workflow event: $EVENT_NAME" >&2; exit 2 ;; - esac - chmod +x ./gradlew - ./gradlew "$gradle_task" --stacktrace --no-daemon - - - name: Verify external resource packaging and report APK size - if: github.event_name == 'push' || inputs.gradle_task != ':app:bundleRelease' - run: python3 ci/distribution/resources/verify_packaged_resources.py - - - name: Upload Android reports - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: operit-android-reports-${{ github.run_number }} - path: | - app/build/reports/** - build/reports/** - if-no-files-found: warn - retention-days: 14 - - - name: Upload successful Android outputs - if: success() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: operit-android-${{ github.run_number }} - path: | - app/build/outputs/apk/**/*.apk - if-no-files-found: error - retention-days: 14 - - - name: Publish distribution release and patch - if: github.event_name == 'workflow_dispatch' && inputs.publish_release - env: - GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} - PREVIOUS_TAG: ${{ inputs.previous_tag }} - RELEASE_NOTES: ${{ inputs.release_notes }} - shell: bash - run: | - set -euo pipefail - - python3 - <<'PY' - from pathlib import Path - - script = Path("ci/distribution/updates/publish_release.py") - source = script.read_text(encoding="utf-8") - - # 仅发布到统一发行仓库,避免无加号版本再次向源码仓库发布。 - # 移除源码标签创建,并避免发行摘要引用未创建的源码标签。 - replacements = { - " destinations = release_repositories(version)\n": - " destinations = (UPDATE_REPOSITORY,)\n", - " ensure_source_tag(tag, args.commit)\n": - "", - "**source_info(args.commit, tag)": - '**{key: value for key, value in source_info(args.commit, tag).items() if key != "sourceTag"}', - } - - # 脚本结构变化时明确报错,防止规则失效后继续创建源码标签。 - for old, new in replacements.items(): - count = source.count(old) - if count != 1: - raise RuntimeError( - f"发布脚本结构已变化,预期匹配一次,实际匹配 {count} 次:{old!r}" - ) - source = source.replace(old, new, 1) - - compile(source, str(script), "exec") - script.write_text(source, encoding="utf-8") - print("本次发布仅写入发行仓库,不创建源码仓库版本标签") - PY - - python3 ci/distribution/updates/publish_release.py \ - --apk app/build/outputs/apk/release/app-kong.apk \ - --build-tools "$ANDROID_HOME/build-tools/35.0.0" \ - --repository "$GITHUB_REPOSITORY" \ - --commit "$GITHUB_SHA" \ - --previous-tag "$PREVIOUS_TAG" \ - --notes "$RELEASE_NOTES" - - - name: Complete verified repository migration - if: github.event_name == 'workflow_dispatch' && inputs.publish_release && inputs.complete_repository_migration - env: - GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} - run: | - python3 ci/distribution/repositories/cleanup_main_releases.py \ - --receipts .ci-repository-migration/cleanup-receipts.json \ - --release-plan .ci-release/release-plan.json \ - --commit "$GITHUB_SHA" \ - --apply - - name: Upload distribution release and migration reports - if: always() && github.event_name == 'workflow_dispatch' && inputs.publish_release - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: operit-distribution-reports-${{ github.run_number }} - path: | - .ci-release/release-plan.json - .ci-release/distribution-release.json - .ci-release/cleanup-report.json - .ci-repository-migration/source-releases.json - .ci-repository-migration/migration-plan.json - .ci-repository-migration/verified-receipts.json - .ci-repository-migration/cleanup-receipts.json - if-no-files-found: warn - retention-days: 30 - include-hidden-files: true - - resources: - if: github.event_name == 'workflow_dispatch' && inputs.gradle_task == 'publishOfflineResources' - name: Publish fixed offline resources - runs-on: ubuntu-24.04 - timeout-minutes: 45 - permissions: - contents: write - actions: read - concurrency: - group: publish-offline-resources-v1 - cancel-in-progress: false - steps: - - name: Checkout source - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - with: - persist-credentials: false - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: '3.12' - - name: Validate cross-repository resource access - env: - GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} - run: python3 ci/distribution/repositories/check_publish_access.py --mode resources - - name: Prepare, verify and publish resources - env: - GH_TOKEN: ${{ secrets.OPERIT_DISTRIBUTION_TOKEN }} - SOURCE_ARTIFACT_ID: ${{ inputs.resource_artifact_id }} - run: python3 ci/distribution/resources/publish_resources.py --source-artifact-id "${SOURCE_ARTIFACT_ID:-0}" From 09959158e1091b32d6dc8d6a3376e372d66d6881 Mon Sep 17 00:00:00 2001 From: kong <135376906+3316891527@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:10:58 +0000 Subject: [PATCH 4/4] =?UTF-8?q?ci:=20=E5=90=8C=E6=AD=A5=20Build=20?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E3=80=81=E6=A3=80=E6=9F=A5=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E4=B8=8E=E5=8F=91=E5=B8=83=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ci/distribution/updates/README.md | 4 ++-- ci/script/pr_check.py | 2 +- ci/test/test_pr_check.py | 2 +- docs/TODO/chore/distribution_integration/index.md | 2 +- docs/TODO/ci/workflow-publishing-sync/index.md | 8 ++++++++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ci/distribution/updates/README.md b/ci/distribution/updates/README.md index a0ab623d9a..0b0a55d1a5 100644 --- a/ci/distribution/updates/README.md +++ b/ci/distribution/updates/README.md @@ -12,13 +12,13 @@ ## 发布方法 -源码仓库的 Android Build 工作流选择 `main`、任务 `:app:verifyCiPreviewRelease`,勾选 `publish_release`,填写非空 Markdown 更新说明和可选 `previous_tag`。本次使用已发布的 `v1.12.2+17` 为基础;留空时选择补丁仓库中同一基础版本且低于目标的最新已公开 APK。普通构建保持 `publish_release=false`。 +源码仓库的 [Build 工作流](../../../.github/workflows/build.yml)选择 `main`、任务 `:app:verifyCiPreviewRelease`,勾选 `publish_release`,填写非空 Markdown 更新说明和可选 `previous_tag`。本次使用已发布的 `v1.12.2+17` 为基础;留空时选择补丁仓库中同一基础版本且低于目标的最新已公开 APK。普通构建保持 `publish_release=false`。 工作流需要源码仓库的 Actions secret `OPERIT_DISTRIBUTION_TOKEN`,授予源码、补丁、资源仓库的 Contents 写入权限及源码仓库 Actions 读取权限。内置 `github.token` 不能向另外两个仓库发布。权限预检在签名构建前执行;缺少配置时明确失败。既有发行仓库和专用 secret 继续使用;本次保持 `complete_repository_migration=false`,不重复执行历史迁移清理。 发布脚本读取 APK 的真实版本,检查 Kong 包名、固定证书和递增内部版本号。所有应用版本均只发往补丁仓库,先完成草稿上传及核验再公开;源码仓库不发布应用副本。已有 Release 不覆盖。 -当前 Android Build 发行步骤仅向统一更新仓库发布,不创建源码仓库版本标签,发行摘要也不再声明 sourceTag。源码提交和源码归档地址仍记录在正文与摘要中。发行仓库自己的 tag 继续承载 Release,已有标签不删除或移动。直接调用未变换的 publish_release.py 仍保留历史源码标签逻辑,正式发行应通过当前工作流。 +当前 Build 发行步骤仅向统一更新仓库发布,不创建源码仓库版本标签,发行摘要也不再声明 sourceTag。源码提交和源码归档地址仍记录在正文与摘要中。发行仓库自己的 tag 继续承载 Release,已有标签不删除或移动。直接调用未变换的 publish_release.py 仍保留历史源码标签逻辑,正式发行应通过当前工作流。 ## 不公开版本的准备 diff --git a/ci/script/pr_check.py b/ci/script/pr_check.py index a38449fa02..d8398dca17 100644 --- a/ci/script/pr_check.py +++ b/ci/script/pr_check.py @@ -39,7 +39,7 @@ "tools/example_packages/sync_example_packages.py", ) ANDROID_FULL_PATTERNS = ( - ".github/workflows/android-build.yml", + ".github/workflows/build.yml", ".github/workflows/android-tests.yml", ".github/workflows/pr-check.yml", ".gitmodules", diff --git a/ci/test/test_pr_check.py b/ci/test/test_pr_check.py index e13f5bd951..957cf93950 100644 --- a/ci/test/test_pr_check.py +++ b/ci/test/test_pr_check.py @@ -57,7 +57,7 @@ def test_native_change_uses_full_lane(self) -> None: def test_android_workflow_changes_use_full_lane(self) -> None: for path in ( - ".github/workflows/android-build.yml", + ".github/workflows/build.yml", ".github/workflows/android-tests.yml", ".github/workflows/pr-check.yml", ): diff --git a/docs/TODO/chore/distribution_integration/index.md b/docs/TODO/chore/distribution_integration/index.md index 68d3412619..7dd960b08e 100644 --- a/docs/TODO/chore/distribution_integration/index.md +++ b/docs/TODO/chore/distribution_integration/index.md @@ -32,7 +32,7 @@ status: implemented-user-verified 来源数量、顺序与提交可追溯性、资源 XML、重复资源、孤立翻译及 diff 空白检查已通过。对手动协调的 Kotlin 文件及原生围栏插件做了辅助语法扫描;静态检查不能代替 Android 编译和运行验证。 -新增数据库联合迁移和 CSV 兼容回归,尚未运行。本轮不在本地执行 Android 构建或 JVM 测试,由本分支 [Android Build](https://github.com/3316891527/Operit/actions/workflows/android-build.yml?query=branch%3Afeat%2Fintegrated-distribution) 和 [Android Tests](https://github.com/3316891527/Operit/actions/workflows/android-tests.yml?query=branch%3Afeat%2Fintegrated-distribution) 工作流执行,实际通过与 APK 产出以对应运行结果为准。 +新增数据库联合迁移和 CSV 兼容回归,尚未运行。本轮不在本地执行 Android 构建或 JVM 测试,由本分支 [Android Build](https://github.com/3316891527/Operit/actions/workflows/build.yml?query=branch%3Afeat%2Fintegrated-distribution) 和 [Android Tests](https://github.com/3316891527/Operit/actions/workflows/android-tests.yml?query=branch%3Afeat%2Fintegrated-distribution) 工作流执行,实际通过与 APK 产出以对应运行结果为准。 设备功能回归已按用户本次确认补录为通过。[聊天 UI 原有 216 项清单](../../feat/chat_ui_redesign/24-回归清单.md) 和本轮集成清单均保留,历史通过不等于本轮集成版本通过。 diff --git a/docs/TODO/ci/workflow-publishing-sync/index.md b/docs/TODO/ci/workflow-publishing-sync/index.md index ceef8ecf5a..7ed937d2d9 100644 --- a/docs/TODO/ci/workflow-publishing-sync/index.md +++ b/docs/TODO/ci/workflow-publishing-sync/index.md @@ -18,3 +18,11 @@ pr-check.yml 将 pull_request.branches 从 development 改为 dev,保留事件 交付时派发工作分支 Debug 构建,不开启发行;受理和执行结果分别记录,派发后不等待或轮询 CI。下载工具消失的问题按维护者要求暂不检查。 [DONE] 源码修改与静态验证完成,远端同步与工作流受理另存交付收据。 + +## 当前构建工作流入口 + +构建工作流文件已由 `.github/workflows/android-build.yml` 改名为 `.github/workflows/build.yml`,Actions 显示名称为 `Build`。使用文件名派发时改为 `gh workflow run build.yml`;构建任务、手动输入、签名、发行目的地和固定资源发布逻辑沿用原配置。 + +工作流自身的 push 路径过滤、PR 变更范围识别及原有路径回归数据同步采用新文件名。历史说明中的旧文件名记录当时入口,当前操作统一使用 `build.yml`。 + +本轮仅进行 YAML 结构与前后等价核对、修改 Python 的 AST 解析及差异空白检查,没有执行本地编译、构建或测试。编号从实际创建的新工作流运行记录确认,文件改名本身不作为已重置的验证结果。