Release to Maven Repositories with Gradle

It took me some time, but I figured out the least painful way to release something to maven central with gradle. Last week I tried the maven-publish plugin, but no matter what I tried it didn’t work. On the other hand the maven plugin kind of did and here I how I did it.

Basically, the release consists of five steps:

  • create the artifacts (the jar, optionally the sources and javadoc jars)
  • sign them with gnupg
  • upload them to sonatype (you “manage” your artifacts there)
  • release (available immediately if you know the details, which you do because you just released it)
  • wait about 2 days until it appears in the search engines

I had to sign the artifacts with gpg2 and upload the generated keys to a public key server so that sonatype can verify that I was the one who uploaded the artifacts. Here is the list of commands I used to do it:

# generate keys if they don't exist yet
gpg2 --gen-key

# list keys on the machine
gpg2 --list-keys

# upload to a key server; I used keyserver.ubuntu.com
gpg2 --send-keys --keyserver keyserver.ubuntu.com <KEY ID>

# I had my keys in backup, so I had to export them:
gpg2 --import path_to_keys/secring.gpg

gpg2 is available on all platforms:

  • windows: download from www.gpg4win.org and add directory to PATH
  • osx: brew install gpg2
  • ubuntu: sudo apt-get install gpgv2

The next step is the upload. Unfortunately, gradlew uploadArchives works with any kind of version, however if I wanted to release so I had to add -SNAPSHOT to the version. When I ran gradlew uploadArchives it built the artifacts (jars), signed them and uploaded to sonatype. Until this point it was automatic.

Here is the relevant part of build.gradle for uploadArchives:

apply plugin: 'maven'
apply plugin: 'signing'

version = '1.0-SNAPSHOT'
String group = <GROUP>

String pomName = <NAME>
String pomRepositoryUrl = <URL (github, gitlab, etc)>
String pomScmUrl = <SCM URL>
String pomDescription = <DESCRIPTION>
String pomLicenseName = <LICENSE NAME>
String pomLicenseUrl = <URL TO THE LICENSE>
String pomDeveloperId = <DEVELOPER ID>
String pomDeveloperEmail = <EMAIL>
String pomDeveloperName = <MY NAME>

task javadocJar(type: Jar) {
    classifier = 'javadoc'
    from javadoc
}

task sourcesJar(type: Jar) {
    classifier = 'sources'
    from sourceSets.main.allSource
}

artifacts {
    archives jar, javadocJar, sourcesJar
}

signing {
    sign configurations.archives
}

// Because if one does not want to release and
// therefore does not have gradle.properties
// the whole build will fail because the uploadArchives
// is being evaluated and sonatypeUsername
// and sonatypePassword is not going to be present
// using '<<' in uploadArchives could be an option,
// but it is deprecated in gradle 3.4
def getSonatypeProperty(String name) {
    name = 'sonatype' + name.capitalize()
    if (project.hasProperty(name)) {
        return project.getProperties()[name]
    } else {
        return ''
    }
}

uploadArchives {
    repositories {
        mavenDeployer {
            beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

            repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
                authentication(userName: getSonatypeProperty('username'), password: getSonatypeProperty('password'))
            }

            snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
                authentication(userName: getSonatypeProperty('username'), password: getSonatypeProperty('password'))
            }

            pom.project {
                name pomName
                description pomDescription
                url pomRepositoryUrl
                version version
                groupId group
                artifactId pomName
                packaging 'jar'
                scm {
                    connection pomScmUrl
                    developerConnection pomScmUrl
                    url pomScmUrl
                }
                licenses {
                    license {
                        name pomLicenseName
                        url pomLicenseUrl
                        distribution 'repo'
                    }
                }

                developers {
                    developer {
                        id pomDeveloperId
                        name pomDeveloperName
                        email pomDeveloperEmail
                    }
                }
            }
        }
    }

The authentication and signing information is in the gradle.properties file which I ignored in git, because I didn’t want to upload any sensitive information to a version control system. I store the following properties there:

# signing properties
signing.keyId=<KEY ID>
signing.password=
signing.secretKeyRingFile=<PATH>/secring.gpg

# sonatype auth
sonatypeUsername=<USERNAME>
sonatypePassword=<PASSWORD>

Releasing is manual. First, I checked if the upload was successful (mind the -SNAPSHOT in the version):

Next, I had to find the staging repository where it was staying:

Before release the repository must be closed:

Wait until all subsequent actions are green:

Now it is time to release:

Everything went fine, the package is available for download:

~/Code/microservices % ./gradlew build
:authservice:compileJava UP-TO-DATE
:authservice:processResources UP-TO-DATE
:authservice:classes UP-TO-DATE
:authservice:findMainClass
:authservice:jar
:authservice:bootRepackage
Download https://repo1.maven.org/maven2/com/zsoltfabok/sqlite-dialect/1.0/sqlite-dialect-1.0.pom
Download https://repo1.maven.org/maven2/com/zsoltfabok/sqlite-dialect/1.0/sqlite-dialect-1.0.jar
:authservice:assemble
:authservice:compileTestJava UP-TO-DATE

It took about two days until the artifact appeared in the search index:

If you release the first time, create an account for sonatype and write an issue to the admins so that they can activate the sync process for your group.

The build.gradle and gradle.properties are available in this project.


comments powered by Disqus