How can I fetch the Kotlin Project Extension? I'm ...
# gradle
a
How can I fetch the Kotlin Project Extension? I'm trying to fix a bug in Dokkatoo https://github.com/adamko-dev/dokkatoo/issues/123 Given a multi-module Gradle project
Copy code
.
└── foo-project/
    ├── submodule1 (Kotlin JVM)
    └── submodule2 (Kotlin JVM)
When I try and get KotlinProjectExtension I get an exception
Copy code
val ke = project.extensions.findByType<org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension>()
// Type org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension not present
I find I need to have
kotlin("jvm") version "1.8.22" apply false
on the root project, otherwise Dokkatoo can't fetch the Kotlin
org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
in the subprojects, even though they have applied the Kotlin JVM plugin. I tried falling back to fetching
org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
, but that also fails with the same error, "Type org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension not present" If I debug print the available extensions, I can see that KotlinJvmProjectExtension is present
Copy code
logger.warn("extensions: ${project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" }}")
// extensions: kotlin org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension (...)
j
as you are not applying the KGP in the root, the extension will not be there
a
sorry, I wasn't clear. The extensions also aren't present in the subprojects, and
kotlin("jvm")
is applied there
j
that is really weird, but you should wrap your calls using the extension with
withPlugin(…) { }
a
I have a test that generates the structure - without
kotlin("jvm") version "1.8.22" apply false
it fails https://github.com/adamko-dev/dokkatoo/blob/v2.0.0/modules/dokkatoo-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt#L397-L399
m
KGP needs to be applied next to dokkatoo I'd say?
If not they might end up in different classloaders
m
That's applying the plugin (calling
Plugin.apply()
) but not loading it (making
.jar
known to the JVM)
j
I don’t think you can avoid applying the Kotlin plugin
a
which line is calling
Plugin.apply()
?
j
@mbonnin I don’t think they should apply the plugin inside
only react to its presence
and as kgp is not being applied, you can’t react to its presence
m
Let me dig the details but what I meant is KGP needs to be loaded next to dokkatoo if dokkatoo wants a chance to see the symbols in its classloader
j
but dokkatoo should only add kgp as compileOnly in its dependencies 🤔
m
Agreed
My understanding is: • root classloader: contains dokkatoo jar, not KGP • module classloader: contains KGP jar, can reference dokkatoo symbols because root classloader is a parent of the module classloader
But root classloader doesn't know anything about children classloaders
It's a huge pain but 🤷
a
the root project has Dokkatoo applied, but no KGP subproject-hello has both Dokkatoo and KGP applied - so am I right in thinking they should both be in the classloader? In subproject-hello when Dokkatoo tries to react to KGP I can see that
KotlinJvmProjectExtension
is present and applied via debug printing, but Dokkatoo fails to fetch it
it makes sense that the root project can't fetch KotlinJvmProjectExtension, but it's not trying to
m
am I right in thinking they should both be in the classloader?
I'm not so sure they are in the "same" classloader. My current thinking is module classes can reference root classes but not the other way around
Need to step away from keyboard for a bit but I'll do a bit more testing to validate that assumption later today
👍 1
🙏 1
j
Kotlin version are matching?
a
Dokkatoo has a compileOnly dependency on KGP 1.9.0, and I've tried the example project with both 1.8.22 and 1.9.0 - same failure for both
findByName("kotlin")
fetches something successfully, but it's an
Any
and casting it to
KotlinProjectExtension
fails
You need to "load" all your plugins side by side
Of course this is all a giant pain because as a plugin author you usually don't control how your plugins are loaded 😕
j
Is it failing on a project or on a test?
m
What do you mean? It's failing during configuration time while evaluating the script
j
If he is using Gradle test kit and he is not adding KGP to the classpath that fail would be normal
m
Don't get me started on Gradle test kit ahah blob upside down
j
Looks like he is using test kit, so it is necessary to add the KGP and Android plugins
m
We should really start using
GradleRunner
and Tony's plugins
"test kit" and
withPluginClasspath
is a huge footgun
j
Copy code
val testPluginClasspath: Configuration =
    configurations.create("testPluginClasspath")

tasks.withType<PluginUnderTestMetadata>().configureEach { metadata ->
    metadata.pluginClasspath.from(testPluginClasspath)
}
and in the script something like
Copy code
testPluginClasspath(libs.agp)
testPluginClasspath(libs.kgp)
m
I'm not touching this with gloves 😄
j
hahaha
what do you mean with
GradleRunner
? It is what TestKit uses
m
GradleRunner
is well defined. It's an API I can use, it has artifacts & kdocs and it's working well 👍 .
TestKit
I have no idea what it is except it tests my plugin in conditions that are different from what my users are going to do
This is especially true for such weird classpath errors
You're better off publishing to a local maven repo and testing in real life conditions
j
I mean... for me
TestKit
is that
GradleRunner
🤔 At least the import includes it
org.gradle.testkit.runner.GradleRunner
a
thanks for the reproducer Martin!
❤️ 1
m
TestKit
is that
GradleRunner
For me
TestKit
=
GradleRunner
+
withPluginClasspath()
+?
a
Dokkatoo uses GradleRunner but it doesn't use withPluginClasspath - it publishes to a local maven repo
👍 1
j
withPluginClasspath
is a configuration inside
GradleRunner
👍 1
m
https://docs.gradle.org/current/userguide/test_kit.html Yep, what I was reading, it's all the same lib
j
you are not forced to use it if you don't depend on other plugins
👍 1
m
Even if you do depend on other plugins, you can avoid it
j
They (Gradle) should fix that then
TBH it should be done automatically, it has no sense you have to "dupe" what your script is doing in a custom configuration
m
Probably they should improve this indeed. Until then, I strongly recommend publishing to a local repo yourself or using Tony's plugin, especially to test classloading issues: https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit
a
I made an issue for Gradle because as far as I can tell this classloader behaviour is a bug, or it should at least have warnings and documentation https://github.com/gradle/gradle/issues/27218
❤️ 1
1
j
Technically it is documented
Copy code
Therefore, the test build does not share the same classpath or classloaders as the test process and the code under test is not implicitly available to the test build.
m
@Javier the bug doesn't have anything to do with TestKit
It's a Gradle bug that classloaders are weird. It's also the reason why so many plugins need to shadow their dependencies because conflict resolution does not always have the expected outcome in plugins.
a
almost 7 years old 😬
🙈 1