Skip to content

Commit 5da0fe3

Browse files
monkopedia-coderMonkopediaclaude
authored
refactor(build): extract Firebase/Google-services into a -Pgoogle-only script so the FOSS build is genuinely Firebase-free (F-Droid) + bump 1.0.7 (#534)
The FOSS build (bare `assembleRelease`) already skipped applying the Firebase Gradle plugins at runtime, but every Firebase/Google-services coordinate — the two plugin ids, the Crashlytics per-build-type config, and the firebase-* dependencies — still lived inline in app/build.gradle.kts, guarded only by `if (googleBuild)`. F-Droid's source scanner is static, so it still saw those non-FOSS coordinates in a file that survives into the FOSS build. The fdroiddata maintainer asked us to remove the non-FOSS deps in prebuild rather than allow-list them with scanignore. This extracts ALL Google/Firebase build config into gradle/google-services.gradle.kts, applied from app/build.gradle.kts ONLY under `-Pgoogle` (`if (googleBuild) { apply(from = ...) }`). The bare FOSS build never applies it, so it compiles ZERO Firebase: no plugins on the classpath, no firebase-* deps, no Crashlytics config, and google-services.json is not required to assemble it. Verified: aapt2 dump strings on the FOSS APK shows no firebase/gms strings and the DEX contains no com.google.firebase / com.google.android.gms classes, while the `-Pgoogle` APK still links Firebase. The extracted file lives in the repo-root gradle/ dir (not app/) because Android Lint's build-script visitor (lintVitalRelease, which the FOSS release build runs) crashes trying to FIR-resolve a standalone apply(from=...) script inside an Android module; the root project has no Android lint task. Its Crashlytics mapping/symbol toggles are set by reflection because an apply-script has no AGP / crashlytics DSL types on its classpath. The fdroiddata recipe now removes the file in `prebuild: rm -f gradle/google-services.gradle.kts` before the scanner runs (no scanignore/scandelete), and the version/toolchain fields + MaintainerNotes are updated to 1.0.7. Adds changelog 8.txt (build-internal, no user-facing change vs 1.0.6). Part of #465. Co-authored-by: Jason Monk <[email protected]> Co-authored-by: Claude Opus 4.8 <[email protected]>
1 parent 36deab2 commit 5da0fe3

5 files changed

Lines changed: 188 additions & 59 deletions

File tree

app/build.gradle.kts

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,32 @@ plugins {
1919
// non-default and forced task-name sniffing to re-derive "is this credentialed?".
2020
val googleBuild = project.hasProperty("google")
2121

22-
// The Firebase Gradle plugins (`google-services`, `firebase-crashlytics`) are
23-
// applied only for the Google build. The FOSS build skips them entirely so that:
24-
// * `google-services.json` is NOT required to assemble it, and
25-
// * no Play-Services / Firebase Gradle processing runs.
26-
// Keeping the plugins behind the flag preserves the google-services plugin's
27-
// hard-fail-on-missing-credentials — a `-Pgoogle` build with no
22+
// ALL Firebase / Google-services build config — the two Gradle plugins, the
23+
// Crashlytics per-build-type setup, and the Firebase runtime dependencies —
24+
// lives in gradle/google-services.gradle.kts and is applied ONLY for the
25+
// `-Pgoogle` build. The bare FOSS build never applies it, so the FOSS build
26+
// compiles ZERO Firebase/Google-services: no plugins on the classpath, no
27+
// Firebase deps, no Crashlytics config, and `google-services.json` is NOT
28+
// required to assemble it.
29+
//
30+
// The config is extracted into a separate file (rather than guarded inline) so
31+
// that NO Firebase dependency/plugin coordinate appears in any build file that
32+
// survives into the FOSS build. F-Droid's static source scanner therefore sees
33+
// no non-FOSS coordinates; the fdroiddata recipe removes the file in `prebuild:`
34+
// before scanning (safe — the FOSS build never applies it).
35+
//
36+
// The script lives OUTSIDE app/ (in the repo-root gradle/ dir) because Android
37+
// Lint's build-script visitor (lintVitalRelease) crashes when it tries to build
38+
// a FIR light-class for a standalone `apply(from=...)` script inside a module
39+
// that has the Android plugin. Keeping it out of app/'s lint scan tree — the
40+
// root project has no Android lint task — sidesteps that upstream crash while
41+
// still letting the `-Pgoogle` app build apply it.
42+
//
43+
// Applying the plugins only under `-Pgoogle` also preserves the google-services
44+
// plugin's hard-fail-on-missing-credentials: a `-Pgoogle` build with no
2845
// `google-services.json` fails loudly rather than producing a broken APK.
2946
if (googleBuild) {
30-
apply(plugin = "com.google.gms.google-services")
31-
apply(plugin = "com.google.firebase.crashlytics")
47+
apply(from = "$rootDir/gradle/google-services.gradle.kts")
3248
}
3349

3450
// Loads a value from `.signing/release.properties` if that file exists.
@@ -109,8 +125,8 @@ android {
109125
applicationId = "com.rousecontext"
110126
minSdk = 24
111127
targetSdk = 35
112-
versionCode = 7
113-
versionName = "1.0.6"
128+
versionCode = 8
129+
versionName = "1.0.7"
114130

115131
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
116132

@@ -149,17 +165,9 @@ android {
149165
debug {
150166
signingConfig = signingConfigs.getByName("debug")
151167
applicationIdSuffix = ".debug"
152-
// No mapping upload in debug — the Crashlytics plugin runs even here
153-
// (under `-Pgoogle`) because the library is linked in that build. We
154-
// still skip the slow symbol upload work to keep debug assembly fast
155-
// for iteration. Guarded so FOSS builds, which never apply the
156-
// Crashlytics plugin, don't reference a missing extension.
157-
if (googleBuild) {
158-
configure<com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension> {
159-
mappingFileUploadEnabled = false
160-
nativeSymbolUploadEnabled = false
161-
}
162-
}
168+
// Crashlytics per-build-type config (mapping/symbol upload toggles)
169+
// lives in app/google-services.gradle.kts, applied only under
170+
// `-Pgoogle`. The FOSS build has no Crashlytics extension to configure.
163171
}
164172
release {
165173
// Only sign when credentials are present; otherwise build unsigned
@@ -175,15 +183,8 @@ android {
175183
// AGP embeds VCS/commit info in the release APK by default, which is
176184
// non-reproducible. Drop it so F-Droid reproducible builds succeed.
177185
vcsInfo.include = false
178-
// Release builds ship R8-obfuscated code; upload the mapping so
179-
// Crashlytics deobfuscates stacks. We have no NDK code, so native
180-
// symbol upload stays off. Guarded as above for FOSS builds.
181-
if (googleBuild) {
182-
configure<com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension> {
183-
mappingFileUploadEnabled = true
184-
nativeSymbolUploadEnabled = false
185-
}
186-
}
186+
// Crashlytics mapping upload is configured in
187+
// app/google-services.gradle.kts, applied only under `-Pgoogle`.
187188
}
188189
}
189190

@@ -268,18 +269,15 @@ dependencies {
268269
ksp(libs.room.compiler)
269270

270271
// Distribution-specific dependencies, selected by the `-Pgoogle` flag
271-
// (issue #467). The Google build links Firebase (FCM wake + Crashlytics);
272-
// the FOSS build links UnifiedPush (distributor-based wake) + ACRA (crash
273-
// reporting via the relay's `POST /crash`) and zero firebase-* artifacts.
274-
// Both distributions sit behind the same distribution-agnostic Koin seams
275-
// (BackgroundDelivery, CrashReporter, device-identity providers — see
276-
// app/src/{google,foss}).
277-
if (googleBuild) {
278-
implementation(platform(libs.firebase.bom))
279-
implementation(libs.firebase.auth)
280-
implementation(libs.firebase.messaging)
281-
implementation(libs.firebase.crashlytics)
282-
} else {
272+
// (issue #467). The Google build links Firebase (FCM wake + Crashlytics) via
273+
// app/google-services.gradle.kts (applied only under `-Pgoogle`, so no
274+
// Firebase coordinate appears in any build file that survives into the FOSS
275+
// build); the FOSS build links UnifiedPush (distributor-based wake) + ACRA
276+
// (crash reporting via the relay's `POST /crash`) and zero firebase-*
277+
// artifacts. Both distributions sit behind the same distribution-agnostic
278+
// Koin seams (BackgroundDelivery, CrashReporter, device-identity providers —
279+
// see app/src/{google,foss}).
280+
if (!googleBuild) {
283281
implementation(libs.unifiedpush.connector)
284282
implementation(libs.acra.core)
285283
implementation(libs.acra.http)
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
First F-Droid release of the FOSS build.
2-
3-
Wakes via UnifiedPush (no Firebase or Play Services), keeps an on-device audit
4-
log of every tool call, and secures each session with a hardware-backed keypair.
5-
On-demand MCP server with per-integration opt-in.
1+
FOSS build: wakes via UnifiedPush (no Firebase or Play Services), keeps an
2+
on-device audit log of every tool call, and secures each session with a
3+
hardware-backed keypair. On-demand MCP server with per-integration opt-in.
64

75
Also: while a UnifiedPush distributor is connecting, Home now shows a neutral
86
"Finishing delivery setup…" instead of a false "wake is off".
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
First F-Droid release of the FOSS build.
2+
3+
Wakes via UnifiedPush (no Firebase or Play Services), keeps an on-device audit
4+
log of every tool call, and secures each session with a hardware-backed keypair.
5+
On-demand MCP server with per-integration opt-in.
6+
7+
No user-facing changes since 1.0.6 — this release is a build-internal change
8+
that makes the FOSS build compile with zero Firebase/Google-services code.

fdroid/com.rousecontext.yml

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,31 @@ Binaries: https://github.com/Monkopedia/rouse-context/releases/download/v%v/rous
7575
AllowedAPKSigningKeys: 7cc8d2d568eb3d20a5e190e77baa97b3bde80782dd2576f29088a16c4ce47850
7676

7777
Builds:
78-
- versionName: 1.0.6
79-
versionCode: 7
80-
# Build from the signed release tag. `v1.0.6` does not exist yet; it is
78+
- versionName: 1.0.7
79+
versionCode: 8
80+
# Build from the signed release tag. `v1.0.7` does not exist yet; it is
8181
# created when the owner cuts the release. Until then this is a template.
8282
#
8383
# IMPORTANT — when copying this to fdroiddata, pin `commit:` to the IMMUTABLE
8484
# release SHA, NOT the tag:
85-
# git rev-parse v1.0.6^{commit}
85+
# git rev-parse v1.0.7^{commit}
8686
# and put that full 40-char SHA here. This matches the proven
8787
# com.monkopedia.healthdisconnect recipe, whose published Builds entry pins a
8888
# full commit SHA rather than a movable tag ref. A SHA can't be retargeted
8989
# after submission, so the from-source build is locked to exactly the bytes
9090
# F-Droid reviewed.
91-
commit: v1.0.6
91+
commit: v1.0.7
9292
subdir: app
93+
# Remove the Google-only Gradle config BEFORE F-Droid scans/builds. All
94+
# Firebase / Google-services build coordinates (the two Gradle plugins, the
95+
# Crashlytics config, and the firebase-* dependencies) live exclusively in
96+
# gradle/google-services.gradle.kts, which the app build applies ONLY under
97+
# `-Pgoogle`. The bare FOSS build (what F-Droid runs) never applies it, so
98+
# deleting it here is safe and leaves ZERO Firebase coordinates for the
99+
# source scanner to see — no `scanignore` needed (the F-Droid maintainer
100+
# asked us to remove the non-FOSS deps in prebuild rather than allow-list
101+
# them). See app/build.gradle.kts (`if (googleBuild) { apply(from = ...) }`).
102+
prebuild: rm -f gradle/google-services.gradle.kts
93103
# No flavor: the bare build is FOSS. The FOSS/Google split is a `-Pgoogle`
94104
# project property, not a product flavor, so the default `assembleRelease`
95105
# (which `gradle: [yes]` invokes) is exactly the FOSS variant. The no-flavor
@@ -125,10 +135,12 @@ Builds:
125135
# (no `-Pgoogle`, no `-Pdomain`), so this is satisfied today — just don't
126136
# regress it.
127137
#
128-
# Reproducibility recon (issue #465) found nothing the scanner flags beyond
129-
# the standard gradle/wrapper/gradle-wrapper.jar, which F-Droid allow-lists.
130-
# No `scandelete` / `rm` is required. If a future fdroidserver scanner run
131-
# disagrees, prefer `scandelete` over disabling the scan.
138+
# The only non-FOSS coordinates in the tree are the Firebase plugins/deps in
139+
# gradle/google-services.gradle.kts, which `prebuild:` deletes above before
140+
# the scanner runs. Everything else the scanner might flag is the standard
141+
# gradle/wrapper/gradle-wrapper.jar, which F-Droid allow-lists. No
142+
# `scanignore` / `scandelete` is used — the maintainer asked us to remove the
143+
# non-FOSS deps in prebuild instead of allow-listing them.
132144

133145
MaintainerNotes: |-
134146
DRAFT recipe staged inside the upstream repo (fdroid/com.rousecontext.yml).
@@ -156,10 +168,37 @@ MaintainerNotes: |-
156168
be built with a bare assembleRelease (no stray -P flags) so it matches
157169
fdroidserver's bare build; release.yml already does this.
158170
171+
Firebase-free FOSS build (extract + prebuild-remove, NOT scanignore):
172+
* ALL Firebase / Google-services build config — the google-services and
173+
firebase-crashlytics Gradle plugins, the Crashlytics per-build-type
174+
setup, and the firebase-* runtime dependencies — is extracted into
175+
gradle/google-services.gradle.kts. app/build.gradle.kts applies that file
176+
ONLY under `-Pgoogle` (`if (googleBuild) { apply(from = ...) }`). The
177+
bare FOSS build never applies it, so it compiles ZERO Firebase: no
178+
plugins on the classpath, no firebase-* deps, no Crashlytics config, and
179+
google-services.json is not required to assemble it.
180+
* Because every non-FOSS coordinate lives in that one extracted file, the
181+
recipe's `prebuild: rm -f gradle/google-services.gradle.kts` deletes it
182+
before F-Droid's source scanner runs, leaving nothing for the scanner to
183+
flag. This replaces the earlier scanignore-free-but-inline approach: the
184+
maintainer asked us to remove the non-FOSS deps in prebuild rather than
185+
allow-list them, and this does exactly that with no `scanignore` /
186+
`scandelete`.
187+
* Verified: `aapt2 dump strings` on the FOSS app-release APK shows no
188+
firebase/gms strings, and the DEX contains no com.google.firebase /
189+
com.google.android.gms classes. (The wire field name `firebaseToken` and
190+
UnifiedPush's own org.unifiedpush...FirebaseReceiver are distribution-
191+
agnostic / FOSS and unrelated to Google Firebase.)
192+
* The extracted file lives in the repo-root gradle/ dir (not app/) so that
193+
Android Lint's build-script visitor (lintVitalRelease, which the FOSS
194+
release build runs) does not try to FIR-resolve a standalone
195+
apply(from=...) script inside an Android module and crash. The root
196+
project has no Android lint task.
197+
159198
Variant facts (do not regress):
160199
* Bare build = FOSS (UnifiedPush + ACRA, no Firebase/GMS). `-Pgoogle` is
161200
the Firebase build and must NOT be used for F-Droid.
162-
* Toolchain at v1.0.6: AGP 8.13.2, Kotlin 2.2.21, JDK 21, compileSdk 36,
201+
* Toolchain at v1.0.7: AGP 8.13.2, Kotlin 2.2.21, JDK 21, compileSdk 36,
163202
minSdk 24, targetSdk 35, Gradle 8.14.3.
164203
* FOSS-only runtime deps: UnifiedPush connector 2.5.0, ACRA 5.13.1.
165204
@@ -168,5 +207,5 @@ MaintainerNotes: |-
168207
169208
AutoUpdateMode: Version
170209
UpdateCheckMode: Tags ^v[0-9.]+$
171-
CurrentVersion: 1.0.6
172-
CurrentVersionCode: 7
210+
CurrentVersion: 1.0.7
211+
CurrentVersionCode: 8

gradle/google-services.gradle.kts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// =============================================================================
2+
// Google/Firebase-only Gradle configuration — applied ONLY under `-Pgoogle`.
3+
// =============================================================================
4+
//
5+
// This file holds EVERY Firebase / Google-services build coordinate: the two
6+
// Gradle plugins, the Crashlytics per-build-type config, and the Firebase
7+
// runtime dependencies. It is applied from app/build.gradle.kts exclusively
8+
// when the `-Pgoogle` credentialed build is selected:
9+
//
10+
// if (googleBuild) { apply(from = "$rootDir/gradle/google-services.gradle.kts") }
11+
//
12+
// The bare FOSS build never applies it, so the FOSS build compiles ZERO
13+
// Firebase/Google-services: no plugins, no deps, no Crashlytics config.
14+
//
15+
// It lives in the repo-root gradle/ dir (not app/) on purpose: Android Lint's
16+
// build-script visitor (lintVitalRelease) crashes trying to build a FIR
17+
// light-class for a standalone apply(from=...) script inside an Android module.
18+
// The root project has no Android lint task, so keeping it here sidesteps that
19+
// upstream crash while the FOSS release build (which runs lintVitalRelease)
20+
// stays green — it never applies this file.
21+
//
22+
// F-Droid: the FOSS build never needs this file, and the fdroiddata recipe
23+
// removes it in `prebuild:` (rm -f gradle/google-services.gradle.kts) BEFORE the
24+
// source scanner runs, so the scanner never sees any Firebase coordinate. This
25+
// is why the coordinates below are written as explicit Maven strings rather
26+
// than `libs.` version-catalog accessors — version-catalog type-safe accessors
27+
// are NOT available inside an `apply(from = ...)` script, and keeping the
28+
// coordinates here (not in libs.versions.toml usage within a surviving build
29+
// file) is what makes the FOSS build genuinely, statically Firebase-free.
30+
//
31+
// Versions are mirrored from gradle/libs.versions.toml — keep them in sync:
32+
// * firebase-bom = 34.12.0 (all firebase-* artifacts are versioned by the BOM)
33+
// * firebase-auth / firebase-messaging / firebase-crashlytics (no explicit
34+
// version; resolved by the BOM platform above)
35+
// The plugin versions (google-services 4.4.4, firebase-crashlytics 3.0.6) are
36+
// declared `apply false` in the root build.gradle.kts so the `apply(plugin=...)`
37+
// calls below can resolve them.
38+
// =============================================================================
39+
40+
apply(plugin = "com.google.gms.google-services")
41+
apply(plugin = "com.google.firebase.crashlytics")
42+
43+
// Crashlytics per-build-type config, reaching into the already-configured
44+
// android extension. Mirrors the guarded inline config that previously lived in
45+
// app/build.gradle.kts `buildTypes { debug { ... }; release { ... } }`.
46+
//
47+
// An `apply(from = ...)` script does NOT have the AGP / firebase-crashlytics
48+
// Gradle plugin types on its compile classpath (unlike the main build file), so
49+
// the typed `configure<ApplicationExtension>` / `configure<CrashlyticsExtension>`
50+
// DSL does not resolve here. We instead reach the build types dynamically and
51+
// set the two upload toggles by reflection, which stays type-agnostic.
52+
//
53+
// * debug — no mapping/symbol upload (keep debug assembly fast).
54+
// * release — upload the R8 mapping so Crashlytics deobfuscates stacks; no
55+
// NDK code, so native symbol upload stays off.
56+
// The firebase-crashlytics plugin registers a `firebaseCrashlytics` extension
57+
// (CRASHLYTICS_EXTENSION_NAME) on every build type (each build type is
58+
// ExtensionAware). We toggle mapping/symbol upload per build type via that
59+
// named extension.
60+
val crashlyticsMappingUpload = mapOf("debug" to false, "release" to true)
61+
val androidExt = extensions.getByName("android")
62+
@Suppress("UNCHECKED_CAST")
63+
val buildTypes = androidExt.javaClass
64+
.getMethod("getBuildTypes")
65+
.invoke(androidExt) as org.gradle.api.NamedDomainObjectContainer<Any>
66+
crashlyticsMappingUpload.forEach { (buildTypeName, uploadMapping) ->
67+
val buildType = buildTypes.getByName(buildTypeName) as org.gradle.api.plugins.ExtensionAware
68+
val crashlytics = buildType.extensions.getByName("firebaseCrashlytics")
69+
// CrashlyticsExtension exposes boxed-Boolean setters
70+
// (set{Mapping,NativeSymbol}UploadEnabled(java.lang.Boolean)).
71+
crashlytics.javaClass
72+
.getMethod("setMappingFileUploadEnabled", java.lang.Boolean::class.java)
73+
.invoke(crashlytics, uploadMapping)
74+
crashlytics.javaClass
75+
.getMethod("setNativeSymbolUploadEnabled", java.lang.Boolean::class.java)
76+
.invoke(crashlytics, false)
77+
}
78+
79+
dependencies {
80+
// FCM wake + Firebase anonymous auth + Crashlytics. Only the `-Pgoogle`
81+
// build links these; the FOSS build uses UnifiedPush + ACRA instead.
82+
add("implementation", platform("com.google.firebase:firebase-bom:34.12.0"))
83+
add("implementation", "com.google.firebase:firebase-auth")
84+
add("implementation", "com.google.firebase:firebase-messaging")
85+
add("implementation", "com.google.firebase:firebase-crashlytics")
86+
}

0 commit comments

Comments
 (0)