Is there official documentation/recommendation aro...
# community-support
c
Is there official documentation/recommendation around having all plugins used by a project defined/declared in a root build.gradle file with apply false? I know there's issues that the kotlin plugin warns about https://www.reddit.com/r/reactnative/comments/1262ry6/the_kotlin_gradle_plugin_was_loaded_multiple/ and the build services https://github.com/gradle/gradle/issues/17559#issuecomment-2549571022 (as well as the plugin classloader hierarchy rework https://github.com/gradle/gradle/issues/25616)
šŸ‘€ 1
In this case I assume 'root apply false' to be interchangeable with buildSrc/includeBuilds. Some shared way to make sure all plugins are shared.
Or perhaps they're not interchangeable? Would a settings plugin loading all plugin versions be a preferable way to load plugins vs apply false in the root build.gradle.kts?
t
the included plugin builds is how most of my stuff ends up on the classpath. then its just making sure its the versions I want that get used there. For the most part trivial once everything was using version catalog. Only a handful of others have had to go into the root with apply false. and its just like you say making sure it only gets loaded once
āž• 1
v
There is no "official recommentation" I'm aware of. Probably because it depends on each individual build. If a plugin is only working in the project it is applied to and not for example communicating with the same plugin applied to other projects, for example using a shared build service, then it is for example not an issue if the plugin in sibling project A is loaded on a different classloader and thus being different classes than the plugin in sibling project B which is loaded on a different classloader. If a plugin does such communication, you have to ensure that both projects use it from the same classloader. How you do that is not relevant. There is a hierarchy of classloaders and you just have to make sure it is on a common parent. The root project buildscript classpath class loader (apply false ...) is one way for example as it is a parent classloader of the subproject's one. Adding a dependency on a plugin in
buildSrc
is another way as that lands on a parent classloader of the root project's one. Adding the plugin with "apply false" to the settings script would be another way, as it is a parent classloader of the
buildSrc
classloader iirc. What effect depending on the plugin in an included build has, depends on where / how / and if you use that included build somewhere like applying a plugin from it. Another reason for such actions could be version conflicts. Because only stuff on the same classpath undergoes conflict resolution. But stuff in parent classloaders wins over stuff in child classloaders by definition. So if you for example • apply plugin A in the root project • apply plugin B in the subproject • A needs C v1 • B needs C v2 and cannot work with v1 Then B will fail as it sees C v1 from the parent class loader. Here again either depending on C v2 in the root buildscript or right away plugin B would fix it (as long as A can work with C v2) as then conflict resolution happens betwenn C v1 and C v2. But you can also get into trouble the other way around. If you for example have • plugin A in the settings script classpath classloader • plugin B in the root project classpath classloader • plugin A has some code like
pluginManager.withPlugin("B") { configure<ExtensionOfA> { ... } }
, then this will again fail, because A reacts to B being applied by its String ID like it is recommended by Gradle folks, but then when it tries to use the extension class of A it fails, because that class is on the root project classloader which is a child classloader and cannot be seen by the settings classloader. So if you had a
compileOnly
dependency on B which most often makes sense with such code, you get a class not found exception. If you badly had an
implementation
dependency, you get a class cast exception stating that you cannot cast
ExtensionOfA
to
ExtensionOfA
, because it gets an instance of the class on the root project classloader and tries to cast it to the class on the settings classloader. ....... What I usually do is not to try to prematurely fix an issue that I might not have. I try to write the build nicely, and if I have a problem which requires that I put things on common classloaders, then I do it. (If I did not anyway put the build logic in an included build that depends on all plugins and thus resolved the problem at the root already šŸ™‚)
c
Thanks for all that @Vampire. I wouldn't normally be worrying about this, but we've started combining projects we've made over the past 7 or so years into a mono-repo. Meaning where there was once only a chance of a small split classloader has now turned into a 400 subproject monstrosity that can blow up a gradle daemon with max metaspace set to 2g 😭
Fwiw we're not regularly sharing build services (although I think kotlin has at least a shared compiler daemon). We're not using buildSrc/includeBuild rn but certainly could if it made version management easier.
I've written a small gradle project to calculate plugin divergence. I'm just worried that loading classes across 400 subprojects is exploding the total number of classes loaded/parsed.
v
Yeah, well, monorepos are imho seldomly a good idea, unless you really want to branch and tag all together as you release them all at the same time and with the same version. If not then imho it has nothing to do in the same repository. And even with a big fat monorepo, does not necessarily mean you have to have all in one build. šŸ¤·ā€ā™‚ļø
šŸ‘ 1
c
@Vampire I'm learning this the hard way šŸ˜‰
šŸ‘Œ 1
I'm trying to learn the gradle profiler rn, seeing if detecting plugin classloader divergence is actually the cause for our metaspace memory issues. If so then it might be helpful beyond just our company.
šŸ‘Œ 1