This message was deleted.
# plugin-development
s
This message was deleted.
m
To do this, we wrap the Kotlin configuration blocks with:
Copy code
public static void whenKotlinSupportPresent(Project p, Consumer<? super Project> action) {
        p.getPluginManager().withPlugin("org.jetbrains.kotlin.jvm", unused -> action.accept(p));
    }
so the lambda is only executed if the Kotlin plugin has been applied.
now, lambda code will call this:
Copy code
tasks.withType(KotlinCompile.class).configureEach(kotlinCompile -> { ... }
which should work because the Kotlin plugin has been applied, but it fails with:
java.lang.ClassNotFoundException: org.jetbrains.kotlin.gradle.dsl.KotlinCompile
😨
I think the problem comes from the fact our plugin uses a
compileOnly
dependency on the Kotlin plugin. It does this because we only want to react to the presence of the plugin, but then we need to access the types to be able to do something.
BUT, it seems this only works if the plugins are applied in a particular order...
m
I don't think this issue is relevant here
because I don't want to execute something after all plugins are appleid
the plugin is applied, but its types are not visible from mine
j
I created that issue because I had a plugins order issue and being unable to check if there is multiple plugins applied and not only one. My solution in my precompiled plugin was applying my plugin the last one which was not ideal…
m
in other words, this works:
Copy code
plugins {
    alias libs.plugins.kotlin.jvm
    alias libs.plugins.kotlin.kapt
    alias libs.plugins.kotlin.allopen
    id 'io.micronaut.build.internal.oraclecloud-example'
}
and this doesn't:
Copy code
plugins {
    id 'io.micronaut.build.internal.oraclecloud-example'
    alias libs.plugins.kotlin.jvm
    alias libs.plugins.kotlin.kapt
    alias libs.plugins.kotlin.allopen
}
but because of classloaders
again this is a different issue
I don't want to check if multiple plugins are applied or not, I just want my plugin to not throw a
ClassNotFoundException
actually re-ordering doesn't seem to work either 😕
it works if I put all plugins on my script classpath (via
buildSrc
). Annoying.
v
Hm, very strange. I would have expected this to work. Maybe you found a bug?
j
I suspect this is caused by the the “root build” classloader thing. The subproject inherits the root loader. Your
io-micronaut...
is loaded there and operates in this context. The Kotlin stuff if loaded on top only in the subproject loader. That explains for me why it is not seen - which would be what I expect (although I think it’s a shitty behaviour, if I am correct).
m
probably something like this, yes. The
io.micronaut
plugin is loaded in
buildSrc
j
it works if I put all plugins on my script classpath (via
buildSrc
). Annoying.
That would confirm it. “buildSrc” is always in the root.
v
Ah, right.
buildSrc
is even one level higher than root iirc
j
I don’t know how to deal with this nicely in a plugin you share publicly. Other then recommending to users to make sure that all plugins involved are loaded (not necessarily applied) in the root. You could catch the ClassNotFound and print a corresponding advice as error. 😕
v
Maybe you could use the thread context class loader if it is set by Gradle. So that you have some "configure kotlin" class that you load from the thread context class loader in your plugin and that can then see the Kotlin classes?
m
That's my problem, I can't really know how the plugin is going to be applied 😕
So that you have some "configure kotlin" class that you load from the thread context class loader in your plugin and that can then see the Kotlin classes?
That would be extremely cumbersome to write the configuration code, basically using reflection
v
loaded in the root.
iirc that wouldn't work if buildSrc is involved as it is the parent of the root project class loader
That would be extremely cumbersome to write the configuration code, basically using reflection
Nah, not with reflection, by loading your class from the lower class loader
Ah, no, I saying rubbish, it would still load from the parent class loader with the same problem
You would need to inject the class to the respective class loader
m
Death by a thousand classloaders
v
So you would need to create a new class loader that has the thread context class loader (if it is set properly) as parent class loader and then load your configure class from that new class loader, then it should properly see the classes without reflection
j
I won’t try to solve this in my plugin (if it was me). I would see to produce an error with good advice. Might probably be different depending on situation then. Things like:
Copy code
If plugin applied in root -> 'apply id("other-plugin") false' in root
When used through buildSrc -> Add dependency 'implementation("other.plugin:coordinates")' to "buildSrc/build.gradle"
👍 3
m
Yes, I can do something like that.
v
I would try to solve it, but I'm crazy 😄
j
This is a general Gradle plugin system design issue that should be addressed in Gradle itself
m
Maven makes it easier, since all plugins are loaded in isolation, and none can see the classes of another. With great powers come great responsibilities, said Hans.
🕷️ 2
one could argue that the other way to fix this is to have a plugin for each "cross configure" aspect, that users would have to explicitly apply. But I don't like much the "explicit" here.
and that also greatly multiplies the number of plugins to publish and understand
all in all it's about finding a reasonable tradeoff, but that error is mystifying 😄
v
Couldn't you make them separate plugins that you then apply in reaction to other plugins being applied, or was that also not possible? I mean to remember some problem there.
t
I have experienced a very similar problem with my plugin, and I even asked about it in this slack a long time ago. The only workaround that made sense was telling users to do this in their root build.gradle:
Copy code
plugins {
  id 'kotlin' version 'whatever' apply false
}
because it is indeed a classloader issue where my plugin couldn't see the kotlin classes
j
when you apply
kotlin
without
jvm
,
multiplatform
and so on, suffix, it is like applying a general one?
I didn't know that exist a
kotlin
id plugin
t
it doesn't, I was being lazy
😅 1
j
Note on this - AFAIK all Kotlin plugins are packaged in one Jar. Therefore, it does not matter which one you use in an “apply false” as Gradle loads the complete Jar onto he plugin classpath.
2
If you use buildSrc, or an included build for plugins, you can instead add a dependency to the plugin Jar (which is more elegant imo):
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
in buildSrc/build.gradle
c
Not all kotlin plugins are in the same jar IIRC, all-open is another one
(just saying because it seemed that everything was reduced to a single jar)
Let me ask a naive question, can't you get the class loader from the target project?
To be fair in a internal plugin I did just require people to include AGP and kotlin in their buildSrc or root project, but now that I have smarter people around I'm very curious
v
As I said above, even if you can get the class loader from the target project, for example if it is set as thread context class loader, that's just half the rent. If you load a class from your plugin through that class loader it wouldn't change anything, as class loaders first ask their parent class loader for the requested class and the highest class loader that finds it returns is, still only able to access classes from its own or higher class loaders. You need to have a separate class that does the configuration that is not part of your normal plugin class files (due to the parent-first delegation), then you need to create a new class loader that has the target project class loader as parent and adds the configuration class, then you load that configuration class from your new class loader. This class can then access the classes in the higher class loader and if it implements some interface that is actually available in your normal plugin classes, then you can also conveniently control that configuration class and go on without any reflection and without requiring the user to do something special. 🙂
😱 1
🫠 2
v
I guess one of the ways to workaround the behavior is to use
reflection
API to configure target plugins. Even though it kind of defeats the reason to have
compileOnly
, it might be viable if you do not need to use
KotlinCompile
-like classes a lot.
v
Of course, the point here was to avoid reflection :-)