An example leibnix artifact to know what to upload to maven central
Web github URL: https://github.com/axeldamian/bibliotecanueva
First of all, dependence
<dependency>
<groupId>com.leibnix</groupId>
<artifactId>newlibrary</artifactId>
<version>1.2</version>
</dependency>
You can see it here:
https://central.sonatype.com/artifact/com.leibnix/newlibrary/1.2
https://mvnrepository.com/artifact/com.leibnix/newlibrary/1.2
bibliotecanueva
Creating a new library in Java with Maven.
It’s kind of cumbersome to upload to Maven Central, but it’s configured only once. I crashed at each step and had to send emails to Maven Central and get very annoying.
First of all, think of a unique groupId, because it’s the Maven namespace that identifies you worldwide. If it’s not valid in Maven, you’ll have to think of another one and modify it in your entire project.
As origen is the remote repository instead of origin
git pull origen main
origen is origin in Spanish
Create it with the command
mvn archetype:generate -DgroupId=com.newlibrary -DartifactId=newlibrary -Dversion=1.0-SNAPSHOT -DarchetypeArtifactId=maven-archetype-quickstart
Replace “com.newlibrary” with your groupId and “nwelibrary” with your artifactId.
Run in a terminal, standing where the pom is:
mvn clean package
mvn install
The project jar is created:
And the .m2/repository/com folder contains our project
Obviously .m2 is invisible and is located in ~
On Mac you can see it by pressing command + shift + .
Once it is in .m2/repository it can be used by adding it to the pom:
within dependencies in another project:
<dependency>
<groupId>com.newlibrary</groupId>
<artifactId>newlibrary</artifactId>
<version>1.0</version>
</dependency>
to use it, for example:
package services;
import org.springframework.stereotype.Service;
import com.newlibrary.clases.Operations;
@Service
public class ExampleService {
public int sumar(){
Operations op = new Operations();
return op.sum(1, 2);
}
}
If the bean is not recognized:
import services.ExampleService;
@Configuration
public class AppConfig {
@Bean
public ExampleService exampleService() {
return new ExampleService();
}
}
And use it, for example, in a controller:
package com.leibnix.leibnix.controllers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import services.ExampleService;
@RestController
public class BibliotecaExternaController {
Logger log = LogManager.getLogger(this.getClass().getSimpleName());
@Autowired
ExampleService exampleService;
@GetMapping("/suma")
public int libreriaExterna() {
log.info("call to endpoint /suma");
return exampleService.sumar();
}
}
when performing a GET to /suma
Publish project to maven central
There are 15 tags that must be included
They are metadata, that is, additional information that accompanies the code.
For example, a book has its content, which would be the code, and its author, which is metadata.
No dice hacer tests, yo hice con Junit, si no haces tests tu biblioteca no puede ser tomada en serio.
number of required tags: 15
within:
<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/xsd/maven-4.0.0.xsd">
...HERE IT GOES...
</project>
groupId —- root of pom.xml, inside project, example com.example
artifactId —- root of pom.xml, inside project, project name, goes inside the groupId hierarchically in maven central, has to be unique in the group
version —- root of pom.xml, inside project, example 1.0.0, another example 0.0.1-SNAPSHOT, if it is a snapshot, you cannot upload it to maven central, it is to indicate that the version is not productive.
packaging: jar, war —- root of pom.xml, inside project, jar is more common than war
name —- root of pom.xml, inside project
description —- root of pom.xml, inside project
url —- root of pom.xml, inside project
licenses — root of the pom.xml, inside project, the project license. It must be an approved OSI license
example:
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/licenses/MIT</url>
</license>
</licenses>
scm — root of the pom.xml, inside project, the project’s version control information (for example, Git),
svn or others.
example of use with github:
<scm>
<connection>scm:git:https://github.com/usuario/proyecto.git</connection>
<developerConnection>scm:git:ssh://github.com/usuario/proyecto.git</developerConnection>
Example of empty tags:
<scm />
<repositories>
<!-- There are no repositories configured -->
</repositories>
<pluginRepositories>
<!-- There are no plugin repositories configured -->
</pluginRepositories>
<dependencies>
<!-- Project Dependencies -->
</dependencies>
<build>
<plugins>
<!-- Project Plugins -->
</plugins>
Sign files with GPG
The documentation says PGP (not free) but GPG (free, a copy of PGP) serves the same purpose.
It is mandatory to sign: they are all generated in /target, the project jar, the jar that has the source code, the javadoc jar, the pom in target which is a copy of the original pom.
GPG is a terminal command program, on Mac it is installed with Homebrew.
I don’t want to make a tutorial about gpg, the only thing I say is that it was born from pgp and is called GnuPG, gnu privacy guard, it was made by Werner Koch and it has a GPL license.
Signature with gpg or asc extension.
The gpg passphrase is not in the pom, but in settings.xml, it can be in the pom.xml but it is not correct.
Keep reading, I’ll explain settings.xml in a bit.
<servers>
<!-- to sign with GPG -->
<server>
<id>gpg.passphrase</id>
<passphrase>GPG PASSPHRASE</passphrase>
</server>
</servers>
To sign with .asc instead of .gpg:
gpg -s --armor file.txt
generate public and private key, for that gpg asks for a passphrase, which is a password but longer
gpg --gen-key
sign a file with gpg
gpg --sign mi-artifact.jar
Verify the signature of a file using the corresponding signature file.
gpg --verify file.txt.asc
gpg encrypts messages using RSA, an algorithm that uses large prime numbers, the message is a signature “First Name Last Name Email”
You can sign by hand or use a plugin in java maven and it signs everything automatically.
The goal is to automate everything, not do anything manually.
Requirements for publishing to maven central
Releases :Only releases can be uploaded to the Central Repository, i.e. files that will not change and only depend on other files already released and available in the repository.
javadoc and sources: for search in IDE,
PGP Signature,
Minimum POM information : There are some requirements for minimum information in the POMs found in the Central Repository.
coordinates : It is important to choose the right coordinates for your project. In particular regarding groupId and domain ownership.
source: https://maven.apache.org/repository/guide-central-repository-upload.html
A command to do everything together can be
mvn clean package javadoc:jar verify
If the commands are executed separately, it throws an error. When signing, you have to make sure that you first create a package, on the same line.
Plugin to create a jar from sources
generate newlibrary-1.0-sources.jar inside /target
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
GPG plugin for maven to automatically sign files needed by maven central
It is executed in the “verify” phase to sign within /target: the .pom which is a copy of the original .pom, the javadoc jar, the documentation, the application jar and the sources.jar which contains the sources, there are 4 signatures, doing them manually with gpg is possible, but it is annoying, the code:
<!-- gpg sign -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.7</version>
<executions>
<execution>
<id>sign-files-for-maven-central</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
<configuration>
<showFiles>true</showFiles>
<displayFingerprint>true</displayFingerprint>
</configuration>
</execution>
</executions>
</plugin>
It should be noted that the javadoc jar and sources.jar cannot be executed because they do not have Main-Class in MANIFEST.mf contained in the jar.
Plugin to generate jar from sources
Don’t put anything that isn’t attached.
<configuration>
<attach>false</attach>
<!-- ... -->
</configuration>
If it is not attached GPG does not sign it, by default it is true, so if you do not set that option, its value is true.
Example:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
configuration is at the same level as goals.
settings.xml file
It goes in ~/.m2/settings.xml
If it is not created, create it.
It is a global configuration file for all java maven projects.
The passwords of the plugins that need them are included, you are not going to put the password in the pom, they can see it from github.
There are also global repositories that all maven projects use and global properties.
For example GPG uses passphrase, so the GPG plugin will need the passphrase, it goes in settings.xml not in the project’s pom.xml.
You can also put the passphrase in an environment variable or a protected environment variable and the project’s pom.xml uses the environment variable
Change where it says GPG PASSPHRASE to the actual passphrase:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<profiles>
<!-- Define a variable -->
<profile>
<id>dev</id>
<properties>
<property name="cloud" value="test"/><!--It doesn't work-->
<server>central</server><!--It works, but it can't be used ${server}-->
<example>test</example><!--works fine-->
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>dev</activeProfile>
</activeProfiles>
<servers>
<!-- to sign with GPG -->
<server>
<id>gpg.passphrase</id>
<passphrase>GPG PASSPHRASE</passphrase>
</server>
<server>
<id>central</id>
<username>USER TOKEN/username>
<password>PASSWORD GENERATED BY CENTRAL SONATYPE</password>
</server>
</servers>
</settings>
Maven central generates the configuration to paste
Tap the following buttons
Obviously log in to sonatype
View account, top right, in the menu that opens on our user
Tap ok in the window that opens
Other details:
Swipe on that window that opens.
<server>
<id>central</id>
<username>USER TOKEN GENERATED BY CENTRAL SONATYPE</username>
<password>PASSWORD GENERATED BY CENTRAL SONATYPE</password>
</server>
Where it says ${server} it goes central, I couldn’t create a property called server, it’s always null.
It’s a reserved word, I think that’s why.
Hardcode the “center”.
Project website
It is https, pay for a web domain, there are free ones, but it will take a long time to find it, and you also have to have control of the DNS part because you have to upload a TXT RECORD, otherwise the domain is not useful because you will not be able to validate the maven namespace.
I did it in
It is a hosting, it has wordpress to install and I chose a blog type template for wordpress, that is, a cms.
Public server for GPG public keys
The public key can be displayed, the private key cannot.
GPG creates a public/private key pair.
Este es un servidor de claves publicas de GPG:
https://keyserver.ubuntu.com/
What is it for?
There are several things that need to be signed with GPG, but the signature has to be valid, for that the public key has to appear on a public key server.
To view your public key
gpg --list-keys
To upload it to a public key server it is not online, it is in the terminal
gpg --send-keys KEY_ID --keyserver hkp://keyserver.ubuntu.com
Replace KEY_ID with the ID of your public key.
Create a namespace in maven and validate it
I hid the verification key in the images, which is then used to verify that the web domain belongs to us. The verification key is placed in a TXT RECORD in the web page’s DNS.
Maven checks your TXT record, the key and if it matches the one they gave you, then it correctly verifies your Maven domain. Mine is com.leibnix
That’s in “Add namespace”
Image of unverified namespace.
To go from unverified to pending you have to touch the button on the right, a banner will open and you have to click OK, at this point it took me a while to realize that.
Image of namespace pending verification, verification is all automatic.
Verified namespace image.
Add a code to the web txt record
what is a txt record?
In short, a website can be queried via DNS for its txt record.
Maven reads the txt record and checks that the verification code is there to validate that the website belongs to you.
This is used to validate the maven namespace.
There are 2 commands per terminal to see what the txt record contains:
dig y host
where the verification key appears, which is what the record txt contains, is crossed out
The steps to add the txt record, is an example of what I do in my hosting
In the web panel, go to dns
Then put new dns
Put in Data the verification code that Maven gives us and in type choose TXT
Plugin to upload code to maven
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.6.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
</configuration>
</plugin>
This will allow you to upload your artifact to maven central, managed by sonatype
mvn deploy
All together it would be, the verify signature:
You need to make a package first
mvn clean compile package javadoc:jar verify deploy
You can see its status in central sonatype
and the new dependency generated
You can also publish artifacts by hand, but I don’t know how it works.
I didn’t look into it much, I think there are several things that need to be uploaded.
Visualize the dependency
Here you can see your versions
https://repo1.maven.org/maven2/com/leibnix/newlibrary/
Here are your deploys:
https://central.sonatype.com/publishing/deployments
Your namespaces:
https://central.sonatype.com/publishing/namespaces
Example, see the dependency:
https://central.sonatype.com/artifact/com.leibnix/newlibrary
Put your groupId and artifact
After a few days it is indexed here, 4 days, it doesn’t matter if it is not a skillful one:
https://mvnrepository.com/
Example:
https://mvnrepository.com/artifact/com.leibnix/newlibrary
Variables in pom or settings
There are reserved words that cannot be used such as server.
It is used with ${}, example: ${example}
The neatest way is to define them in profiles and activate those profiles:
<!-- Define a variable -->
<profile>
<id>dev</id>
<properties>
<property name="cloud" value="test"/><!--It doesn't work-->
<server>central</server> <!-- the value of ${server} is null -->
<example>test</example><!-- works fine -->
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>dev</activeProfile>
</activeProfiles>
They can be added in the pom.xml or settings.xml
I added the following plugin to log variables in the terminal:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo message="text to display in terminal and the value of the example property of settings.xml ${example} is displayed"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
Example see the example property of the dev profile
mvn help:evaluate -Dexpression=example -Pdev
Show which profiles are active:
mvn help:active-profiles