diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 985eee5..ea28935 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,8 +9,8 @@ jobs: checks: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm diff --git a/.github/workflows/fdroid-build.yml b/.github/workflows/fdroid-build.yml new file mode 100644 index 0000000..2a430fe --- /dev/null +++ b/.github/workflows/fdroid-build.yml @@ -0,0 +1,110 @@ +# Verify fdroid/com.bracketup.app.yml the way fdroiddata's CI does: run +# `fdroid build` inside F-Droid's own buildserver image. Manual only -- this is +# a slow, large job and nothing about a normal push needs it. +# +# The recipe provisions its own Temurin 17 (see FDROID.md, "The Java 17 +# toolchain"), so this job only has to supply the SDK and NDK that fdroiddata's +# CI installs on top of the image. +name: F-Droid recipe build + +on: + workflow_dispatch: + inputs: + versioncode: + description: Which build entry to run (21 armeabi-v7a, 22 arm64-v8a, 23 x86_64) + type: choice + options: ['22', '21', '23'] + default: '22' + +jobs: + fdroid-build: + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v7 + + # The buildserver image plus the SDK, NDK and a Gradle build do not fit + # alongside the runner's preinstalled toolchains. + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ + /usr/local/share/boost "$AGENT_TOOLSDIRECTORY" + df -h / + + - name: Run fdroid build in the buildserver image + env: + VERSIONCODE: ${{ inputs.versioncode }} + run: | + mkdir -p work && cp fdroid/com.bracketup.app.yml work/ + cat > work/build.sh <<'SCRIPT' + set -e + source /etc/profile.d/bsenv.sh + export PYTHONUNBUFFERED=true + + # fdroidserver from git master needs a git checkout to report a + # version, so use the distro package. + apt-get update -qq + apt-get install -qy --no-install-recommends fdroidserver + fdroid --version + + # fdroiddata CI refreshes gradlew-fdroid before every build. The copy + # bundled in the .deb lags the gradle-transparency-log, so point + # fdroid at the image's checkout instead. + git config --global --add safe.directory /home/vagrant/gradlew-fdroid + git -C /home/vagrant/gradlew-fdroid pull --quiet || true + + yes | sdkmanager "platform-tools" "build-tools;36.0.0" \ + "platforms;android-36" "ndk;27.1.12297006" "cmake;3.22.1" > /tmp/sdk.log 2>&1 \ + || { tail -20 /tmp/sdk.log; exit 1; } + + mkdir -p "$home_vagrant"/{metadata,build,tmp,logs,unsigned,srclibs,.android,.gradle} + printf 'gradle: /usr/local/bin/gradle\n' > "$home_vagrant/config.yml" + chmod 0600 "$home_vagrant/config.yml" + cp /work/com.bracketup.app.yml "$home_vagrant/metadata/" + chown -R vagrant "$home_vagrant" "$ANDROID_HOME" + cd "$home_vagrant" + + asvagrant() { + sudo --preserve-env --user vagrant \ + env PATH="$PATH" PYTHONUNBUFFERED=true HOME="$home_vagrant" "$@" + } + + # `fdroid build` expects build/ to exist. fdroiddata CI creates + # it with `fdroid fetchsrclibs`, which only exists in git master. + asvagrant git clone https://github.com/lbellows/bracket-up.git \ + "$home_vagrant/build/com.bracketup.app" + + set +e + asvagrant fdroid build --verbose --test --refresh-scanner --on-server \ + --no-tarball "com.bracketup.app:$VERSIONCODE" + rc=$? + set -e + + cp -v "$home_vagrant"/tmp/*.apk /work/ 2>/dev/null || true + mkdir -p /work/logs && cp -v "$home_vagrant"/logs/* /work/logs/ 2>/dev/null || true + chmod -R a+rw /work + exit $rc + SCRIPT + docker run --rm \ + -e VERSIONCODE \ + -v "$PWD/work:/work" \ + registry.gitlab.com/fdroid/fdroidserver:buildserver-trixie \ + bash /work/build.sh + + - name: Inspect the built APK + if: always() + run: | + APK=$(ls work/*.apk 2>/dev/null | head -1) || exit 0 + [ -n "$APK" ] || { echo "no APK produced"; exit 0; } + BT=$(ls -d "$ANDROID_HOME"/build-tools/* | tail -1) + "$BT/aapt2" dump badging "$APK" | grep -E '^package:|^native-code:|^uses-permission:' + ls -l "$APK" + + - uses: actions/upload-artifact@v7 + if: always() + with: + name: fdroid-build-${{ inputs.versioncode }} + path: | + work/*.apk + work/logs/ + if-no-files-found: warn diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 208ad0d..07d8c67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,19 +23,19 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v6 with: distribution: temurin java-version: 17 - - uses: android-actions/setup-android@v3 + - uses: android-actions/setup-android@v4 - name: Install dependencies run: npm ci @@ -118,7 +118,7 @@ jobs: done ls -l dist - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: apks path: dist/*.apk @@ -131,14 +131,14 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v6 with: distribution: temurin java-version: 17 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: apks path: dist @@ -164,13 +164,13 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: apks path: dist - name: Publish GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: dist/*.apk generate_release_notes: true diff --git a/FDROID.md b/FDROID.md index e139b3c..fb4de2f 100644 --- a/FDROID.md +++ b/FDROID.md @@ -54,7 +54,12 @@ in this repo, so the description, screenshots and changelogs are already in plac everything except publish — that is the way to check a build before tagging. - `fdroid/com.bracketup.app.yml` is the recipe to submit to fdroiddata. It has one build entry per architecture, each pinned to a single ABI with - `-PreactNativeArchitectures=`. + `gradleprops: reactNativeArchitectures=`. It is kept in fdroid's canonical + format — the exact output of `fdroid rewritemeta` — so it can be copied into a + fdroiddata fork verbatim. fdroiddata's CI runs `fdroid rewritemeta` on every + changed file and fails the merge request if the file changes, and that rewrite + strips YAML comments, which is why the recipe carries no comments and the + rationale a reviewer needs lives in its `MaintainerNotes` instead. - Version numbers live in `app.json` (`expo.version` and `expo.android.versionCode`). EAS was removed entirely (`eas.json` and the `extra.eas` project binding), so the repo is the single source of truth — F-Droid reads the version from source and @@ -262,15 +267,36 @@ within a day — and you do not file anything again. Users install by adding `VercodeOperation` (`10 * %c + 1/2/3`) tells the auto-updater to copy all three entries on a new tag and assign each the code that ABI's APK declares. The entries must stay in ascending-offset order for that mapping to hold. -3. Test the recipe if you can — it needs Docker and a lot of disk: - - ```bash - fdroid build -v -l com.bracketup.app - ``` +3. Let fdroiddata's CI build it. The merge request runs `fdroid build` on + F-Droid's own buildserver image, which is the verification that counts — + building locally means reproducing that environment by hand, and a local pass + would not prove anything the CI run does not. 4. Open a merge request. Review is slow and reviewers do ask questions; the common ones for this app are answered in the recipe's `MaintainerNotes`. +Before opening the merge request, run the two checks fdroiddata's CI runs — both +work on an ordinary machine, no Docker or Android SDK needed for the first: + +```bash +# in a fdroiddata checkout, with the recipe copied to metadata/ +fdroid lint -f com.bracketup.app # metadata + canonical formatting +fdroid rewritemeta com.bracketup.app # must leave the file unchanged + +# the scan Izzy and fdroiddata both run over the built APKs +fdroid scanner -r -e BracketUp--arm64-v8a.apk + +# the source-tree scan; clones the tag and runs `npm ci`, needs no Android SDK +fdroid scanner -e com.bracketup.app: +``` + +`fdroid scanner` needs `dexdump` from the Android SDK build-tools to scan an APK; +unpacking `build-tools_r36.1_linux.zip` from + into `$ANDROID_HOME/build-tools/36.1.0` +is enough, and does not require a JDK. Note that `fdroid lint` run outside a +fdroiddata checkout wrongly reports the category as invalid — it reads the valid +list from that repo's `config/categories.yml` and finds nothing without it. + Things a reviewer may raise, and where they stand here: - **Prebuilt binaries in `node_modules`.** Covered by `scanignore`. React Native's @@ -287,6 +313,63 @@ Things a reviewer may raise, and where they stand here: `expo prebuild` (which fetches templates at build time). CI proves the committed project matches `app.json`. +### The Java 17 toolchain + +React Native's Gradle plugin (`JdkConfiguratorUtils`) applies +`kotlin { jvmToolchain(17) }` and `sourceCompatibility/targetCompatibility = +VERSION_17` to *every* module in the build, and `expo-modules-core` sets +`kotlin.jvmToolchain(17)` for KSP as well. Gradle matches a toolchain version +exactly. The buildserver installs `default-jdk-headless` and nothing else, which +on Debian trixie is JDK 21 — and trixie has no `openjdk-17` package at all, only +21 and 25. Without a JDK 17 the build fails at +`:app:compileReleaseJavaWithJavac` with: + +``` +Cannot find a Java installation on your machine matching: +{languageVersion=17, ...}. Toolchain auto-provisioning is not enabled. +``` + +So the recipe fetches a pinned Temurin 17 in its `sudo:` block, the same way it +fetches Node, and names the path in `gradleprops` so Gradle finds it whatever +else it auto-detects. This is verified — it produces the APK. + +If a reviewer questions the download, the answer is that it is not a workaround +invented for F-Droid. The `build` and `smoke-test` jobs in +`.github/workflows/release.yml` both run `actions/setup-java` with +`distribution: temurin, java-version: 17`, so every APK on the GitHub Releases +page — including the one IzzyOnDroid serves — is already built with Temurin 17. +Pinning it here only makes the buildserver match how the app is built +everywhere else. + +Two alternatives, if it comes to that. React Native checks +`react.internal.disableJavaVersionAlignment` and skips all of the above, but it +sets no replacement target, so `android/app/build.gradle` would need explicit +`compileOptions` and a Kotlin `jvmTarget` to avoid an inconsistent-JVM-target +failure, and it does not cover `expo-modules-core`'s KSP toolchain. Or wait: the +requirement is upstream React Native's, not this app's, and disappears when RN +aligns on 21. + +Bump the pinned JDK the same way you would bump Node — new URL, new checksum +from the same release, and the new directory name in the `gradleprops` path. + +### Testing the recipe remotely + +`.github/workflows/fdroid-build.yml` runs `fdroid build` inside F-Droid's own +buildserver image on a GitHub runner — the same thing fdroiddata's CI does, so +the recipe can be checked without reproducing that environment locally. It is +`workflow_dispatch` only: + +```bash +gh workflow run "F-Droid recipe build" # arm64-v8a +gh workflow run "F-Droid recipe build" -f versioncode=21 # a different ABI +``` + +It builds from the tag named in the recipe's `commit:`, not from the branch you +dispatch it on, and uploads the APK and fdroid's build logs as artifacts. The +job installs only what fdroiddata's CI installs on top of the buildserver image +— platform-tools, build-tools, a platform, the NDK and CMake — so anything else +the build needs has to come from the recipe, which is the point. + --- ## Development caveat diff --git a/fdroid/com.bracketup.app.yml b/fdroid/com.bracketup.app.yml index 2329f6c..c8b8b35 100644 --- a/fdroid/com.bracketup.app.yml +++ b/fdroid/com.bracketup.app.yml @@ -1,11 +1,5 @@ -# F-Droid build recipe for BracketUp. -# -# This file is not used by this repository — it is the metadata to submit to -# https://gitlab.com/fdroid/fdroiddata as metadata/com.bracketup.app.yml. -# It is kept here so the recipe is versioned alongside the code it builds. -# See FDROID.md for the submission procedure. - Categories: + - Game Helper - Sports & Health License: MIT AuthorName: LCB @@ -19,88 +13,89 @@ RepoType: git Repo: https://github.com/lbellows/bracket-up.git Builds: - # One build per architecture. The Gradle build splits the APK by ABI, and - # `reactNativeArchitectures` narrows each run to a single one so every build - # here produces exactly one APK. Each APK's versionCode is - # `expo.android.versionCode * 10 + ` (armeabi-v7a 1, arm64-v8a 2, - # x86_64 3), assigned in plugins/withAbiSplits.js. - versionName: 1.0.1 versionCode: 21 commit: v1.0.1 subdir: android/app - gradleprops: - - reactNativeArchitectures=armeabi-v7a sudo: - - curl -Lo node.tar.xz https://nodejs.org/dist/v22.23.2/node-v22.23.2-linux-x64.tar.xz - - echo "d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307 node.tar.xz" | sha256sum -c - - - tar xf node.tar.xz --directory=/opt + - curl -Lo node.tar.gz https://nodejs.org/dist/v22.23.2/node-v22.23.2-linux-x64.tar.gz + - echo "b294a556e639d64338823920e5866c21c02741742d2e1529ee1a225c1ec9252a node.tar.gz" + | sha256sum -c - + - tar xzf node.tar.gz --directory=/opt - mv /opt/node-v22.23.2-linux-x64 /opt/node - ln -s /opt/node/bin/node /opt/node/bin/npm /opt/node/bin/npx /usr/local/bin/ - init: - - npm ci + - curl -Lo jdk17.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.20.1%2B1/OpenJDK17U-jdk_x64_linux_hotspot_17.0.20.1_1.tar.gz + - echo "3808d1d15e3ec6bd5b84057fb5d84c33d8a1536a258146bcea2e603fc726e08e jdk17.tar.gz" + | sha256sum -c - + - mkdir -p /usr/lib/jvm + - tar xzf jdk17.tar.gz --directory=/usr/lib/jvm + init: npm ci gradle: - yes rm: - .github - store-assets scanignore: - # React Native ships prebuilt tooling (hermesc, the Gradle plugin's jars) - # inside node_modules. These are build-time only and are not packaged into - # the APK; the shipped native libraries are compiled from the Maven-hosted - # React Native and Hermes sources during the Gradle build. - node_modules + gradleprops: + - reactNativeArchitectures=armeabi-v7a + - org.gradle.java.installations.paths=/usr/lib/jvm/jdk-17.0.20.1+1 - versionName: 1.0.1 versionCode: 22 commit: v1.0.1 subdir: android/app - gradleprops: - - reactNativeArchitectures=arm64-v8a sudo: - - curl -Lo node.tar.xz https://nodejs.org/dist/v22.23.2/node-v22.23.2-linux-x64.tar.xz - - echo "d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307 node.tar.xz" | sha256sum -c - - - tar xf node.tar.xz --directory=/opt + - curl -Lo node.tar.gz https://nodejs.org/dist/v22.23.2/node-v22.23.2-linux-x64.tar.gz + - echo "b294a556e639d64338823920e5866c21c02741742d2e1529ee1a225c1ec9252a node.tar.gz" + | sha256sum -c - + - tar xzf node.tar.gz --directory=/opt - mv /opt/node-v22.23.2-linux-x64 /opt/node - ln -s /opt/node/bin/node /opt/node/bin/npm /opt/node/bin/npx /usr/local/bin/ - init: - - npm ci + - curl -Lo jdk17.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.20.1%2B1/OpenJDK17U-jdk_x64_linux_hotspot_17.0.20.1_1.tar.gz + - echo "3808d1d15e3ec6bd5b84057fb5d84c33d8a1536a258146bcea2e603fc726e08e jdk17.tar.gz" + | sha256sum -c - + - mkdir -p /usr/lib/jvm + - tar xzf jdk17.tar.gz --directory=/usr/lib/jvm + init: npm ci gradle: - yes rm: - .github - store-assets scanignore: - # React Native ships prebuilt tooling (hermesc, the Gradle plugin's jars) - # inside node_modules. These are build-time only and are not packaged into - # the APK; the shipped native libraries are compiled from the Maven-hosted - # React Native and Hermes sources during the Gradle build. - node_modules + gradleprops: + - reactNativeArchitectures=arm64-v8a + - org.gradle.java.installations.paths=/usr/lib/jvm/jdk-17.0.20.1+1 - versionName: 1.0.1 versionCode: 23 commit: v1.0.1 subdir: android/app - gradleprops: - - reactNativeArchitectures=x86_64 sudo: - - curl -Lo node.tar.xz https://nodejs.org/dist/v22.23.2/node-v22.23.2-linux-x64.tar.xz - - echo "d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307 node.tar.xz" | sha256sum -c - - - tar xf node.tar.xz --directory=/opt + - curl -Lo node.tar.gz https://nodejs.org/dist/v22.23.2/node-v22.23.2-linux-x64.tar.gz + - echo "b294a556e639d64338823920e5866c21c02741742d2e1529ee1a225c1ec9252a node.tar.gz" + | sha256sum -c - + - tar xzf node.tar.gz --directory=/opt - mv /opt/node-v22.23.2-linux-x64 /opt/node - ln -s /opt/node/bin/node /opt/node/bin/npm /opt/node/bin/npx /usr/local/bin/ - init: - - npm ci + - curl -Lo jdk17.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.20.1%2B1/OpenJDK17U-jdk_x64_linux_hotspot_17.0.20.1_1.tar.gz + - echo "3808d1d15e3ec6bd5b84057fb5d84c33d8a1536a258146bcea2e603fc726e08e jdk17.tar.gz" + | sha256sum -c - + - mkdir -p /usr/lib/jvm + - tar xzf jdk17.tar.gz --directory=/usr/lib/jvm + init: npm ci gradle: - yes rm: - .github - store-assets scanignore: - # React Native ships prebuilt tooling (hermesc, the Gradle plugin's jars) - # inside node_modules. These are build-time only and are not packaged into - # the APK; the shipped native libraries are compiled from the Maven-hosted - # React Native and Hermes sources during the Gradle build. - node_modules + gradleprops: + - reactNativeArchitectures=x86_64 + - org.gradle.java.installations.paths=/usr/lib/jvm/jdk-17.0.20.1+1 MaintainerNotes: |- Expo (React Native) app. The native android/ project is committed to the @@ -108,9 +103,19 @@ MaintainerNotes: |- it drifts from app.json, so the checked-in project always matches the config. Every release has three builds, one per ABI, because a universal APK is ~97 MB. - VercodeOperation reproduces the versionCode scheme, so the auto-updater copies - all three entries on a new tag and gives each the right code — the build blocks - are ordered to match the operations, ascending. + The Gradle build splits the APK by ABI and `reactNativeArchitectures` narrows + each build entry to a single one, so every entry here produces exactly one APK, + whose versionCode is `expo.android.versionCode * 10 + ` + (armeabi-v7a 1, arm64-v8a 2, x86_64 3), assigned in plugins/withAbiSplits.js. + VercodeOperation reproduces that scheme, so the auto-updater copies all three + entries on a new tag and gives each the right code — the build blocks are + ordered to match the operations, ascending. + + `scanignore: node_modules` covers the prebuilt tooling React Native ships + inside node_modules (hermesc, the Gradle plugin's jars) and the local Maven + repos a few Expo modules carry. All of it is build-time only and none of it is + packaged into the APK; the native libraries that are shipped are compiled + during the Gradle build from the Maven-hosted React Native and Hermes sources. `gradle assembleRelease` produces an unsigned APK here: the release signing config activates only when a keystore is supplied via environment variables,