Getting 'multiple plugins are using the same ID' for precompiled script plugin

Hi

I am working on a convention plugin which will have a build script with config for third party plugins like Spotbugs. I want to publish this to the plugin portal. So then I am using the id ‘com.gradle.plugin-publish’ version ‘1.0.0’ plugin. Which also applies the java-gradle-plugin and maven-publish plugins.

But since my plugin is really a more a groovy plugin I also applies the groovy-gradle-plugin. By that an “adapter” is generated for the build script.

build.gradle:

plugins {
  id 'com.gradle.plugin-publish' version '1.0.0'
  id 'groovy-gradle-plugin'
}

repositories {
  mavenCentral()
  gradlePluginPortal()
}

dependencies {
  implementation 'com.diffplug.spotless:spotless-plugin-gradle:6.11.0'
  implementation 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13'
  implementation 'org.owasp:dependency-check-gradle:7.3.0'
  implementation 'se.solrike.sonarlint:se.solrike.sonarlint.gradle.plugin:1.0.0-beta.6'
}

group = 'se.solrike.conventions'
version = '1.0.0-beta.2'
gradlePlugin {
  plugins {
    javaConventionsPlugin {
      id = group + '.java-conventions'
      // the class is a generated adapter class when using the groovy-gradle-plugin plugin
      implementationClass = 'SeSolrikeConventionsJavaConventionsPlugin'
      description =
          'Gradle convention plugins for Java projects. ' +
          'Configures Checkstyle, Dependency check, Eclipse, Jacoco, ' +
          'Java, Sonarlint, Spotbugs, Spotless plugins. ' +
          'Defines for Checkstyle rules, ' +
          'Spotbugs exclude filter, changes the default included and excluded Sonarlint rules and ' +
          'formatting rules for Spotless. Sets the output folder in Eclipse .classpath file.'
      displayName = 'Java conventions plugin for solrike.se'
    }
  }
}
pluginBundle {
  website = 'https://github.com/Lucas3oo/solrike-conventions-gradle-plugin'
  vcsUrl = website

  pluginTags = [
    javaConventionsPlugin: ['conventions', 'checkstyle', 'dependencycheck', 'eclipse', 'jacoco', 'java', 'sonarlint',
        'spotbugs', 'spotless']
  ]
}
wrapper {
  gradleVersion = '7.5.1'
}

The build script “convention” is under src/main/groovy/se.solrike.conventions.java-conventions.gradle

When I run ./gradlew publishPlugins I get this error:
Invalid plugin ID ‘se.solrike.conventions.java-conventions’: multiple plugins are using the same ID

The plugin works if I used it in a composite build. Like just make a ref to it in the settings.gradle in some other project.

I will anser my own question. It seems to me this is a bug. The list of PluginDescriptors in the GradlePluginDevelopmentExtension has two entries. One added in the build.gradle and the other must be generated by the groovy-gradle-plugin but I am not sure. That one isn’t complete either so it can’t really be used.

Here is at least a workaround which will simply remove the extra PluginDescriptor:

class Name implements Named {
  String name
  Name(String name) {
    this.name = name
  }
}

task configureGradlePluginDev {
  // remove the extra plugin description that gets generated I think due to both groovy-gradle-plugin
  // and java-gradle-plugin are used
  doLast {
    def ex = getProject().getExtensions().getByType(GradlePluginDevelopmentExtension.class)
    ex.getPlugins().remove(new Name(myPluginId))
  }
}
tasks.publishPlugins.dependsOn(configureGradlePluginDev)

What you have is not a normal plugin, but a precompiled script plugin.
A normal plugin consists of normal classes in .groovy files or .java files or .kt files or whatever.
A precompiled script plugin is a file ending in .gradle for Groovy DSL or .gradle.kts for Kotlin DSL.
The groovy-gradle-plugin cares about making a properly compiled binary plugin out of those .gradle files and also registers the plugin in the gradlePlugin extension for you.

What you did and what causes your problem is, that you register a second plugin in the gradlePlugin extension with the same plugin id which then causes the duplicate plugin id.

Instead you should simply conifigure the existing entry which was added for you by the groovy-gradle-plugin plugin.
The default name of the plugins entry is the same as the plugin id.

Unfortunately the PrecompiledGroovyPluginsPlugin only configures this plugins entry using afterEvaluate.
So you have to delay the configuration for example using:

gradlePlugin {
    plugins {
        matching { it.name == 'se.solrike.conventions.java-conventions' }.configureEach {
            description =
                    'Gradle convention plugins for Java projects. ' +
                            'Configures Checkstyle, Dependency check, Eclipse, Jacoco, ' +
                            'Java, Sonarlint, Spotbugs, Spotless plugins. ' +
                            'Defines for Checkstyle rules, ' +
                            'Spotbugs exclude filter, changes the default included and excluded Sonarlint rules and ' +
                            'formatting rules for Spotless. Sets the output folder in Eclipse .classpath file.'
            displayName = 'Java conventions plugin for solrike.se'
        }
    }
}

Thanks @Vampire, that solution works. Much cleaner.