diff --git a/.github/workflows/formulus-android.yml b/.github/workflows/formulus-android.yml index 919a9e606..5c020c844 100644 --- a/.github/workflows/formulus-android.yml +++ b/.github/workflows/formulus-android.yml @@ -75,7 +75,7 @@ jobs: retention-days: 7 build-android: - name: Build Formulus Android APK + name: Build Formulus Android runs-on: ubuntu-latest needs: build-formplayer-assets permissions: @@ -121,6 +121,16 @@ jobs: working-directory: formulus run: pnpm run vendor:notifee + - name: Apply FOSS Android patches (match F-Droid init) + if: github.event_name != 'pull_request' + working-directory: formulus + run: pnpm run patch:android-foss + + - name: Generate injection script (match F-Droid init) + if: github.event_name != 'pull_request' + working-directory: formulus + run: pnpm run generate + - name: Set up Java uses: actions/setup-java@v4 with: @@ -166,6 +176,11 @@ jobs: working-directory: formulus/android run: ./gradlew assembleRelease --no-daemon + - name: Build release AAB for Play Store (main/dev/release) + if: github.event_name != 'pull_request' + working-directory: formulus/android + run: ./gradlew bundleRelease --no-daemon + - name: Upload APK artifact uses: actions/upload-artifact@v6 with: @@ -173,11 +188,20 @@ jobs: path: | formulus/android/app/build/outputs/apk/**/**/*.apk + - name: Upload AAB artifact + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v6 + with: + name: formulus-android-aab-${{ github.event_name }}-${{ github.run_id }} + path: | + formulus/android/app/build/outputs/bundle/release/*.aab + - name: Upload APK to GitHub Release if: github.event_name == 'release' uses: softprops/action-gh-release@v2 with: files: | formulus/android/app/build/outputs/apk/**/**/*.apk + formulus/android/app/build/outputs/bundle/release/*.aab env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/formulus/android/ANDROID_RELEASE.md b/formulus/android/ANDROID_RELEASE.md new file mode 100644 index 000000000..f01fe14ea --- /dev/null +++ b/formulus/android/ANDROID_RELEASE.md @@ -0,0 +1,49 @@ +# Android release: Google Play and F-Droid + +Formulus ships from one FOSS codebase. Play Store and F-Droid use the same dependency patches and build steps; only packaging and version-code handling differ. + +## Shared release prep + +1. Bump `versionName` / `versionCode` in `app/build.gradle` (or run `pnpm run sync:version` from `formulus/`). +2. From `formulus/`: + ```sh + pnpm install --frozen-lockfile + pnpm run vendor:notifee + pnpm run patch:android-foss + pnpm run generate + ``` +3. Tag the release commit on the upstream repo, e.g. `v1.0.2` (F-Droid update checks use stable tags matching `v[\d.]+$`). + +## Google Play + +- **Artifact:** Android App Bundle (AAB) — `./gradlew bundleRelease` in `formulus/android/`. +- **CI:** GitHub Actions builds `bundleRelease` on `main`, `dev`, and GitHub Releases; uploads the AAB artifact and attaches it to releases. +- **versionCode:** Uses `defaultConfig.versionCode` in the AAB (single code per release). +- **Signing:** See [SIGNING_CONFIG.md](./SIGNING_CONFIG.md). CI uses repository secrets; local builds use `android/local.properties`. + +Optional split APKs for sideloading or GitHub Release assets: + +```sh +./gradlew assembleRelease +``` + +Split APKs use per-ABI version codes: `baseVersionCode + abiOffset` (2–5 when base is 2). + +## F-Droid + +- **Artifact:** One APK per ABI, built from source by F-Droid. +- **Metadata:** `fdroiddata/metadata/org.opendataensemble.formulus.yml` — four builds with `abiFilters` and explicit `versionCode` per ABI. +- **Gradle:** Pass `-PabiFilters=`; F-Droid prebuild strips custom APK naming and sets `versionCode = $$VERCODE$$`. +- **Init steps:** Same as shared prep above (`vendor:notifee`, `patch:android-foss`, `generate`). + +After tagging, update the metadata commit SHA and version fields, then open/update the fdroiddata merge request. + +## Version codes + +| Channel | versionCode | +|---------|-------------| +| Play AAB | `defaultConfig.versionCode` | +| Play / local split APKs | base + ABI offset (armeabi-v7a +0, arm64-v8a +1, x86 +2, x86_64 +3) | +| F-Droid per-ABI build | Set in metadata prebuild (`$$VERCODE$$`) | + +When bumping a release, increase `defaultConfig.versionCode` and update F-Droid build entries (and `CurrentVersionCode`) to match the highest per-ABI code. diff --git a/formulus/android/app/build.gradle b/formulus/android/app/build.gradle index 7c869c946..74cc1a770 100644 --- a/formulus/android/app/build.gradle +++ b/formulus/android/app/build.gradle @@ -87,7 +87,10 @@ def enableProguardInReleaseBuilds = false */ def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' -def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4] +def perAbiVersionCode(int baseVersionCode, String abi) { + def offsets = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4] + return baseVersionCode + offsets.get(abi, 0) - 1 +} android { ndkVersion = rootProject.ext.ndkVersion @@ -158,8 +161,7 @@ android { variant.outputs.each { output -> def abi = output.getFilter(com.android.build.OutputFile.ABI) if (abi != null) { - output.versionCodeOverride = - (100 * defaultConfig.versionCode) + abiVersionCodes.get(abi, 0) + output.versionCodeOverride = perAbiVersionCode(defaultConfig.versionCode, abi) } } }