Completes the crash-interrupted #192 WIP (app/build.gradle.kts already had a jacocoTestReport task and toolVersion pin recovered onto build-192-jacoco): - Move the JaCoCo tool version into gradle/libs.versions.toml instead of a hardcoded string in app/build.gradle.kts, matching how every other plugin version in this repo is sourced. - Fix the generated-code exclusion list against the real compileDebugKotlin output (verified by inspecting the compiled class tree): Room's KSP-generated `_Impl` DAOs/database and the Compose compiler's per-file ComposableSingletons holders are the only generated code that actually lands in classDirectories, since Hilt/Dagger's generated Java and AGP's BuildConfig/R/Manifest are compiled by a separate javac task this report never reads. Drop the blanket `**/*$$*` exclude the WIP had — it was silently discarding ~200 real classes' worth of coverage on Kotlin's own `$$inlined$` synthetic classes (e.g. Flow.map { ... } transforms in the repositories), which is hand-written logic, not generated boilerplate. - Add Hilt_*/Dagger* prefix patterns so the (currently inert, belt-and-suspenders) Hilt exclusions are actually correct if the classDirectories scope ever changes. - Add a minimal CI step to the existing unit-tests job that runs jacocoTestReport and uploads the XML+HTML report as a build artifact. No coverage threshold gate yet (a jacocoTestCoverageVerification rule is a natural follow-up once there's a baseline). - Document the new :app:jacocoTestReport task in CLAUDE.md. Verified on JDK 21: fast gate (assembleDebug, testDebugUnitTest, compileDebugAndroidTestKotlin, lintDebug, ktlintCheck, detekt) plus jacocoTestReport all pass, from both a warm and a `clean` build. The report shows real signal (30% instruction / 38% line coverage) with no generated classes leaking in. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
LibreMail is a GPL-3.0 Android email client (Kotlin, Jetpack Compose, Material 3).
See @README.md for OAuth/account setup (Gmail, Outlook), the tech stack, and the
offline-first architecture — this file only covers what isn't obvious from the code.
Build, test, lint
Use a JDK 17–21 for the Gradle daemon. AGP 9.2 does not support JDK 25 — if
JAVA_HOME points at 25+, builds fail. Commands (PowerShell: use .\gradlew):
./gradlew :app:assembleDebug # build debug APK
./gradlew :app:testDebugUnitTest # JVM unit tests
./gradlew :app:lintDebug # Android lint
./gradlew :app:ktlintCheck :app:detekt # static analysis (CI's "Static analysis" gate)
./gradlew :app:jacocoTestReport # JVM unit-test coverage (XML+HTML under app/build/reports/jacoco/)
# single unit test:
./gradlew :app:testDebugUnitTest --tests "org.libremail.data.SomeClassTest"
E2E/instrumented tests need a booted emulator: ./gradlew :app:connectedDebugAndroidTest,
or via Gradle Managed Devices ./gradlew e2eGroupDebugAndroidTest (whole matrix) /
./gradlew api29DebugAndroidTest (one API level). The managed-device list in
app/build.gradle.kts must stay in lockstep with the E2E matrix in .github/workflows/ci.yml.
Before treating a change as done, run the fast CI gate: assembleDebug +
testDebugUnitTest + compileDebugAndroidTestKotlin + lintDebug + ktlintCheck +
detekt (the /preflight skill does this). compileDebugAndroidTestKotlin compiles the
androidTest source set that the rest of the gate skips, catching E2E/instrumented-test
compile errors before they surface only in CI. ktlintCheck/detekt cover the
test/androidTest source sets that lintDebug skips, so they catch style violations that
would otherwise fail CI's Static analysis gate. Emulator E2E is left to CI unless asked.
Build-config gotchas
- Built-in Kotlin (AGP 9.x). Kotlin compilation is handled by AGP's built-in Kotlin;
the Kotlin version (2.4.0) is pinned via the root
build.gradle.ktsbuildscript classpath. Never apply theorg.jetbrains.kotlin.androidplugin — it throws a ClassCastException against AGP 9's DSL. (Thekotlin-androidalias inlibs.versions.tomlexists but must not be used.)libs.versions.tomlstill supplies all library versions. - KSP, not KAPT for all annotation processing (Hilt, Room).
- Room schemas are exported to
app/schemasand validated by migration tests — commit schema changes. - OAuth client IDs come from
secrets.properties(git-ignored) viaBuildConfig; the build works without it (empty/placeholder values).
Code conventions
- Sources live under
app/src/{main,test,androidTest}/kotlin/; package rootorg.libremail(applicationIdorg.libremail.app). - Every source file starts with
// SPDX-License-Identifier: GPL-3.0-or-later(or the<!-- ... -->form for XML/Markdown). All 117 current.ktfiles follow this. kotlin.code.style=official.
Testing
JVM unit tests use JUnit4 + kotlin.test, Turbine for Flow, MockK for mocks,
GreenMail for a real in-process IMAP/SMTP server, and coroutines-test. org.json is
pulled in as a real dependency for unit tests because android.jar's version is a no-op stub.
Repo etiquette
- Branch off
main; branch names likefeat-…/fix-…. PRs targetmainand must pass theCI passedgate. - Conventional Commits for commit subjects and PR titles:
type(scope): summary(feat,fix,chore, …), matching existing history.