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
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.