Say, like me, you have some code you want to share with the world.
package dev.mccue.datastructures;
/**
* "Sum Type" representation of a linked list.
*/
public sealed interface LinkedList<T> {
/**
* An empty list.
*/
<T>()
record Emptyimplements LinkedList<T> {}
/**
* A not empty list.
*/
NotEmpty<T>(T first, LinkedList<T> rest)
record implements LinkedList<T> {}
}
To do this, you need to put that code in a place others can find it.
For Python programmers this means publishing to PyPI, Javascript programmers to npm, Rust programmers to crates.io, and C++ programmers to somewhere I assume.
For Java there are a few options, but the only one that will work by default in every build tool is Maven Central. Its apparently really good at being a repository, so publishing there is the thing to do.
There are plugins for all the major build tools that do this. However, last I tried, uploading a Java 16+ library to Maven Central using Maven was busted and requires exposing Java internals to work around.
So we are going to do something a little different. I am going to show you how to go through the entire process manually in the hope that it is straightforward enough to write your own scripts to do.
javac --version
jar --version javadoc --version
gpg --version
curl --version
git --version
gh --version
For this example I am going to put the linked list code from the top of the page in a file src/dev/mccue/datastructures/LinkedList.java
and make a small .gitignore
.
target/
.idea/
*.iml .DS_Store
git init
git add src/
git add .gitignore git commit -m "Initial Commit"
You will need a public url to refer to later and services like Github are convenient for that.
gh auth login
gh repo create --public --source .
git branch -M main git push origin main
Unlike other package repositories, Maven Central requires that you have a unique "group id" to prefix any packages you make. You cannot publish code under com.google
, only Google can.
To meet this requirement you either need to
com.yoursite
.io.github.yourusername
or similar.Once you got that all settled
This is an annoying step, I know, but it is what it is. If you get caught here ask in the comments below and I'll add more clarification.
javac -d target/classes -g --release 17 src/**/*.java
The -g
includes debug information. Always do that.
javadoc -d target/doc src/**/*.java
If you get warnings about undocumented classes and methods ignoring them is a choice you are technically allowed to make.
When you publish code there is the implicit assumption that you might upload newer versions of that code at a later point in time. To distinguish between versions, you need to number them. There are a few schemes for doing this including Semver, Calver, and 0ver.
In the commands from this point on, I am going to assume that the initial version being published is 0.0.1
, but you can do what you feel is best.
As early minecraft players learned when installing mods, Jar files are just zip files with a few extra bells and whistles.
mkdir target/deploy
jar --create \
--file target/deploy/datastructures-0.0.1.jar \ -C target/classes .
jar --create \
--file target/deploy/datastructures-0.0.1-sources.jar \ -C src .
jar --create \
--file target/deploy/datastructures-0.0.1-javadoc.jar \ -C target/doc .
A POM - "Project Object Model" - file is the standard format for declaring information about your library including any dependencies it may have on other libraries. This format is going to be around forever and all build tools have to handle it.
The following I am going to put into target/deploy/datastructures-0.0.1.pom
. This is the "minimal" POM and every field I list needs to be specified.
<?xml version="1.0" encoding="UTF-8"?>
project xmlns="http://maven.apache.org/POM/4.0.0"
< xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
modelVersion>4.0.0</modelVersion>
<
<groupId>dev.mccue</groupId>
<artifactId>datastructures</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>Datastructures</name>
<description>Basic Datastructures for Java.</description>
<url>https://github.com/bowbahdoe/java-datastructures</url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<name>Ethan McCue</name>
<email>ethan@mccue.dev</email>
<organization>McCue Software Solutions</organization>
<organizationUrl>https://www.mccue.dev</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/bowbahdoe/java-datastructures.git</connection>
<developerConnection>scm:git:ssh://github.com:bowbahdoe/java-datastructures.git</developerConnection>
<url>https://github.com/bowbahdoe/java-datastructures/tree/main</url>
</scm>
</project>
Okay so this part might feel wierd.
The idea here was that you generate a public and private key. You sign all the files you upload with those keys and then later on someone can confirm that it was "you" that actually did that signing.
Maven Central just makes sure that everything is signed, not that there is any way to associate the signed files back to you. Because public key infrastructure never really took off, this step is largely ceremonial in practice. You still need to do it though.
The official guide is more comprehensive than I am going to be
gpg --gen-key
Make sure to save your passphrase if you made one.
Run this command
gpg --list-keys
And you should get output that kinda looks like this.
[SC] [expires: 2023-06-23]
pub rsa3072 2021-06-23
CA925CD6C9E8D064FF05B4728190C4130ABA0F98[ultimate] Central Repo Test <central@example.com>
uid [E] [expires: 2023-06-23] sub rsa3072 2021-06-23
You want to take the part that looks like CA925CD6C9E8D064FF05B4728190C4130ABA0F98
and run the following command.
gpg --keyserver keyserver.ubuntu.com \ --send-keys CA925CD6C9E8D064FF05B4728190C4130ABA0F98
gpg --armor --detach-sign target/deploy/datastructures-0.0.1.jar
gpg --armor --detach-sign target/deploy/datastructures-0.0.1-sources.jar
gpg --armor --detach-sign target/deploy/datastructures-0.0.1-javadoc.jar gpg --armor --detach-sign target/deploy/datastructures-0.0.1.pom
If you are scripting this you should add --pinentry-mode loopback
and provide your passphrase via --passphrase
.
Yes, we are making a jar jar.
The most convenient api for uploading code manually is a form submit on the gui that is undocumented. I wanted to use something more official, but I had trouble finding what to do. I think its probably fine.
Said api wants one large jar as its input.
jar --create --file target/bundle.jar -C target/deploy .
Use the username and password you got from step 5.
curl --request GET \
--url https://s01.oss.sonatype.org/service/local/authentication/login \
--cookie-jar cookies.txt \ --user USERNAME:PASSWORD
curl --request POST \
--url https://s01.oss.sonatype.org/service/local/staging/bundle_upload \
--cookie cookies.txt \
--header 'Content-Type: multipart/form-data' \ --form file=@target/bundle.jar
When you run this command, you will get output back that looks like this
{"repositoryUris":["https://s01.oss.sonatype.org/content/repositories/STAGING_REPOSITORY_ID"]}
At this point, you can pause and point a build tool to the staging repository to make sure that everything is okay with your code before releasing the final version.
Fill in the STAGING_REPOSITORY_ID
from the output of the last command. There is no going back once the staging repostory is released.
curl --request POST \
--url https://s01.oss.sonatype.org/service/local/staging/bulk/promote \
--cookie cookies.txt \
--header 'Content-Type: application/json' \
--data '{
"data": {
"autoDropAfterRelease": true,
"description": "",["STAGING_REPOSITORY_ID"]
"stagedRepositoryIds":
} }'
You can try out the linked list we just published by including it in your build tool of choice.
dependency>
<<groupId>dev.mccue</groupId>
<artifactId>datastructures</artifactId>
<version>0.0.1</version>
</dependency>
A fully scripted version of this process can be seen here along with an associated Github workflow
Explain what a Maven MOJO is in 140 characters or less in the comments below.