Depending on types of a 3rd-party plugin in buildSrc

I’m writing an extension function for a 3rd-party plugin’s type in buildSrc, snippet follows.

// In <project_dir>/buildSrc/src/main/kotlin/Publish.kt

import com.vanniktech.maven.publish.MavenPublishBaseExtension

fun MavenPublishBaseExtension.configurePublications(project: Project) {
    // ...
}

I tried to follow the pointers from this previous topic and declared my dependency like the following, but it resulted in a compilation error.

// In <project_dir>/buildSrc/build.gradle.kts

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("com.vanniktech:gradle-maven-publish-plugin:0.29.0")
    }
}

It compiles after I tried to declare it as a regular, compileOnly dependency (not within buildscript). But then fails with the following error message when I attempt to call the function from one of the subprojects.

java.lang.NoClassDefFoundError: com/vanniktech/maven/publish/MavenPublishBaseExtension
	at PublishKt.configurePublications(Publish.kt:38)
	at Build_gradle$6.execute(build.gradle.kts:62)
	at Build_gradle$6.execute(build.gradle.kts:61)
	at org.gradle.internal.extensibility.ExtensionsStorage$ExtensionHolder.configure(ExtensionsStorage.java:173)
	at org.gradle.internal.extensibility.ExtensionsStorage.configureExtension(ExtensionsStorage.java:64)
	at org.gradle.internal.extensibility.DefaultConvention.configure(DefaultConvention.java:210)
	at org.gradle.kotlin.dsl.Accessors21ap5ptrvz3qbl2mbj1tcqw9vKt.mavenPublishing(Unknown Source)
	at Build_gradle.<init>(build.gradle.kts:61)
	at Program.execute(Unknown Source)
	at org.gradle.kotlin.dsl.execution.Interpreter$ProgramHost.eval(Interpreter.kt:518)
	at org.gradle.kotlin.dsl.execution.Interpreter$ProgramHost.evaluateSecondStageOf(Interpreter.kt:446)
	at Program.execute(Unknown Source)

Please find the complete diff here. Any help would be appreciated. Thanks in advance!

Adding it in buildscript { ... } block of buildSrc/build.gradle.kts adds it … well … to the classpath of that build script, so you could use it in that build script, not anywhere else.

Besides that for that you should still use the plugins { ... } block with apply false and not the buildscript { ... } block.

Adding it as a normal dependency for the project where you build the plugin is exactly the right thing to do.

What you did would work, if you would use an included build, instead of buildSrc.
The problem with using buildSrc is, that all the classes from it and its dependencies are put to a classloader.
This classloader is in the ancestry of the buildscript classloaders, so the buildscripts can see those things, but the things in there cannot see the things on the buildscript classloaders.
As you only did a compileOnly dependency in buildSrc, the classes are not available at runtime if you don’t provide them in an accessible way, and adding it to child classloaders is not accessible.

Either use an included build, or change in buildSrc the compileOnly to implementation and remove the version where you apply the plugin to the buildscript, as it is already present on the classpath through the parent classloader coming from buildSrc.

Björn, thanks so much for your reply.

I have applied your suggestions (diff). Things are working as expected save for one problem stemming from subprojects that use the kotlin-android plugin.

I needed to add another dependency to buildSrc as apparently required by the Maven publishing library:

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
    implementation("com.vanniktech:gradle-maven-publish-plugin:0.29.0")
}

With this setup, I’ve gotten the following errors from aforementioned subprojects. I’ve confirmed that if they are excluded from the build, the configuration succeeded without issues.

An exception occurred applying plugin request [id: 'kotlin-android']
> Failed to apply plugin 'kotlin-android'.
   > Could not create an instance of type org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget.
      > Could not generate a decorated class for type KotlinAndroidTarget.
         > com/android/build/gradle/api/BaseVariant

Both kotlin-android and kotlin-multiplatform are supposedly included in kotlin-gradle-plugin. I’m not sure why then this issue occurred.

Because your cascade continues. If you would follow the advice from the error to use --scan or at least --stacktrace if you cannot for some reason, you would maybe have recognized it because it is a ClassNotFoundException.

Now the kotlin-android plugin wants to access the classes from the Android Gradle Plugin, but again you only add it in one of your build scripts, so it cannot access its classes. So also add the AGP to the buildSrc dependencies.

I see. If these parts are integral to the workings, I wonder why are they not declared as a transitive dependency?

Apologies if this is again me overlooking the obvious. I’m still well within the early stages of learning the intricacies of Gradle.

Well, the plugin code artifact has various plugins inside.
The world exists of more things than Android. :wink:
Would be strange if you use the plugin to do a Kotlin/JVM or Kotlin/JS project but have a totally useless AGP dependency there. :slight_smile:

They could of course split their plugins into multiple artifacts, so that in one are only Android related things.

But even then it might be better to not have a dependency, depending on exact details, for example if the plugin in question changed artifact coordinates while packages stayed the same. You might then for example be compatible with both versions better by not having a dependency and so on.

Why it is done like it is done, you have to ask the Kotlin folks. :slight_smile: