Custom plugin in buildSrc leads to issues

Hey there,

currently I switch from a single-module Android Project to a multi-module Android Project.
Means we have in each module the same “boilerplate” setup for an Android library.

To reduce that code (and for maintainability) I thought I can move some parts to the buildSrc dir, create a new Plugin there and apply that new plugin in our Android library modules.

I have created the following build.gradle.kts in the buildSrc:

plugins {
    kotlin("jvm") version "1.2.41"
    id("org.gradle.java-gradle-plugin")
}

repositories {
    jcenter()
    google()
}

dependencies {
    implementation(kotlin("stdlib-jdk8"))

    implementation(kotlin("gradle-plugin"))
    implementation("com.android.tools.build:gradle:3.1.1")
}

gradlePlugin {
    plugins {
        create("android-module-std-plugin") {
            id = "some.package.name.android-module-std-plugin"
            implementationClass = "some.package.name.plugin.AndroidModuleStdPlugin"
        }
    }
}

Without any further implementation (for the AndroidModuleStdPlugin) I get an error with the dependency kotlin("gradle-plugin")…I think :upside_down_face:
The Android App module (the module which applies the com.android.application module) uses also Kotlin and the Kotlin-extensions. We only use the “View binding” feature here. Unfortunately - if I try to build the project with that setup - the build is broken with the message that it can’t resolve the package kotlinx (and of course each implemented class):
20

To my question:
Do I use the/understand the usage of the buildSrc correctly? :thinking:
As I said above - in the end I wanted to have a build.gradle in my Android library modules like this:

plugins {
  id("some.package.name.android-module-std-plugin")
}

Is that the way to go with the buildSrc?
If so, why do the dependencies inside the buildSrc break my builds? They shouldn’t belong to a module or am I wrong here?

And if I do everything correct here. Where is the issue then? In the Kotlin Plugin? In the Kotlin extension Plugin? And what should the Kotlin (extension) Plugin developers do/change that something like this will work?

The buildSrc classloader is a parent to all buildscript classloaders. Any classes loaded there can’t see classes loaded by individual projects. The Kotlin plugin’s “subplugin” mechanism is broken, as it relies on all Kotlin subplugins to be in the same classloader. There is an open issue for that. You’ll have to pull up your other plugin dependencies into buildSrc to fix this.

1 Like

Thanks @st_oehme for the answer. But either I don’t understand it or it doesn’t work :joy: .

As I understand you correctly you say that each Kotlin plugin (e.g. org.jetbrains.kotlin.android, org.jetbrains.kotlin.android.extensions, org.jetbrains.kotlin.kapt etc.) should be loaded by the same classloader like the “main” plugin (the org.jetbrains.kotlin.gradle-plugin).

To solve that I moved all of other plugins to a new plugin which just applied these plugins. Meaning my project looks something like that:
// buildSrc/build.gradle.kts
See above :point_up_2: +:

create("android-app-std-plugin") {
    id = "some.package.name.android-app-std-plugin"
    implementationClass = "some.package.name.plugin.AndroidAppStdPlugin"
} 

The AndroidModuleStdPlugin plugins looks like this:

class AndroidModuleStdPlugin : Plugin<Project> {
    override fun apply(project: Project) = with(project) {
        applyPlugins()
        setupAndroidLibrary()
        addDependencies()
    }
    private fun Project.applyPlugins() {
        pluginManager.apply {
            apply("com.android.library")
            apply("org.jetbrains.kotlin.android")
        }
    }
   // The other code which is not worth to mention IMO
}

The AndroidAppStdPlugin plugin looks like this:

class AndroidAppStdPlugin : Plugin<Project> {

    override fun apply(project: Project) {
        project.pluginManager.apply {
            apply("com.android.application")
            apply("org.jetbrains.kotlin.android")
            apply("org.jetbrains.kotlin.android.extensions")
            apply("org.jetbrains.kotlin.kapt")
        }
    }
}

The Android App module and the Android Library module apply only these new plugins:

// App
plugins {
    id("de.mgs.build.android-app-std-plugin")
    id("jacoco")
}

// other modules
plugins {
    id("de.mgs.build.android-module-std-plugin")
}

With that kind of code I expect that each plugin is loaded by the same buildSrc classloader, right?!
But I still get the same issue that the kotlinx and classes can not be resolved…

(I could also provide a sample project if you like?)

I just created a sample project from sketch and… it works.
Even with my original code without the new AndroidAppStdPlugin.

Seems there is another issue.
Anyway. The original idea works here (in my sample).
Will investigate further on the original project…

After playing around with my sample a little bit I found the issue:
I had to simply add the kotlin-gradle-plugin to the buildscript classpath dependencies to my own plugin:

class AndroidAppStdPlugin : Plugin<Project> {
    // ...
    private fun Project.addPluginsToClasspath() = with(buildscript.dependencies) {
        add("classpath", "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.41")
        Unit
    }
}

And everything works now :tada:

I don’t know if that is what you meant @st_oehme or another “issue”?!
But yeah, it works now :upside_down_face:

Don’t do that, just add org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.41 as a dependency to buildSrc/build.gradle

That does not work.
As you see in my first post I have already applied that plugin:

dependencies {
    implementation(kotlin("stdlib-jdk8"))

    implementation(kotlin("gradle-plugin")) // The ~droids~ plugin you are looking for
    implementation("com.android.tools.build:gradle:3.1.1")
}

I’ve prepared a sample project which demonstrate it:
TestApplication.zip (1.9 MB)

I’ve also created a Dockerfile in that project with that you can build the project to don’t install the all the Android stuff (SDK and so on). Simply build the image with the following command and you will see that the build will fail:

docker build . -t test-app

The output:

But if you commend in the line 63 in /buildSrc/src/main/kotlin/something/Plugins.kt the build will work:

Line 63: //project.buildscript.dependencies.add("classpath", "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.40")

Okay, so this is because of the bug in the Kotlin plugin I mentioned. It uses the current buildscript’s classpath to load extensions, which is broken by design. Please +1 the issue I linked above.