How does the gradle daemon manage classpath dependencies?

Last time I asked about this, I was taught about build services. Since then, I’ve started using build services everywhere and am very familiar with them.

But now I’m considering if it may be possible to optimize performance by using static properties, even though previously I had issues. Perhaps, I’m thinking, if I understand completely how static stuff is used by the gradle daemon I can optimize something.

So say I have a jar with a static property. Then say in my settings.gradle.kts script, I add that jar as a classpath dependency in the buildscript block. Then later in some build.gradle.kts script, I set the value of this property.

My understanding (which may be wrong) is that the memory for this property is within the gradle daemon process. Therefore, if the gradle daemon does not restart and I run a new gradle command, the static property will still be there.

But what exactly happens then if I modify that jar in between gradle runs? What if I change the type of that property or remove it all together? Will that lead to a runtime error? When, if ever, is it safe to have static properties in gradle?

When we add a classpath dependency to the buildscript block in build.gradle.kts, my understanding is that the gradle daemon itself is changing its own classpath. Is that accurate? Does the gradle daemon change its own classpath during each gradle run, adding the classpath dependencies in the buildscript block and then perhaps removing those again when the gradle command finishes?

I know I can test these things. But even with testing, I’m sure I’ll still have an incomplete understanding. I’m hoping someone here might be able to educate me or point me towards where I could start to get a more complete understanding of how the gradle daemon manages its own classpath dependencies.

The daemon does not modify its own classpath.
It even cannot do that in a clean and portable way, just with JVM implmentation and version specific hacks iirc.

It uses a deep hierarchy of class loaders for build scripts, where a build script’s class loader has the parent project’s class loader as parent and so on up to the root project which has the buildSrc class loader as parent, which has the settings script class loader as parent, which has the init script class loader as parent and so on. While legacy script plugins on the other hand are somewhat outside the hiearchy and are special with also added quite some quirks which is why they are discouraged.

But there is also more to it, because not everything is done in the daemon, some things are done in worker processes that might be reused and so on.

The classes that are actually in the build script class loaders could maybe be used with static state, I have no idea whether those are reused, but it could as well be the case.

In the case of worker processes it is definitely the case that they are reused if possible because the versions did not change for example.
So if you for example use some tool directly in-process that uses static state, you easily get into trouble like Multiple runs and even builds for different projects influence each other · Issue #113 · spotbugs/spotbugs-gradle-plugin · GitHub where even Spotbugs runs of different projects did influence each other and changed outcome due to that static state.

So to cut it short, just don’t use static state and save you a lot of trouble that can even be extremely subtle and hard to find. A build service is properly scoped to one build execution no matter what happens to the daemon or any worker process and thus is the best way for sharing such state within one build.

1 Like

Thanks, I half expected I might get an answer like this. I guess its probably best to stick with build services.

Its really useful to know about the hierarchy of class loaders and having that bit of insight might help me diagnose some problems that come up in the future. But I will definitely try to take your advice and avoid anything static.

1 Like