I’ve been working on a plugin for testing Gradle plugins – bootstage/testkit-gradle-plugin. Since running unit tests for a Gradle plugin requires using Gradle’s plugins DSL to apply the plugin, I reluctantly followed Gradle’s officially recommended best practices. And promptly fell into a pit.

Gradle Plugin Portal

Gradle has a Gradle Plugin Portal, similar to an App Store. Developers can publish Gradle plugins directly through java-gradle-plugin, making them searchable by keyword. Great idea in theory, but in practice I gave up on it. It was neither mature nor stable. The first version, testkit-gradle-plugin v0.1.0, was published successfully. But when publishing 1.0.0, a network timeout caused the publish to fail. When I tried again, it reported that version 1.0.0 already existed.

I’d long been frustrated with Sonatype’s slow console loading and had considered switching to another Maven hosting service. I tried bintray, struggled for a while, and failed to publish. After being burned repeatedly, I finally appreciated Sonatype and went back to publishing there. Using the standard publishing configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
publishing {
repositories {
maven {
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
}
}
publications {
create<MavenPublication>("mavenJava") {
groupId = "${project.group}"
artifactId = project.name
version = "${project.version}"

from(components["java"])

artifact(sourcesJar.get())
artifact(generateJavadoc.get())

pom {
...
}
}
}
}

signing {
sign(publishing.publications["mavenJava"])
}

I tested with publishToMavenLocal – no issues. Then I published to Sonatype. Sonatype’s POM signature verification failed with: BAD Signature. I downloaded the uploaded POM and POM.asc files and verified locally with GPG:

1
gpg --verify ~/Downloads/testkit-gradle-plugin-1.0.0.pom.asc

Sure enough:

1
gpg: BAD signature from "Johnson Lee <[email protected]>" [ultimate]

Opening the POM file revealed that all my custom POM properties were missing – overwritten. After commenting out the java-gradle-plugin plugin, the POM was fine, but the published plugin no longer supported the plugins DSL. I filed an issue with the Gradle team: https://github.com/gradle/gradle/issues/14993. They responded the next day: turns out java-gradle-plugin auto-generates a Publication, so the standard configuration approach doesn’t work. You need to use the Publication generated by java-gradle-plugin. Updated config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
publishing {
repositories {
maven {
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
}
}
publications {
withType<MavenPublication>().configureEach {
groupId = "${project.group}"
artifactId = project.name
version = "${project.version}"

artifact(sourcesJar.get())
artifact(javadocJar.get())

pom {
...
}

signing {
sign(this@configureEach)
}
}
}
}

Published locally – worked. Custom POM properties all present. Published to Sonatype again. Another error:

1
Invalid POM: /io/bootstage/testkit/io.bootstage.testkit.gradle.plugin/1.2.0/io.bootstage.testkit.gradle.plugin-1.2.0.pom: Project name missing, Project description missing

Opened the local POM: the io.bootstage.testkit.gradle.plugin generated by java-gradle-plugin indeed had no name or description. Then I remembered java-gradle-plugin‘s gradlePlugin DSL. Checked the API, found displayName and description. Tried it as a Hail Mary:

1
2
3
4
5
6
7
8
9
10
gradlePlugin {
plugins {
create("testkitPlugin") {
id = "io.bootstage.testkit"
displayName = "${id}.gradle.plugin"
description = project.description
implementationClass = "io.bootstage.testkit.gradle.TestKitPlugin"
}
}
}

Finally, a complete plugin published. Exhausting. Gradle has been brutal lately.

DSL

When writing Gradle scripts, every plugin you integrate requires some DSL configuration. But how is a user supposed to know what DSL each plugin provides? Do I need to read the entire API doc just to add a few lines of config?

And when things get complex – when plugins need to interact – figuring out the DSL becomes pure guesswork. Take the conflict between java-gradle-plugin and maven-publish above. Unless you’re on the Gradle team or have hit the exact same problem before, there’s no way to know the solution without reading the source code.

I just want to write a config script!

Having to dig through source code just to add a few lines of configuration? I’d rather give up. This is exactly why Booster has never supported DSL from the start. There’s simply no need. If something can be done in one line of code, never use two. How to minimize onboarding cost should be every developer’s concern.