Apps built with clj-android can be published on F-Droid with reproducible builds. This document covers the required configuration.
The clj-android build system produces deterministic output:
- Clojure AOT compilation generates identical bytecode across builds (via a
patched
Compiler.javawith deterministicLocalBinding.hashCode()) - Namespace compilation order is sorted, eliminating filesystem ordering dependencies
The F-Droid source scanner flags certain patterns in the dependency source
trees that are cloned into build/deps/. Add these fields to your app's
F-Droid metadata file to handle them:
Builds:
- versionName: ...
versionCode: ...
gradle:
- yes
scandelete:
# Gradle compiles the plugin at configuration time, creating binary
# artifacts before the scanner runs. They are recompiled during the
# actual build.
- build/deps/android-clojure-plugin/build/
scanignore:
# InMemoryDexClassLoader triggers a substring match on "DexClassLoader".
# This class is only used in debug builds for REPL support.
- build/deps/runtime-repl/src/main/java/clojure/lang/AndroidDynamicClassLoader.javascandelete for android-clojure-plugin/build/: The android-clojure-plugin
is an included Gradle build. Gradle must compile it to apply the plugin during
project configuration, which happens before the scanner runs. The scandelete
removes the compiled artifacts; they are regenerated when the actual build
starts.
scanignore for AndroidDynamicClassLoader.java: This file imports
dalvik.system.InMemoryDexClassLoader (an Android SDK class for loading DEX
from memory). F-Droid's scanner flags the substring "DexClassLoader" within
that class name. The class is only included in debug builds via the
runtime-repl dependency and is not present in release APKs.
Use apksigner from build-tools 34.x, not 35.0.0+. The newer
apksigner produces signature padding that apksigcopier (used by
F-Droid to verify reproducible builds) cannot transplant correctly,
causing verification failure even when the unsigned APKs are
byte-identical.
~/Android/Sdk/build-tools/34.0.0/apksigner sign \
-ks your-keystore.jks --ks-key-alias your-alias \
app/build/outputs/apk/release/your-app.apkDisable ART profile generation — it is non-deterministic across build
environments. In app/build.gradle.kts:
tasks.whenTaskAdded {
if (name.contains("ArtProfile")) {
enabled = false
}
}F-Droid automatically removes gradle-wrapper.jar files from cloned
dependencies. No metadata configuration is needed for these.