Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
110 changes: 110 additions & 0 deletions .github/workflows/fdroid-build.yml
Original file line number Diff line number Diff line change
@@ -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/<appid> 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
20 changes: 10 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
95 changes: 89 additions & 6 deletions FDROID.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<abi>`. 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
Expand Down Expand Up @@ -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-<version>-arm64-v8a.apk

# the source-tree scan; clones the tag and runs `npm ci`, needs no Android SDK
fdroid scanner -e com.bracketup.app:<versionCode>
```

`fdroid scanner` needs `dexdump` from the Android SDK build-tools to scan an APK;
unpacking `build-tools_r36.1_linux.zip` from
<https://dl.google.com/android/repository/> 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
Expand All @@ -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
Expand Down
Loading