Preparing Recipes¶
This document describes how to set up your build recipes. Start with simple things: for the „average“ Java/Kotlin app, the first two sections should get you going. They are followed by some more special cases.
The recipe file¶
The easiest way to get started, is probably to grab an existing recipe as „template“ and adjust it to the app to be build. Most of the fields are self explaining, others need some background. Here's an example template:
---
repository: https://github.com/Owner/App_Name.git
updates: releases
versions:
- tag: v0.1.2
apks:
- apk_pattern: app-release\.apk
apk_url: https://github.com/Owner/App_Name/releases/download/v0.1.2/app-release.apk
build:
- sed -r '/signingConfigs.releaseConfig/d' -i app/build.gradle
- ### when adding extra_packages from upstream specs, watch the log for 'is already the newest version' to see if they are really needed (remove this line before running the recipe)
- chmod +x gradlew
- ./gradlew assembleRelease
- find . -name '*.apk'
- mv app/build/outputs/apk/release/*unsigned.apk /outputs/unsigned.apk
build_cpus:
build_home_dir: /build
build_repo_dir: /build/repo
build_timeout:
build_user: build
provisioning:
android_home: /opt/sdk
build_tools:
cmake:
cmdline_tools:
version: '12.0'
url: https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
sha256: 2d2d50857e4eb553af5a6dc3ad507a17adf43d115264b1afc116f95c92e5e258
extra_packages: []
image: debian:bookworm-slim
jdk: openjdk-17-jdk-headless
ndk:
platform:
platform_tools:
tools:
verify_gradle_wrapper: true
You can look up all fields in schemas/recipe.json of course, which is also used to lint recipes. Not all of them are mandatory, and some of them you rarely ever need to change (e.g. the cmdline_tools). Instead of going through each of them here, let's focus on those which usually need adjustment, and leave the others for the special cases they will be used for.
repository:the Git repository the source code resides in. Should end in.git.updates:will usually be set toreleases, which means update checks will be performed on the latest release (not pre-release, those are ignored withreleases). While you usually will want to stick to stable releases, you have other options here:disabledyou will use when an app is no longer maintained (e.g. its repository has been archived), but you still want to keep itmanualif you want to exclude the app from automated update checkstags:.*can be used to specify a tag name pattern. This will also work with pre-releases. APKs will still be looked at the tag/release (seeapk_pattern)- you can prefix the update mode with
checkonly:if you don't want automated updates to pick up new versions, but just report them in the logs
versions:in here will go the version specific things. Each version will have its own tag. The value you have to provide fortag:must match the tag name in the repository.apk_pattern:a regular expression matching the name of the APK file to fetch from the tag. If there are multiple APKs, make sure to match only the one you need: this is the APK used to compare your build against. There can be multiple APK items in a tag block, useful e.g. if you want to confirm multiple architectures/build-flavors.apk_url:where to fetch the upstream APK frombuild:here goes the build recipe. Above template has a comment in you should of course remove (it's just a hint to the builder's operator when setting up recipes), and thefindis intended for the first test runs when you are not yet sure how the output file might be named.build_home_dir:,build_repo_dir:,build_user:you „normally“ do not need to touch these – unless there are e.g. embedded paths in library files you need to match (e.g. with Flutter or Rust components).provisioning:is where to define how your image should be prepared. Most of the fields here you only need to touch in special cases. The most relevant „for starters“ areimage:andjdk:, which are explained in the next section. A special flag isverify_gradle_wrapper:, which you in most cases will leave attrueto ensure to only use approved gradle binaries. Exceptions to this are e.g. Flutter recipes – and cases where you have to use your own gradle wrapper (details on that further down).extra_packages:is a list of OS packages a build depends on, but which are not provided by default. An example for that would becurlwith Flutter apps.
Chosing JDK and image¶
Our current defaults are to use OpenJDK 17 on Debian bookworm – unless it is clear that something else would be needed. There are different pointers helping with the selection.
JDK version¶
The build.gradle files usually have hints for this:
jvmTarget: „Target version of the generated JVM bytecode“. The linked documentation does not explain, but it most likely means „generate byte code compatible to that Java version“ – which implies you must have at least that version available, as a lower one would not know how to do thatsourceCompatibility: code needs at least this Java version to compiletargetCompatibility: resulting app needs at least that Java version to run (cannot run with a lower JRE)- Github workflows provide different SDKs. Most projects use
temurin, which seems to be very close to OpenJDK.adoptseems to be problematic,zuluwe had issues with, too (though it's not yet clear if they came fromzulu– often it works fine). Then there'soracle, which should be OpenJDK (not sure yet). This needs some more evaluation to be used for recommendations (statistics maybe, how often which is used and we can RB, and how often RB fails?). - Java release dates and Gradle/JDK compatibility Matrix (see the
distributionUrlingradle/wrapper/gradle-wrapper.properties):
| Java Version | class file format | released | minGradle |
|---|---|---|---|
| 11 | 55 | 2018-09-25 | 5.0 |
| 17 | 61 | 2021-09-14 | 7.3 |
| 21 | 65 | 2023-09-19 | 8.5 |
| 25 | 69 | 2025-09-16 | 9.1 |
Decide for an image¶
- Debian:
- Trixie for JDK-21:
debian:trixie-slim - Bookworm for JDK-17:
debian:bookworm-slim - Bullseye for JDK-11 (in
extra_packages):debian:bullseye-slim
- Trixie for JDK-21:
- Ubuntu:
- 22.04 for JDK-21:
ubuntu:jammy - 24.04:
ubuntu:latestin Github actions currently (3/2025) meansubuntu:noble, so this might be worth trying if the defaults fail
- 22.04 for JDK-21:
An example for JDK 11, as this needs some special adjustments (due to the fact that Gradle itself still needs JDK 17 or newer):
build:
- JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 ./gradlew assembleRelease
...
provisioning:
...
extra_packages:
- openjdk-11-jdk-headless
image: debian:bullseye-slim
jdk: openjdk-17-jdk-headless
Gradle wrapper¶
Most repositories will come prepared with a gradle-wrapper.properties file and the corresponding ./gradlew wrapper script ready, which in above recipe is reflected by ./gradlew assembleRelease. But sometimes your builds might „crash“ before they even start, stating that the gradle version is unknown, or the gradle hash does not match. Of course it would be easy to simply set verify_gradle_wrapper: false in such cases – but for security reasons, that's obviously not advised. And it wouldn't help in cases where the gradle wrapper is missing, or even just parts of it (e.g. the gradlew script).
For such cases, Fay has developed gradlew.py, which we continue maintaining, so you can use like this:
- git clone https://codeberg.org/IzzyOnDroid/gradlew.py.git
- gradlew.py/gradlew.py --version 8.4 -v assembleRelease
The --version <version> can be omitted when just the gradlew script was missing, gradlew.py then looks up the gradle version in gradle-wrapper.properties. And it brings its own verifier along; so when using gradlew.py, you even should set verify_gradle_wrapper: false.
Note
as Fay can no longer maintain her gradlew.py, it will probably fail with newer Gradle versions (8.14+). You can use our maintained fork instead.
Recipe specialities¶
Whenever a project has set up some CI to build their app, you should take a look at that. Not only it usually reveals what system (image) they build on and which JDK they use, but it might also reveal special build steps needed, as well as their order and dependencies. With projects hosted at Github, you usually find the corresponding definitions in the .github/workflows directory. Projects on Codeberg might have them defined in their .woodpecker.yml.
Signing¶
To confirm a build as reproducible, we will need to build an unsigned APK. But more often than not, instructions in the build.gradle/build.gradle.kts enforce signing. Our build would fail then, as we of course lack the credentials and the keystore. This is why in above example recipe you see the line holding sed -r '/signingConfigs.releaseConfig/d', to remove the signing instruction. This instruction varies between projects, so you need to check what to match exactly (to not accidentally remove to much and e.g. also break the syntax of the gradle file). You usually find the corresponding line in the buildTypes section of the app/build.gradle[.kts] file under release.
To ease your work with this, you can point developers to our chapter on the Signing Config, advising them to make signing optional – depending on whether the keystore exists or not.
Build flavors¶
If a project offers different build variants (e.g. one for PlayStore, and one Foss), you need to match that in your recipe. We cannot give in-depth instructions on that here, but basically for a foss flavor, you'd change the build instruction to ./gradlew assembleFossFelease – and need to adjust the output file name as well then (the find in the example recipe above takes care to list you resulting APKs to pick from).