Skip to content

Android build, capture, and release #114

Android build, capture, and release

Android build, capture, and release #114

Workflow file for this run

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"