I'm continuing my forray into making my companies ...
# community-support
c
I'm continuing my forray into making my companies gradle build faster. Right now the configuration phase seems to be the most problematic. We have a fairly simple app in my opinion but the complexity comes into play (i think) from the fact that we have a buildSrc to make convention plugins so that each module added to our app gets all of the typical dependencies and configuration (its an android app). it kills me when i make a change and then see gradle configuring for like 12 seconds on such a simple app. Recently ive been trying to resolve this by giving claude a goal to keep iterating until it finds a meaningful improvement. I initially thought going from buildSrc to build-logic would do the trick but it ended up with worse perf. Seemingly the only change that meaningfully improved things was converiting to a binary kotlin plugin away from *.gradle.kts. After all of this experimentation, im sitll left sorta banging my head against a wall with the question of "if i have two android library modules in my app, what's the BEST way to share a standard configuration (api level, etc) and set of dependencies between them?" I know a lot of answers in software are "it depends" but surely theres like a 99th percentile answer that would be the go-to way for each app, right?
j
i would say
build-logic
is the 99th percentile you speak of. if making structural changes to the project is something you want to optimize for, then yeah, putting your logic into a precompiled plugin is probably the way to go.
you can also use the build scan to see what is taking up the most time in that 12 sec... or even the gradle profiler and jfr to get a flamegraph to see where the time is being spent
c
precompiled plugin meaning something that lives outside the repo and consuming it like you would any other 3rd party plugin? i just feel like it so crazy how I can do file > new project. create two android lib modules. the build.gradle.kts is identical between the two. and the build is fast. but then if i want to DRY the gradle.kts I just add an overhead of a few seconds.
i just feel like this conf talk lied to me so hard. lol
j
when you are getting the configuration cache miss (12 seconds configuration), what is the included build
build-logic
doing? are all of its tasks UP-TO-DATE or FROM-CACHE?
it might not be the existence of the build-logic convention plugin, but what that plugin is doing when run. that where the build scan comes in handy
c
for example. i hit sync in the IDE after just completing a succesful sync and it took like 1 second. Awesome. works as expected.
Copy code
> Task :buildSrc:checkKotlinGradlePluginConfigurationErrors SKIPPED
> Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
> Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
> Task :buildSrc:compilePluginsBlocks UP-TO-DATE
> Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
> Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
> Task :buildSrc:compileKotlin UP-TO-DATE
> Task :buildSrc:compileJava NO-SOURCE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE

> Configure project :app

Local build

> Task :prepareKotlinBuildScriptModel UP-TO-DATE
and then I updated my android app version from 1.1 to 1.2 and hit sync and it took 33 seconds. lol
Copy code
> Task :buildSrc:checkKotlinGradlePluginConfigurationErrors SKIPPED
> Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
> Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
> Task :buildSrc:compilePluginsBlocks UP-TO-DATE
> Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
> Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
> Task :buildSrc:compileKotlin UP-TO-DATE
> Task :buildSrc:compileJava NO-SOURCE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE

> Configure project :app

Local build

> Task :prepareKotlinBuildScriptModel UP-TO-DATE

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

BUILD SUCCESSFUL in 29s
9 actionable tasks: 9 up-to-date
j
all of the buildSrc tasks are up to date
so that is not what is taking a long time
you have configuration cache on right?
c
yep. its on and cache.parallel is on too. but good point on the up to date stuff. Let me look into this more. there has to be something thats the culprit here.
j
so this comes down to a CC miss vs a CC hit. so on the CC miss, its running all of the plugins and scripts again. so a build scan will tell you which scripts or plugins are taking the most time
c
thanks. im going to try that or that new devlocity ide plugin.
👍 1
i always have a hard time with the build scan web output honestly. esp since its something that gets published to the "public web"
j
valid point ^ you can use --profile, but its much less granular. https://github.com/gradle/gradle-profiler is much MORE granular. you can use it with jfr and open the data with java mission control. this gives full cpu profiling at the method level
but build scan is the best place to start imo
c
gotcha. lets see if theres any smoking gun from develocity plugin.
caved and ended up doing the publish to web since the plugin didn't really help locally.
i updated a bunch of plugins. but just hitting the sync button still takes a few seconds. idk why execution is included in this. i thought for sync it would only configure?
v
esp since its something that gets published to the "public web"
Yeah, well, if that is an issue, you can buy a Develocity license and so have your build scans private and also much more functionality like comparing two build scans and so on. 🙂
Seemingly the only change that meaningfully improved things was converiting to a binary kotlin plugin away from *.gradle.kts.
Yes. If you are concerned about non-CC-hit builds, not having precompiled Kotlin DSL script plugins but only Groovy DSL precompiled script plugins, or even only normal plugins written in any JVM language (be it Groovy, Kotlin, Java, or whatever other language you prefer) is indeed a performance push. Because for precompiled Kotlin DSL script plugins, you for example always have the extra step to generate the accessors for the script plugins which can also be significant time as it needs to apply all the plugins from the
plugins { ... }
block to find out which accessors to generate to then compile the actual script plugins.
Whether you have
buildSrc
or an included
build-logic
build should not have a significant performance impact either way. Since Gradle 8 the
buildSrc
is extremely similar to an included build and only different in slight but slippy details.
j
in the build scan, you want to look in the performance section on the left, then the configuration tab
once you have a scan of your cache miss
v
idk why execution is included in this. i thought for sync it would only configure?
The main build, yes. But the build logic still needs to be built of course, so I guess the execution comes from there. But the details sections should tell you.
j
just a note, precompiled plugin does not need to live in a separate repo. it also can exist in a build-logic project
v
Yes, who did suggest anything different? o_O
j
earlier in the thread, @Colton Idle asked "precompiled plugin meaning something that lives outside the repo and consuming it like you would any other 3rd party plugin?"
c
interesting. i wonder if theres any issue with me moving to a precompiled plugin in the same repo in build-logic project
v
Ah, sorry. I think terms were maybe a bit shuffled there. precompiled Kotlin DSL script plugin =>
*.gradle.kts
file precompiled Groovy DSL script plugin =>
*.gradle
file And I think what Colton meant was anything that is published, so is (pre-)compiled once at the time it is published. With something that is built separately, published, and then just consumed like any other plugin you of course gain the most performance. But from all things that live in
buildSrc
or a included
build-logic
build I think the only significant performance drain is, if you use precompiled Kotlin DSL script plugins as the accessors need to be generated for them. Everything else is probably not so much performance relevant. But like always with performance, don't trust any statistics you didn't falsify yourself, but measure your concrete case and options. precompiled Kotlin DSL script plugins are basically a tradeoff off performance on recompilation against syntactic sugar.
j
Ah yes. I forgot precompiled usually means script. "Binary" is the term for a plugin written in java/Kotlin and packaged as a library I think
v
Actually a script plugin is only syntactic sugar over writing a normal .kt or .groovy plugin class and after compilation it should be identical to a binary plugin or "be" a binary plugin. But yeah, sometimes those are called "binary plugins". Even though I somehow disagree to that, as does the Gradle docs. The Gradle docs at docs.gradle.org/current/userguide/implementing_gradle_plugins_binary.html say
Binary plugins refer to plugins that are compiled and distributed as JAR files.
So a plugin you have as
.kt
file but in
buildSrc
or
build-logic
is not distributed as JAR file, so is not a binary plugin as per that definition. And a plugin you have as
.gradle.kts
but in a dedicated plugin project that you build a jar from and publish to a repository falls under that definition, so actually is a binary plugin. 🙂
I would probably call them "conventional plugin" but that is too near to "convention plugin". So maybe "classical plugin".
Or how about vintage plugin? 😄
Or maybe better "regular plugin" as all other terms indicate some deprecation that is not true
Yeah, I think that's how I usually call them. Brain is off right now 😄
c
lmao. all this makes my head hurt. i'm going to convert buildSrc to build-logic and then set a bunch of agents out to use gradle profiler to see if it can improve the 10-30 second sync time using a /goal
¯\_(ツ)_/¯
v
Yeah, and make the precompiled Kotlin DSL script plugins regular plugins, then measure again, maybe that is already the relevant point. 🤷‍♂️
c
thanks all! if i only had more time in the day id love to get more nitty gritty with gradle. lol. i feel like i know 0 about it, but somehow ive gotten stuck as the build expert on my team. lmao
v
Never too late to learn 🙂 We're here if you need us. 🙂
❤️ 1
j
i feel like i know 0 about it, but somehow ive gotten stuck as the build expert on my team. lmao
this is how it happens...
😅 1