Ok, I have a stumper. I have a convention plugin. ...
# community-support
j
Ok, I have a stumper. I have a convention plugin. When another (specific) plugin is applied, I want to configure that plugin. But I don't want to constrain the version of that plugin, so I don't want to put it on the classpath of my convention plugin. Is that possible?
Clarification: If I add the other plugin to my classpath then the build fails because you can't specify versions on plugins that are provided that way.
p
You can with 9.1, or don’t use alias but id in 9.0
πŸ‘Ž 1
y
Copy code
project.pluginManager.withPlugin('that.other.plugin.id') {
}
is normally your friend in this case. Also if you want to do that in your plugin's code, then just put the other plugin as a
compileOnly
dependency. It works in most cases.
☝️ 2
πŸ‘† 1
j
I'm on 8.9
I tried compileOnly but I get classloader issues
y
What kind of classloader issues do you get?
j
ClassNotFoundException
Because the class isn't in my classpath
Might have something to do with using the kotlin extension function? let me try with more java-style
m
You'll have to tell your users to load the other plugin in a classloader that is accessible to your plugin
Most of the time, the root project classloader is a good one
j
Like an
apply false
thing at the root?
☝️ 1
m
Yes, that
j
Ok, thanks!
m
The important thing is that both your plugin and the other one must be loaded from the same place
(it's not a strict requirement but I find it easier to just tell everyone to
apply false
or else it becomes confusing real quick)
πŸ™Œ 1
j
ahahaha brilliant
we load the convention plugin at the root but not this other one
easy fix!
πŸ₯³ 1
v
The important thing is that both your plugin and the other one must be loaded from the same place
Not really, the other plugin has to be accessible by the convention plugin. Whether they are loaded in the same class loader or the other plugin in some parent classloader is not really important. :-)
m
This is what I said in the message just below but I'd rather tell everyone to just apply load in a single place than start explaining the nuances of classloader hierarchies in Gradle
πŸ‘ 1
v
Sooner or later they will hit that problem anyway. :-D Because even if they add everything in the root project with "apply false" (which I personally consider bad practice), they still get into trouble when something conflicting is happening for example with the settings classpath class loader or the
buildSrc
dependencies. :-)
m
Yea, they will get one problem at a time πŸ˜…
v
Sometimes one, sometimes multiple πŸ˜„
☝️ 1
y
https://gradle-community.slack.com/archives/CAHSN3LDN/p1754981072652939?thread_ts=1754935180.286129&cid=CAHSN3LDN I disagree with that and it is a personal preference. If everything is setup in the root project and some are with apply false, then: β€’ I know exactly all plugins that are being used across all subprojects. It is a summary. β€’ I don't get surprised by some subprojects having different classpaths (and as such different instances of build services)
m
@ysb33r I think @Vampire point was more about applying to "parents" (buildSrc, settings). Sometimes you'll have to do this. If your build becomes too big or if you need
Settings
features (for isolated projects, this is becoming more and more important)
So it's something you might need at some point but for starters, and given this is the default in sooo many places, I like
apply(false)
in the root buildscript too
I mean technically,
buildSrc
is probably better replaced with
build-logic
and then you would
apply(false)
your build logic so it stays the same
v
His point was about me not liking having all plugins declared in the root buildscript which he disagrees to πŸ™‚
βœ… 1
m
But
Settings
you might need at some point
Too many negations around here, I'm lost
Anyways, one day we'll have nice things: https://github.com/gradle/gradle/issues/1370
v
Ok, once more without negations: I said "I don't like declaring all plugins in root buildscript". He said "I like declaring all plugins in root buildscript". πŸ™‚
πŸ™ 1
m
Yes, which I replied that it's working until you need some of them in
settings.gradle.kts
(which was your point I think)
And there are good reasons to apply in
settings.gradle.kts
But us having this conversation is yet another sign that we need https://github.com/gradle/gradle/issues/1370 hard
v
One of my points, yes. πŸ™‚ But I indeed also don't like declaring all plugins in the root buildscript without technical reason, but that's more a personal thing as he mentioned. πŸ™‚
y
Ja, weiss gegenüber dunkel bier. 😜 Other things said above about complexities of build management hold true.
🍻 1
v
Nope, those are both good πŸ˜„
Having all plugins in the root project can also cause issues instead of fixing them. If you for example have plugin A that is only used in subproject X and plugin B that is only used in subproject Y. Both need the same dependency L but in different versions and neither can work with the version that the other requires. If you just have A in X and B in Y, all works just fine. But if you both move to the root project, the one that cannot work with the newer L will fail. πŸ€·β€β™‚οΈ
So anyway, whatever you do will be wrong in one case or the other. That's why I prefer having the plugins where they are needed and only move them to some parent classloader if there is a technical necessity to do so. πŸ™‚
y
Yes, that is a problem, but that also comes down to plugin authors not applying classpath isolation where possible.
v
Which is not too uncommon, yeah πŸ™‚
m
plugin authors not applying classpath isolation where possible
The amount of boilerplate required to do that is quite daunting
Wouldn't call it boilerplate, but it is quite a load of extra work.
πŸ’― 1
j
What's classpath isolation? (Since y'all are discussing this in my thread πŸ˜†)
πŸ˜„ 1
y
Haha, yes, we're those friends that hijack your conversation and start talking about other stuff
Anyway, classpath isolation is the ability to run executions where the classpath is not the classpath that Gradle was started was,
j
So you load dependencies into a child classloader so that you don't accidentally pollute a given scope?
m
Ideally completely isolated classloader, not child because child can access their parents IIRC
y
Effectively. One example is the library that I pointed to. It used a specific version of JGit. If it did not a worker with classpath isolation, it would not have been possible to have both that and the JReleaser plugin working together, as the latter uses another (older & incompatible) version of JGit.
j
Oh, I see. So only for specific actions, and you still have to be careful about what you pass across the boundary.
y
It is actually better to most cases where you use other libraries, to try and run them inside tasks with worker that are classpath isolated. It is just better for the plugin ecosystem overall.
☝️ 1
m
I'm still curious the impact of that at scale actually because it's a lot more work for the JVM having to load all those classes
But one problem at a time, it's like for nuclear plants, we can solve the immediate problem of classpath isolation, the next generation will have to deal with the consequence 😬
y
It only needs to load them if the task executes
m
True true
I'll tell that to my grand kids πŸ˜„
It might actually be faster in most cases.... 🀞
j
Reporting back: It worked, although I have to be careful about getting new objects from static fields. It causes weird side-effects.
v
Static state in Gradle build logic is a very bad idea anyway. Gradle daemons and also worker daemons are reused. So your static state can influence other builds, even builds of completely different projects.
The SpotBugs plugin in the past tried to for example use the worker API to parallelize things and with process isolation to try fixing that issue. But even then, I built one SpotBugs using project, then another one and the second had very abnormal behavior. This was so because SpotBugs heavily over-uses static state to be used in such an environment. They need to change it so that they still use either API for parallel execution, but then using
javaExec
to do the actual execution in a separate dedicated JVM that is not reused.