buildSrc dependency declarations running interference

I am working with a multi-module Gradle project I don’t control. It follows the preferred structure:

rootProject/
|-buildSrc
|-subprojectA
|-subprojectB
...
|-subprojectN

Most of the subprojects use custom plugins from buildSrc/. In the buildSrc/build.gradle.kts, dependency declarations are:

// buildSrc/build.gradle.kts

...
dependencies {
  implementation("com.google.protobuf:protobuf-gradle-plugin:0.8.18")
  // ...
}

One such custom plugin (call it, “X”) from buildSrc/ applies the Protobuf Gradle Plugin (at version 0.8.18).

Most subprojects [ A, B, etc ] in my example above apply plugin “X”.

// subprojectA/build.gradle.kts

plugins {
  id("X")
  // ...
}

//...

However, in my case, I want to apply an external, binary plugin (e.g. with id “com.external.Z”), to 1 of the subprojects (say, N) that also applies the Google Protobuf Plugin, but at version 0.9.5, using plugin with id “com.external.Z”, and no other plugin.

In the external plugin Z’s build.gradle.kts file, the dependencies are as follows:

dependencies {
  implementation("com.google.protobuf:protobuf-gradle-plugin:0.9.5")
  // ...
}

It therefore uses the configuration logic (Extensions, etc) supplied by the Google Protobuf Plugin at version 0.9.5, which is significantly different that 0.8.18.

You can safely assume plugin Z applies the Google Protobuf Plugin; for example:

class Z : Plugin<Project> {

  override fun apply(project: Project) { 
    project.pluginManager.apply("com.google.protobuf")
    // ...
  }

  // ... 
}

I go onto modify subproject N, such that the configuration appears as:

// subprojectN/build.gradle.kts

plugins {
  id("com.external.Z") version "0.1.0"
}

// ...

Again, only applies plugin Z and no other plugin, not even from the project’s buildSrc/ directory. Plugin Z was properly published to a (internal Artifactory) Maven repository that is resolvable from the root project above.

No matter what I try, the classpath determined by buildSrc/ applies to subproject’s N’s build script when resolving the Google Protobuf Plugin binary, despite subprojectN not using a single plugin from buildSrc/, or anything other plugin for that matter.

I have tried:

// subprojectN/build.gradle.kts

buildscript {
  dependencies {
    constraints {
      classpath("com.google.protobuf:protobuf-gradle-plugin:0.9.5")
    }
  }
}

plugins {
  id("com.external.Z") version "0.1.0"
}

// Optionally, this, though I think this only affects the configurations (classpath) of the project, not the Gradle buildscript (for subprojectN) itself
configurations.all {
    resolutionStrategy.eachDependency {
        if (requested.group == "com.google.protobuf" && requested.name == "protobuf-gradle-plugin") {
            println("Current version of Protobuf Gradle Plugin is [${requested.version}]")
            useVersion("0.9.5")
        }
    }
}

// ...

In all cases, I still get:

An exception occurred applying plugin request [id: 'com.external.Z, version: '0.1.0']
> Failed to apply plugin 'com.external.Z'.
   > Extension of type 'ProtobufExtension' does not exist. Currently registered extension types: [ExtraPropertiesExtension, ... ]

Indeed, ProtofExtension(source) does not exist in version 0.8.18, but certainly does in 0.9.5 (source).

I know that 0.8.18 binary version for the Protobuf plugin is used in this case because I checked the Gradle build scan.

Is there any workaround, or other solution for this problem?

In the meantime, I will re-review the docs on buildSrc/ (and other related doc) in case I missed anything.

Thanks in advance.

For clarification, the project I mention above uses Gradle 7.6.4.

Yes, this is the expected behavior when using buildSrc, which is one of the reasons I avoid it wherever possible and instead use an included build.

The things you build in buildSrc and their dependencies are in a class loader that is in the ancestry of the root project build script classpath class loader and thus also in the ancestry of all build script classpath class loaders and thus the classes are used from there, no matter what you do in the build script.

If you instead use an included build for the build logic, then it takes part in normal classpath conflict resolution and would show the behavior you would have expected.

1 Like

Thank you for the explanation & confirmation @Vampire. Much appreciated.

1 Like