https://kotlinlang.org logo
#gradle
Title
# gradle
a

Adam S

11/30/2023, 11:41 AM
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

Javier

11/30/2023, 11:43 AM
as you are not applying the KGP in the root, the extension will not be there
a

Adam S

11/30/2023, 11:44 AM
sorry, I wasn't clear. The extensions also aren't present in the subprojects, and
kotlin("jvm")
is applied there
j

Javier

11/30/2023, 11:45 AM
that is really weird, but you should wrap your calls using the extension with
withPlugin(…) { }
a

Adam S

11/30/2023, 11:45 AM
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

mbonnin

11/30/2023, 11:45 AM
KGP needs to be applied next to dokkatoo I'd say?
If not they might end up in different classloaders
m

mbonnin

11/30/2023, 11:46 AM
That's applying the plugin (calling
Plugin.apply()
) but not loading it (making
.jar
known to the JVM)
j

Javier

11/30/2023, 11:46 AM
I don’t think you can avoid applying the Kotlin plugin
a

Adam S

11/30/2023, 11:47 AM
which line is calling
Plugin.apply()
?
j

Javier

11/30/2023, 11:47 AM
@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

mbonnin

11/30/2023, 11:48 AM
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

Javier

11/30/2023, 11:49 AM
but dokkatoo should only add kgp as compileOnly in its dependencies 🤔
m

mbonnin

11/30/2023, 11:49 AM
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

Adam S

11/30/2023, 11:55 AM
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

mbonnin

11/30/2023, 11:59 AM
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

Javier

11/30/2023, 12:07 PM
Kotlin version are matching?
a

Adam S

11/30/2023, 12:12 PM
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

Javier

11/30/2023, 12:29 PM
Is it failing on a project or on a test?
m

mbonnin

11/30/2023, 12:30 PM
What do you mean? It's failing during configuration time while evaluating the script
j

Javier

11/30/2023, 12:30 PM
If he is using Gradle test kit and he is not adding KGP to the classpath that fail would be normal
m

mbonnin

11/30/2023, 12:31 PM
Don't get me started on Gradle test kit ahah blob upside down
j

Javier

11/30/2023, 12:31 PM
Looks like he is using test kit, so it is necessary to add the KGP and Android plugins
m

mbonnin

11/30/2023, 12:32 PM
We should really start using
GradleRunner
and Tony's plugins
"test kit" and
withPluginClasspath
is a huge footgun
j

Javier

11/30/2023, 12:33 PM
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

mbonnin

11/30/2023, 12:34 PM
I'm not touching this with gloves 😄
j

Javier

11/30/2023, 12:34 PM
hahaha
what do you mean with
GradleRunner
? It is what TestKit uses
m

mbonnin

11/30/2023, 12:35 PM
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

Javier

11/30/2023, 12:37 PM
I mean... for me
TestKit
is that
GradleRunner
🤔 At least the import includes it
org.gradle.testkit.runner.GradleRunner
a

Adam S

11/30/2023, 12:37 PM
thanks for the reproducer Martin!
❤️ 1
m

mbonnin

11/30/2023, 12:38 PM
TestKit
is that
GradleRunner
For me
TestKit
=
GradleRunner
+
withPluginClasspath()
+?
a

Adam S

11/30/2023, 12:38 PM
Dokkatoo uses GradleRunner but it doesn't use withPluginClasspath - it publishes to a local maven repo
👍 1
j

Javier

11/30/2023, 12:39 PM
withPluginClasspath
is a configuration inside
GradleRunner
👍 1
m

mbonnin

11/30/2023, 12:39 PM
https://docs.gradle.org/current/userguide/test_kit.html Yep, what I was reading, it's all the same lib
j

Javier

11/30/2023, 12:39 PM
you are not forced to use it if you don't depend on other plugins
👍 1
m

mbonnin

11/30/2023, 12:40 PM
Even if you do depend on other plugins, you can avoid it
j

Javier

11/30/2023, 12:41 PM
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

mbonnin

11/30/2023, 12:43 PM
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

Adam S

11/30/2023, 12:48 PM
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

Javier

11/30/2023, 12:50 PM
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

mbonnin

11/30/2023, 12:51 PM
@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

Adam S

11/30/2023, 12:57 PM
almost 7 years old 😬
🙈 1
5 Views