Unable to locate id of convention/script plugin hosted in GCP Artifact Registry

Greetings. I’ve created two convention/script plugins that I have successfully published to a Google Artifacts Registry of type Maven repo. This entails using a Google plugin that handles things like authentication, etc. Publishing works fine, and I end up with the jar file published and Google tells me the POM looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.test</groupId>
  <artifactId>build-logic</artifactId>
  <version>0.3</version>
</project>

As mentioned, my plugins are script convention plugins. This is the src/main/groovy/build.gradle for the plugins:

plugins {
    id 'groovy-gradle-plugin'
}

repositories {
    gradlePluginPortal() // so that external plugins can be resolved in dependencies section
}

dependencies {
    implementation "org.springframework.boot:spring-boot-gradle-plugin:3.1.5"
}

The plugins themselves look like this:
src/main/groovy/java-conventions.gradle:

plugins {
    id 'java'
}

java {
    sourceCompatibility = JavaVersion.VERSION_21
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compileOnly "org.projectlombok:lombok:1.18.30"
    annotationProcessor "org.projectlombok:lombok:1.18.30"
    testImplementation "org.junit.jupiter:junit-jupiter:5.10.0"
    testRuntimeOnly "org.junit.platform:junit-platform-launcher:1.10.0"
}

tasks.named('test') {
    useJUnitPlatform()
    testLogging.showStandardStreams = true
}

and src/main/groovy/spring-conventions.gradle:

plugins {
    id 'java-conventions'
    id('org.springframework.boot')
}

dependencies {
    implementation('org.springframework.boot:spring-boot-gradle-plugin:3.1.5')
    implementation 'org.springframework.boot:spring-boot-starter-actuator:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.5'

    implementation 'jakarta.validation:jakarta.validation-api:3.0.2'

    testImplementation 'org.springframework.boot:spring-boot-testcontainers:3.1.4'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.5'
    testImplementation 'org.springframework.boot:spring-boot-test:3.1.5'
    testImplementation 'io.projectreactor:reactor-test:3.5.11'
    testImplementation 'org.testcontainers:junit-jupiter:1.19.1'
}

The project compiles fine, and I’m able to publish it to the GCP Artifacts Registry without problems. The issue arises when I’m trying to use the plugins in a different project. Because accessing the GCP Artifacts Registry requires authentication, etc., there’s a Google Gradle plugin that must be used for “bootstrapping”. After a lot of googling for this is sort of scenario, I found an article that suggested something like this in the settings.gradle file:

pluginManagement{
    repositories{
        maven {
            url = "artifactregistry://us-west1-maven.pkg.dev/<my-secret-project>/maven"
        }
        gradlePluginPortal()
        mavenLocal()
    }
}

dependencyResolutionManagement {
    repositories {
        maven {
            url "https://us-west1-maven.pkg.dev/<my-secret-project>/maven"
        }
        mavenCentral()
        gradlePluginPortal() // so that external plugins can be resolved in dependencies section
        mavenLocal()
    }
}

buildscript {
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
    dependencies {
        classpath("gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:latest.release")
    }
}

apply plugin: "com.google.cloud.artifactregistry.gradle-plugin"

In my build.gradle file, I have:

plugins {
     id 'java-conventions' version '0.3'
}

So a few details here. If I change my build.gradle file to simply declare the jar file containing my plugin (that lives in GCP Artifact Registry) as a dependency, I’m able to successfully have Gradle pull it down. So that means that my repo config with the Google plugin, etc. works. I can see that it successfully pulls down the jar file. What puzzles me is what should the plugin id be? I’ve tried a gazillion different variants with/without the artifact name, the package, etc. I’ve even tried to specifically name it in the project that publishes the plugin jar, but no matter what I try, it fails with the same error:

Plugin [id: 'java-conventions', version: '0.3'] was not found in any of the following sources:

- Gradle Core Plugins (not a core plugin. For more available plugins, please refer to https://docs.gradle.org/8.4/userguide/plugin_reference.html in the Gradle documentation.)
- Plugin Repositories (could not resolve plugin artifact 'java-conventions:java-conventions.gradle.plugin:0.3')
  Searched in the following repositories:
    maven(https://us-west1-maven.pkg.dev/<my-secret-project>/maven)
    Gradle Central Plugin Repository
    MavenLocal(file:/Users/<my-secret-user>/.m2/repository/)

Did you maybe only publish the code artifact?
You usually have an additional artifact for each plugin, the plugin marker artifact at coordinates java-conventions.gradle.plugin:java-conventions in your case.
This marker artifact is used to resolve the id from the plugins { ... } block to the actual plugin dependency.

Btw. you should add some namespace either by adding it to the name of the plugin file, or as package statement in the plugin file. IDs without dots / package should usually only be used by built-in plugins to prevent name clashes, also in the future.

Thanks for the response. If I create a package inside the plugin, I assume it should live in a corresponding directory? I.e. src/main/groovy/com/example/foo/bar/my-plugin.gradle and then use package com.example.foo.bar; inside the plugin per usual Java/Groovy standards?

I’m only publishing the jar file that the plugin build produces:

publishing {
    repositories {
          maven {
                 url "artifactregistry://us-west1-maven.pkg.dev/<my-secret-project>/maven"
          }
    }

    publications {
        maven(MavenPublication) {
            groupId = 'com.example.test'
            artifactId = 'build-logic'
            version = '0.3'

            artifact "build-logic/build/libs/build-logic.jar"
        }
    }
}

The only artifact that I can see getting produced by the build is this:

% ls -l build/libs
total 56
-rw-r--r--  1 foobar  staff  24679 Oct 25 15:14 build-logic.jar

The jar itself looks like this:

META-INF/
META-INF/MANIFEST.MF
SpringConventionsPlugin.class
JavaConventionsPlugin.class
com/
com/example/
com/example/CheckstyleUtil.class
com/example/ReadmeVerificationTask.class
META-INF/gradle-plugins/
META-INF/gradle-plugins/spring-conventions.properties
META-INF/gradle-plugins/java-conventions.properties
checkstyle.xml
precompiled_JavaConventions$_run_closure1.class
precompiled_JavaConventions$_run_closure3.class
precompiled_JavaConventions$_run_closure5.class
precompiled_JavaConventions$_run_closure2.class
precompiled_JavaConventions$_run_closure4.class
precompiled_SpringConventions.class
precompiled_SpringConventions$_run_closure1.class
precompiled_JavaConventions.class
precompiled_JavaConventions$_run_closure2$_closure6.class
cp_precompiled_JavaConventions.class
cp_precompiled_SpringConventions$_run_closure1.class
cp_precompiled_SpringConventions.class
cp_precompiled_JavaConventions$_run_closure1.class

At one point, I dabbled with adding this inside the maven(MavenPublication) { block:

            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')

                configurations.compile.allDependencies.each {
                    def dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', it.group)
                    dependencyNode.appendNode('artifactId', it.name)
                    dependencyNode.appendNode('version', it.version)
                }
            }
            from components.java

What I’m curious to know is 1) how is the plugin id defined? Which components are part of it? 2) is the jar file name that contains the plugin part of the plugin id? I have scoured the Gradle documentation, and not found any details on exactly how the plugin id is defined, and how it relates to a Maven repo artifact. If anyone can shed some light on this, that would be wonderful. Thanks!

Besides that using artifact ... in a publication is bad, and especially with a hard-coded path, you do not need to define any publication.
Just applying the groovy-gradle-plugin and the maven-publish plugin should be enough.
The groovy-gradle-plugin also applies the java-gradle-plugin plugin which automatically defines the necessary publications for the code artifact and the marker artifacts for you unless you disabled that functionality explicitly.
So just throw out your custom publication and have a look at the tasks you have. There should everything you need already be present.

And regarding the other questions, yes, I would also put it in the package-according subdirectory, though it is not strongly required afair.

And if you for example have plugins foo.a, foo.b, and foo.c in your build-logic project, you will get four artifacts. yourGroup:build-logic that contains the code for all plugins, and foo.a.gradle.plugin:foo.a, foo.b.gradle.plugin:foo.b, and foo.c.gradle.plugin:foo.c, which will not have a jar but only depend on the first artifact and are used to translate the plugin id from the plugins { ... } block to dependency coordinates as I described above. This is described also at Using Gradle Plugins.

Hmm. My build.gradle now looks like this:

plugins {
    id 'groovy-gradle-plugin'
    id "maven-publish"
    id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.1"
}

repositories {
    gradlePluginPortal() // so that external plugins can be resolved in dependencies section
}

publishing {
    repositories {
        maven {
            url "https://us-west1-maven.pkg.dev/<my-secret-project>/maven"
        }
    }
}

dependencies {
    implementation "org.springframework.boot:spring-boot-gradle-plugin:3.1.5"
}

and when I run ./gradlew publish, I get the following error:

> Task :publishJava-conventionsPluginMarkerMavenPublicationToMavenRepository FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':publishJava-conventionsPluginMarkerMavenPublicationToMavenRepository'.
> Failed to publish publication 'java-conventionsPluginMarkerMaven' to repository 'maven'
   > Could not PUT 'https://us-west1-maven.pkg.dev/<my-secret-project>/maven/java-conventions/java-conventions.gradle.plugin/unspecified/java-conventions.gradle.plugin-unspecified.pom'. Received status code 401 from server: Unauthorized

The Google plugin takes care of the authentication, but it doesn’t seem to get invoked here.

¯_(ツ)_/¯

As far as I remember that plugin only cares about repository for resolving things, not for publishing repositories

Ah, no, it does care about publishing repositories too, but you also there have to use the artifactregistry scheme, not the https scheme.

And you should probably set a version for your plugin project

If I use the artifactregistry scheme in the publications block, I get an error, and the usage string says that scheme is not allowed.

“works” for me.
Maybe you have a typo in the scheme?
If I just put

plugins {
    id("groovy-gradle-plugin")
    id("maven-publish")
    id("com.google.cloud.artifactregistry.gradle-plugin") version "2.2.1"
}

group = "foo"
version = 1

repositories {
    gradlePluginPortal() // so that external plugins can be resolved in dependencies section
}

publishing {
    repositories {
        maven("artifactregistry://us-west1-maven.pkg.dev/my-secret-project/maven")
    }
}

in my play project and invoke publish I get

Could not PUT ‘https://us-west1-maven.pkg.dev/my-secret-project/maven/foo/showcase/1/showcase-1.jar’. Received status code 401 from server: Unauthorized

which sounds like it works correctly.

1 Like

Yes!!! Thank you! I was able to publish it properly now. Next, I’m going to try to use the plugins from my “consumer” project, now that they hopefully are published correctly.

1 Like