Skip to content
/ mavence Public

CLI for publishing Gradle projects (Kotlin, Java, etc.) to Maven Central

License

Notifications You must be signed in to change notification settings

rtmigo/mavence

Repository files navigation

mavence #experimental

CLI utility for publishing Gradle projects (Kotlin, Java, etc.) to Maven Central.

This essentially does the same thing as the Signing and Nexus plugins.

Why not publish with plugins?
  • Building locally
  • Publishing somewhere

These tasks are almost unrelated.

By placing publishing logic in a build script, you make the foundation of the project complex and ugly.

Install and run

Command line

wget https://github.com/rtmigo/mavence/releases/latest/download/mavence.jar

Run:

java -jar mavence.jar

Manually

Just get the latest mavence.jar from the releases page.

Run:

java -jar ~/Downloads/mavence.jar

Setting the environment

Before publishing, you will need to set the following four environment variables:

variable wtf
SONATYPE_USERNAME Username for Sonatype JIRA (optionally replaced by token)
SONATYPE_PASSWORD Password for Sonatype JIRA (optionally replaced by token)
MAVEN_GPG_KEY Locally generated private key in ASCII armor
MAVEN_GPG_PASSWORD Password protecting the private key
Where to get Sonatype variables

Register on the Sonatype Jira and chat with bots, until they verify that you can publish a package. That gives you SONATYPE_USERNAME and SONATYPE_PASSWORD you can use for publishing.

Additionally, you can generate tokens to use them instead of the username and password. The tokens can be placed in the same SONATYPE_USERNAME and SONATYPE_PASSWORD and do not require other changes.

May the Google be with you.

Where to get GPG variables

Generate key

It gives you MAVEN_GPG_PASSWORD.

$ gpg --gen-key

gpg will interactively prompt you to choose a password for the new key. It is this password that should later be placed in the variable MAVEN_GPG_PASSWORD.

See your private key

It gives you MAVEN_GPG_KEY.

$ gpg --list-keys
pub   rsa3072 2022-10-18 [SC]
      1292EC426424C9BA0A581EE060C994FDCD3CADBD       << this is the ID
uid           [ultimate] John Doe <doe@example.com>
sub   rsa3072 2022-10-18 [E]
$ gpg --export-secret-keys --armor 1292EC426424C9BA0A581EE060C994FDCD3CADBD
-----BEGIN PGP PRIVATE KEY BLOCK-----

lQWGBGNOko0BDACzxxMh4EwjlOBRuV94reQglPp5Chzdw4yJHKBYffGGCy27nmde
Q05nuVbGJvHqv6jF1+zRNMIEKS/Ioa1C4jenEe0j3boGM2IgjHtPq7WuOeSR2ErX
...

-----END PGP PRIVATE KEY BLOCK-----

Or put it directly to MAVEN_GPG_KEY environment variable (Bash):

$ MAVEN_GPG_KEY=$(gpg --export-secret-keys --armor 1292EC426424C9BA0A581EE060C994FDCD3CADBD)

$ export MAVEN_GPG_KEY 

Send the public key to a keyserver

You won't come back to this again, but it will be important for the servers when publishing the package.

$ gpg --list-keys
pub   rsa3072 2022-10-18 [SC]
      1292EC426424C9BA0A581EE060C994FDCD3CADBD       << this is the ID
uid           [ultimate] John Doe <doe@example.com>
sub   rsa3072 2022-10-18 [E]
$ gpg --keyserver hkps://keys.openpgp.org --send-keys 1292EC426424C9BA0A581EE060C994FDCD3CADBD

Some servers will just store the key. Some may require prior email verification. Some servers disappear. You have to choose the right one for the moment.

Minimal configuration

We're using Gradle configuration to build a Maven package, but not push it Central. Creating in this way seems like a reasonable compromise.

build.gradle.kts

plugins {
    id("java-library")
    id("maven-publish")
}

java {
    withSourcesJar()
    withJavadocJar()
}

group = "my.domain"
version = "0.1.2"

publishing {
    publications {
        create<MavenPublication>("thelib") {
            from(components["java"])
            pom {
                val github = "https://github.com/doe/thelib"

                name.set("The Lib")
                description.set("There are dumber things than copy-pasting")
                url.set(github)

                developers {
                    developer {
                        name.set("John Doe")
                        email.set("doe@sample.com")
                    }
                }
                scm {
                    url.set(github)
                    connection.set(github.replace("https:", "scm:git:"))
                }
                licenses {
                    license {
                        name.set("Apache 2.0 License")
                        url.set("$github/blob/HEAD/LICENSE")
                    }
                }
            }
        }
    }
}

settings.gradle.kts

rootProject.name = "thelib"

Package name

The published package will have a version like my.domain:thelib:0.1.2.

Group and Version

It is the first and third part of my.domain:thelib:0.1.2, i.e. my.domain and 0.1.2.

They can be defined in build.gradle.kts like that:

group = "my.domain"
version = "0.1.2"
Artifact

It is the second part of my.domain:thelib:0.1.2, i.e. thelib.

mavence takes it from archivesBaseName Gradle property.

If we release the root project:

thelib/                   <<< dir name will be the artifact name 
    src/
    build.gradle.kts
    settings.gradle.kts   <<< unless redefined here

The redefine the root project name, add the following:

// settings.gradle.kts

rootProject.name = "newname"

If we release a subproject:

myrootproject/ 
    thelib/               <<< dir name will be the artifact name
        src/
        build.gradle.kts
    settings.gradle.kts    

Publishing

Keep in mind

When publishing, the servers may not return meaningful error responses.

They often return a generic "500 Internal Server Error" code, spontaneous "403 Forbidden" or accept the file, but never publish it as a maven package.

If publishing a package fails for any reason, sometimes this can be fixed by:

  • retrying
  • retrying later
  • editing your package's metadata

Publish to Maven Central

Set environment variables MAVEN_GPG_KEY, MAVEN_GPG_PASSWORD , SONATYPE_USERNAME, SONATYPE_PASSWORD and run:

cd /path/to/thelib
java -jar mavence.jar central

This single command will do all the necessary work: build, signing, staging and release.

Publish to Maven Local

This will place the package file inside the local ~/.m2 directory. This way you can test the package without sending it anywhere.

cd /path/to/thelib
java -jar mavence.jar local
stdout
{
  "group": "my.domain",
  "artifact": "thelib",
  "version": "0.1.2",
  "notation": "my.domain:thelib:0.1.2",
  "mavenRepo": "file:///home/doe/.m2/repository"
}

Publish to Staging

Set environment variables MAVEN_GPG_KEY, MAVEN_GPG_PASSWORD , SONATYPE_USERNAME, SONATYPE_PASSWORD and run:

cd /path/to/thelib
java -jar mavence.jar stage

This will push the package to a temporary remote repository . This way you can test the package without sending it to Central.

Testing before publishing

Although the utility prints quite a lot, stdout remains clean and only receives the result as JSON.

Bash:

JSON=$(java -jar mavence.jar local)

echo $JSON

Output:

{
  "group": "my.domain",
  "artifact": "thelib",
  "version": "0.1.2",
  "notation": "my.domain:thelib:0.1.2",
  "mavenRepo": "file:///home/doe/.m2/repository"
}

Using this data, you can test the package before it is sent.

I usually use Python and tempground for such testing.

License

Copyright © 2022 Artsiom iG. Released under the ISC License.