Repository navigation
Android build, capture, and release #114
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: Android build, capture, and release | |
| on: | |
| push: | |
| branches: [main] | |
| # Marketing-only updates publish independently through pages.yml. | |
| paths-ignore: | |
| - '.github/pages/**' | |
| - '.github/workflows/pages.yml' | |
| - 'docs/**' | |
| - 'README.md' | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| archivebox_version: | |
| description: Released ArchiveBox server version to test (for example 0.9.50) | |
| required: true | |
| type: string | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: android-${{ github.event_name == 'pull_request' && github.ref || 'main-release' }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| BACKEND_REVISION: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', inputs.archivebox_version) || 'dev' }} | |
| jobs: | |
| version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| name: ${{ steps.version.outputs.version }} | |
| code: ${{ steps.version.outputs.code }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| - name: Validate released server version | |
| if: github.event_name == 'workflow_dispatch' | |
| env: | |
| ARCHIVEBOX_VERSION: ${{ inputs.archivebox_version }} | |
| run: node -e 'if (!/^\d+\.\d+\.\d+(?:rc\d+)?$/.test(process.env.ARCHIVEBOX_VERSION)) process.exit(1)' | |
| - run: node scripts/test-release-version.mjs | |
| - id: version | |
| run: node scripts/release-version.mjs | |
| verify: | |
| needs: version | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: 21 | |
| - uses: android-actions/setup-android@v4.0.4 | |
| with: | |
| packages: 'platform-tools build-tools;36.0.0 platforms;android-37.0' | |
| log-accepted-android-sdk-licenses: false | |
| - uses: gradle/actions/setup-gradle@v4 | |
| - name: Compile, lint, and test | |
| run: ./gradlew --no-daemon lintDebug testDebugUnitTest assembleDebug assembleDebugAndroidTest -PversionName=${{ needs.version.outputs.name }} -PversionCode=${{ needs.version.outputs.code }} | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-test-apks | |
| path: | | |
| app/build/outputs/apk/debug/*.apk | |
| app/build/outputs/apk/androidTest/debug/*.apk | |
| if-no-files-found: error | |
| - uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: android-verification-reports | |
| path: | | |
| app/build/reports/ | |
| app/build/test-results/ | |
| capture: | |
| needs: verify | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/checkout@v5 | |
| with: | |
| repository: ArchiveBox/ArchiveBox | |
| ref: ${{ env.BACKEND_REVISION }} | |
| path: backend | |
| persist-credentials: false | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: '3.13' | |
| - name: Install the real ArchiveBox server | |
| run: | | |
| echo "BACKEND_REVISION=$(git -C backend rev-parse HEAD)" >> "$GITHUB_ENV" | |
| uv sync --frozen --project backend | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.33.2 | |
| - name: Archive real pages and start the server | |
| run: bash scripts/start-screenshot-server.sh "$GITHUB_WORKSPACE/backend" "$RUNNER_TEMP/archivebox-capture" | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| name: android-test-apks | |
| path: artifacts/apks | |
| # Older runner-provided avdmanager versions misread API 37.0 as android-0. | |
| - uses: android-actions/setup-android@v4.0.4 | |
| with: | |
| packages: 'platform-tools' | |
| log-accepted-android-sdk-licenses: false | |
| - name: Use current tools at the emulator action's fixed SDK path | |
| shell: bash | |
| run: | | |
| sdk_tools=$(dirname "$(dirname "$(command -v sdkmanager)")") | |
| grep -Eq '^Pkg.Revision=22\.' "$sdk_tools/source.properties" | |
| if [[ "$sdk_tools" != "$ANDROID_HOME/cmdline-tools/latest" ]]; then | |
| if [[ -e "$ANDROID_HOME/cmdline-tools/latest" ]]; then | |
| mv "$ANDROID_HOME/cmdline-tools/latest" "$RUNNER_TEMP/android-cmdline-tools-preinstalled" | |
| fi | |
| mv "$sdk_tools" "$ANDROID_HOME/cmdline-tools/latest" | |
| fi | |
| echo "$ANDROID_HOME/cmdline-tools/latest/bin" >> "$GITHUB_PATH" | |
| - name: Enable hardware virtualization | |
| run: | | |
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger --name-match=kvm | |
| - uses: reactivecircus/android-emulator-runner@v2 | |
| with: | |
| api-level: '37.0' | |
| # Match the current 16 KB image used for local device acceptance. | |
| target: google_apis_ps16k | |
| arch: x86_64 | |
| profile: pixel_7 | |
| # The API 37 image otherwise kills foreground processes during initial boot. | |
| ram-size: 4096M | |
| disk-size: 4096M | |
| # Correct API metadata enables the DMA readback required by Android 37's mapper. | |
| emulator-options: -no-window -gpu swiftshader -no-snapshot -noaudio -no-boot-anim -no-metrics | |
| disable-animations: true | |
| pre-emulator-launch-script: grep -Fx 'target=android-37.0' "$HOME/.android/avd/test.ini" | |
| script: bash scripts/capture-screenshots.sh "$RUNNER_TEMP/archivebox-capture" artifacts/screenshots artifacts/apks | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-screenshots | |
| path: artifacts/screenshots/ | |
| if-no-files-found: error | |
| retention-days: 90 | |
| - name: Export public Chrome capture diagnostics | |
| if: failure() | |
| run: uv run --no-project python scripts/export-capture-diagnostics.py "$RUNNER_TEMP/archivebox-capture" artifacts/capture-diagnostics | |
| - uses: actions/upload-artifact@v4 | |
| if: failure() | |
| with: | |
| name: capture-diagnostics | |
| path: | | |
| artifacts/capture-diagnostics/ | |
| artifacts/instrumentation.log | |
| artifacts/logcat.txt | |
| artifacts/failed-screenshots/ | |
| artifacts/emulator-diagnostics/ | |
| ${{ runner.temp }}/archivebox-capture/server.log | |
| site: | |
| needs: [capture, version] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| name: android-screenshots | |
| path: artifacts/screenshots | |
| - uses: astral-sh/setup-uv@v7 | |
| - name: Build website from this revision's verified captures | |
| env: | |
| RELEASE_VERSION: ${{ needs.version.outputs.name }} | |
| run: node scripts/build-site.mjs --screenshots-dir artifacts/screenshots --require-screenshots | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: site-screenshots | |
| path: artifacts/screenshots | |
| if-no-files-found: error | |
| retention-days: 90 | |
| release: | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| needs: [version, verify, capture, site] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/.release-signing.jks | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| - uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: 21 | |
| - uses: android-actions/setup-android@v4.0.4 | |
| with: | |
| packages: 'platform-tools build-tools;36.0.0 platforms;android-37.0' | |
| log-accepted-android-sdk-licenses: false | |
| - uses: gradle/actions/setup-gradle@v4 | |
| - name: Restore persistent signing identity | |
| shell: bash | |
| run: | | |
| for name in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do | |
| if [[ -z "${!name}" ]]; then | |
| echo "::error::Missing $name. Configure the permanent Android release signing identity; see docs/RELEASES.md." | |
| exit 1 | |
| fi | |
| done | |
| umask 077 | |
| printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$ANDROID_KEYSTORE_PATH" | |
| - name: Build signed APK and Play bundle | |
| run: ./gradlew --no-daemon assembleRelease bundleRelease -PversionName=${{ needs.version.outputs.name }} -PversionCode=${{ needs.version.outputs.code }} | |
| - name: Stage and verify signed artifacts | |
| shell: bash | |
| run: | | |
| mkdir -p artifacts/releases | |
| cp app/build/outputs/apk/release/app-release.apk artifacts/releases/ArchiveBox-Android.apk | |
| cp app/build/outputs/bundle/release/app-release.aab artifacts/releases/ArchiveBox-Android.aab | |
| apksigner=$(find "$ANDROID_HOME/build-tools" -name apksigner | sort -V | tail -1) | |
| "$apksigner" verify --verbose --print-certs artifacts/releases/ArchiveBox-Android.apk | |
| jarsigner -verify artifacts/releases/ArchiveBox-Android.aab | tee artifacts/bundle-verification.txt | |
| grep -q '^jar verified\.' artifacts/bundle-verification.txt | |
| cd artifacts/releases | |
| sha256sum ArchiveBox-Android.apk ArchiveBox-Android.aab > SHA256SUMS | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| name: android-screenshots | |
| path: artifacts/screenshots | |
| - name: Publish stable download names and capture provenance | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_TAG: ${{ needs.version.outputs.tag }} | |
| shell: bash | |
| run: | | |
| tar -czf artifacts/releases/ArchiveBox-Android-screenshots.tar.gz -C artifacts/screenshots . | |
| node scripts/publish-release-notes.mjs | |
| gh release upload "$RELEASE_TAG" artifacts/releases/* --clobber | |
| gh release edit "$RELEASE_TAG" --draft=false --latest --title "$RELEASE_TAG" --notes-file artifacts/release-notes.md | |
| - name: Remove private signing material | |
| if: always() | |
| run: rm -f "$ANDROID_KEYSTORE_PATH" |