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.
wget https://github.com/rtmigo/mavence/releases/latest/download/mavence.jar
Run:
java -jar mavence.jar
Just get the latest mavence.jar from the releases page.
Run:
java -jar ~/Downloads/mavence.jar
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
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
.
It gives you MAVEN_GPG_KEY
.
$ gpg --list-keys
pub rsa3072 2022-10-18 [SC]
1292EC426424C9BA0A581EE060C994FDCD3CADBD << this is the ID
uid [ultimate] John Doe <[email protected]>
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 <[email protected]>
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.
We're using Gradle configuration to build a Maven package, but not push it Central. Creating in this way seems like a reasonable compromise.
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("[email protected]")
}
}
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")
}
}
}
}
}
}
rootProject.name = "thelib"
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.
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"
myrootproject/
thelib/ <<< dir name will be the artifact name
src/
build.gradle.kts
settings.gradle.kts
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
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.
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"
}
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.
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.
Copyright © 2022 Artsiom iG. Released under the ISC License.