Making AI Review Your Gradle Build — The Unglamorous Win
Build files are the least-reviewed, highest-impact code in an Android repo. Auditing them is a near-perfect task for AI: dense, declarative, and full of implications nobody memorises. The audit prompt, the five findings it produces most often, and how to verify.
Hessam Rastegari
Senior Android Developer · 12 years shipping Android
TL;DR — build.gradle.kts determines how fast every engineer's day is, how large your APK ships,
and whether builds are reproducible — and it gets a fraction of the review attention a ViewModel gets.
Auditing it is an unusually good fit for AI: the work requires knowing what a few hundred flags do, not
cleverness. Ask for concrete effects, then verify with a build scan. Never apply a build change you
haven't timed.
Why build files go unreviewed
Three reasons, all cultural:
- Nobody feels qualified. Gradle knowledge is unevenly distributed, so reviewers skim the build file and comment on the Kotlin.
- It works. A build file that produces a working APK looks correct. Slow, bloated, and non-reproducible all look identical to correct.
- The feedback loop is long. A flag that disables an optimisation costs you 20 seconds per build forever, which nobody attributes to that line.
Meanwhile it's leveraged code: one line affects every engineer, every build, every release.
Why AI is well suited to it
This isn't a task requiring insight. It requires knowing what isMinifyEnabled,
proguard-android-optimize.txt, api vs implementation, kapt vs ksp,
org.gradle.configuration-cache, and two hundred other switches actually do — and noticing when a
combination is contradictory.
That's boring breadth. It's precisely what a model has and a human doesn't hold in working memory, and unlike application code there's no product context it's missing.
The audit prompt
Here are my build.gradle.kts (app + one library module), gradle.properties and
libs.versions.toml.
Point out:
1. Configuration that silently disables an optimisation.
2. Dependencies declared with the wrong configuration (api vs implementation,
compileOnly, debugImplementation).
3. Anything that breaks the configuration cache or build cache.
4. Anything non-deterministic across machines or across time.
5. Deprecated or superseded setups (kapt where KSP exists, old plugin DSL).
For each finding: the concrete effect (size, build time, correctness),
and how I'd verify it. Rank by impact. Mark anything you're unsure about.
The last two lines matter as much as the list. "Rank by impact" prevents a flat wall of nitpicks, and "mark anything you're unsure about" surfaces the guesses — Gradle has enough version-specific behaviour that a confident wrong answer is easy to produce.
What it finds most often
proguard-android.txt instead of proguard-android-optimize.txt. The non-optimising default
turns off R8's optimisation pass. Everything still works, the APK is bigger, startup is slower, and
this has been true silently for years. The single most common finding.
api where implementation belongs.
dependencies {
api(libs.retrofit) // leaks Retrofit onto every consumer's compile classpath
implementation(libs.okhttp) // correct: an internal detail
}
api puts the dependency on the compile classpath of every module that depends on you, so a Retrofit
version bump recompiles the world. In a 40-module project this is one of the largest available
incremental-build wins.
Non-deterministic version codes.
versionCode = (System.currentTimeMillis() / 1000).toInt() // every build differs
Reproducible builds become impossible and every cache lookup misses. Derive it from a tag or a CI build number instead.
Environment-dependent config with no default.
buildConfigField("String", "API_URL", "\"${System.getenv("API_URL")}\"")
Works on CI, produces "null" for every new hire on their first day. A default with a clear warning
costs one line.
kapt left behind after a KSP migration. One remaining kapt dependency keeps the entire kapt
stub-generation round alive, so you pay the cost you thought you'd removed.
Verify — always
A model can tell you a flag is wrong; it cannot tell you what it's worth on your build. Measure:
./gradlew --profile clean :app:assembleRelease
./gradlew :app:assembleDebug --scan
Change one thing at a time, and compare against a baseline you captured first. Build changes are unusually easy to convince yourself about — the build "feels" faster the moment you've spent an hour on it.
For APK size, bundletool or the APK Analyzer gives you a before/after that isn't a feeling.
The limits
It will occasionally recommend flags that were removed in your Gradle version, or repeat advice that
was true in 2021. It has no idea that your api declaration is deliberate because three modules
genuinely need that transitive type. Treat findings as leads with stated effects — which is exactly
why the prompt demands the effect and a verification method rather than a verdict.
The habit
Run this audit once a quarter, and whenever you upgrade AGP or Gradle. It takes ten minutes, it's the cheapest performance work available, and it targets the one file in your repository that affects everyone on the team every single day.