Search code examples
gradleplugins

Using custom binary gradle plugin within a multi-module gradle project


The Plugin

I have created a custom binary Gradle plugin that encompasses all of the capabilities that we were previously keeping in individual project build.gradles and copying around from project to project. Because we have a couple of different types of project, I created a different plugin for each type such that loading the correct plugin gives all of the expected capabilities. Because everything is so similar I have a single project that contains the code for all plugins, and publishes all of them.

// Plugin Structure
my-gradle-plugin
   |-src/main/java
   |    |-com.company.gradle.plugin
   |         |-common.CommonPlugin.java
   |         |-a.TypeAPlugin.java
   |         |-b.TypeBPlygin.java
   |         |-etc
   |-build.gradle

// build.gradle
gradlePlugin {
    plugins {
        common {
            id = 'com.company.gradle.plugin.core'
            implementationClass = 'com.company.gradle.plugin.common.CommonPlugin'
        }
        a {
            id = 'com.company.gradle.plugin.a'
            implementationClass = 'com.company.gradle.plugin.a.TypeAPlugin'
        }
        // etc
    }

The common plugin is used by all, and loaded in code by each "project type" plugin

// TypeAPlugin.java
public class TypeAPlugin implements Plugin<Project> {
    
    @Override
    public void apply(Project project) {
        project.getPlugins().apply(CommonPlugin.class);
    }

    // snip
}

Publishing

The publishing of the plugin is handled via maven-publish, where for my testing I publish via publishToMavenLocal and it is deployed to our in-house Nexus repo via publish. Our Nexus repo is configured to act as a plugin repository at the system level via ~/.gradle/init.gradle

settingsEvaluated { settings ->
    settings.pluginManagement {
        repositories {
            mavenLocal()
            maven {
                allowInsecureProtocol = 'true'
                url 'http://myNexusHost/repository/internal'
                mavenContent {
                    releasesOnly()
                }
            }
            maven {
                allowInsecureProtocol = 'true'
                url 'http://myNexusHost/repository/snapshots'
            }
        }
    }
}

The appropriate configuration for our Nexus repo is also done within the same ~/.gradle/init.gradle

Client Project

If I attempt to use it within another project then I can so with within an simple/individual project by adding

plugins {
    id 'com.company.plugin.a' version '1.2.3'
}

and all is good in the world. The plugin is loaded and works as expected. Now however I'm trying to use it within a multi-module project with the following structure

projectRoot
   |-buildSrc
   |    |-src/main/groovy/project-convensions.groovy
   |    |-build.gradle
   |-moduleA
   |    |-src/main/java
   |    |-build.gradle
   |-moduleB
   |    |-src/main/java
   |    |-build.gradle
   |-etc
   |-settings.gradle

// settings.gradle
rootProject.name = 'projectRoot'
include 'moduleA'
include 'moduleB'
// etc

What I want to accomplish is include com.company.plugin.a in the buildSrc project-convensions.groovy, such that it becomes available through that to all nested modules (i.e.: changing its version down the road will be done in one place, rather than for each module).

I tried to simply include the same plugins section as per an individual project, but that doesn't work

// buildSrc/src/main/groovy/project-convensions.groovy
plugins {
    id 'com.company.plugin.a' version '1.2.3'
}

// moduleA/build.gradle
plugins {
    id 'project-convensions'
}

// Error
Invalid plugin request [id: 'com.company.plugin.a', version: '1.2.3']. Plugin requests from precompiled scripts must not include a version number. Please remove the version from the offending request and make sure the module containing the requested plugin 'com.company.plugin.a' is an implementation dependency

Alright, so next I tried placing the version into buildSrc/build.gradle, but that is also not working.

// buildSrc/build.gradle
dependencies {
    implementation 'com.company.plugin.a:com.company.plugin.a.gradle.plugin:1.2.3'
}

// buildSrc/src/main/groovy/project-convensions.groovy
plugins {
    id 'com.company.plugin.a'
}

// moduleA/build.gradle
plugins {
    id 'project-convensions'
}

// Error
> Failed to apply plugin 'project-conventions'.
   > Failed to apply plugin class 'com.company.gradle.plugin.common.CommonPlugin'.
      > plugin has been deployed with wrong coordinates: expected group to be 'com.company' and name to be 'my-gradle-plugin'

Now I'm at a loss... The second approach is what I've used for other third-party plugins successfully, but yet it's not working here. Should I be applying this plugin differently? Do I need to break up my-gradle-plugin such that each has it's own project (and thus the deployment coordinate of the "library" matches that of the plugin? Why would this work for an individual project but not in this nested manner?

To note, if I apply the plugin to each module within projectRoot individually via the first approach (as if it were an independent project) then it works....

I am using Gradle 7.2.0

Update - trying to access via <group>:<artifact>:<version>

// buildSrc/build.gradle
dependencies {
    implementation 'com.company:my-gradle-plugin:1.2.3'
}

// buildSrc/src/main/groovy/project-convensions.groovy
plugins {
    id 'com.company.plugin.a'
}

// moduleA/build.gradle
plugins {
    id 'project-convensions'
}

// Error
> Failed to apply plugin 'project-conventions'.
   > Failed to apply plugin class 'com.company.gradle.plugin.common.CommonPlugin'.
      > plugin has been deployed with wrong coordinates: expected group to be 'com.company' and name to be 'my-gradle-plugin'

No dice unfortunately :-(

Update - Stacktrace

The source of the problem is as follows

Caused by: java.lang.IllegalStateException: plugin has been deployed with wrong coordinates: expected group to be 'com.company' and name to be 'my-gradle-plugin'
    at com.company.gradle.plugin.GradleHelper.lambda$getPluginVersion$2(GradleHelper.java:25)
    at com.company.gradle.plugin.GradleHelper.getPluginVersion(GradleHelper.java:25)
    at com.company.gradle.plugin.common.CommonPlugin.applyCommonDependencies(FrameworkCorePlugin.java:99)
    at com.company.gradle.plugin.common.CommonPlugin.apply(FrameworkCorePlugin.java:50)
    at com.company.gradle.plugin.common.CommonPlugin.apply(FrameworkCorePlugin.java:38)
    at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:43)
    at org.gradle.api.internal.plugins.RuleBasedPluginTarget.applyImperative(RuleBasedPluginTarget.java:51)
    at org.gradle.api.internal.plugins.DefaultPluginManager.addPlugin(DefaultPluginManager.java:187)
    at org.gradle.api.internal.plugins.DefaultPluginManager.access$100(DefaultPluginManager.java:52)
    at org.gradle.api.internal.plugins.DefaultPluginManager$AddPluginBuildOperation.run(DefaultPluginManager.java:282)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:74)
    at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:74)
    at org.gradle.api.internal.plugins.DefaultPluginManager.lambda$doApply$0(DefaultPluginManager.java:167)
    at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)
    at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:166)
    ... 196 more

Solution

  • Kudos to @Vampire for seeing what I was unable to see in code that I never posted to here.

    The issue has nothing to do with Gradle as such, but rather a mechanism I employed in the plugin so that it could determine its own version. While I have no real recollection of this, I evidently followed the solution as outlined on the gradle forums (https://discuss.gradle.org/t/how-can-a-custom-gradle-plugin-determine-its-own-version/36761/2) and unsurprisingly ran into the exact issue as the OP in that thread. What this ultimately means is doing exactly what the OP did in that thread:

    Original (Not working)

    public class GradleHelper {
        
        public static String getPluginVersion(Project project) {
            final Configuration classpath = project.getBuildscript().getConfigurations().getByName("classpath");
            final String version = classpath.getResolvedConfiguration().getResolvedArtifacts().stream()
                .map(artifact -> artifact.getModuleVersion().getId())
                .filter(id -> "com.company".equals(id.getGroup()) && "my-gradle-plugin".equals(id.getName()))
                .findAny()
                .map(ModuleVersionIdentifier::getVersion)
                .orElseThrow(() -> new IllegalStateException("plugin has been deployed with wrong coordinates: expected group to be 'com.company' and name to be 'my-gradle-plugin'"));
            return version;
        }
    }
    

    Updated (Working)

    public class GradleHelper {
        
        public static String getPluginVersion(Project project) {
            Properties props = new Properties();
            try {
                props.load(GradleHelper.class.getClassLoader().getResourceAsStream("myplugin.properties"));
            } catch (IOException e) {
                throw new IllegalStateException("Unable to determine the plugin version", e);
            }
            return props.getProperty("version");
        }
    }
    
    // my-gradle-plugin/build.gradle
    task generateResources {
       ext {
          propFile = file("$buildDir/generated/myplugin.properties")
       } 
       outputs.file propFile 
       doLast {
          mkdir propFile.parentFile
          propFile.text = "version=$project.version" 
       } 
    } 
    processResources {
       from files(generateResources) 
    }
    

    Making this change to include the version in a property file, and loading it from said file, fixed the issue with multi-module projects.