As a Java developer, you may have an awesome artifact you want to share to the world. One of the best place to do that is probably Maven Central and this tutorial will help you go global 😉.
Why Use Maven Central ?
Assuming you know how Maven or Gradle works, you should know that most of the dependencies we use in a Java project are hosted in a repository. There are many available repositories like Jitpack and you can even create your own private one. But Maven Central is for repositories what Netscape was to browsers, an ancestor but one still living and widely used. In fact, Maven and Gradle by default lookup artifacts through Maven Central while in order to use other repositories, you need a little additional configuration and above all, it's totally free. So, using Maven Central which is well known, our artifacts will be available to anyone without further configuration. With this, you should agree that Maven Central is a good start right ?!
How ?
In order to push your artifact to Maven Central, you need to first push the code in a Github repository and add at least a SSH key in Github. I haven't tested if this method works with Gitlab or any other hoster but I guess it should do it too. After that, we need to sequentially compile, prepare the release before actually releasing it or deploying it if you prefer. We will use this workflow as the steps you should perform (except the "push your code to Github" part) since each one of them require some configuration. the examples I will give are based on the supposition that you are using Maven but feel free to adapt them to Gradle.
⚠️ If you don't own a custom domain, I strongly recommend using io.github.<your_github_username>
as groupId for your artifact otherwise, you'll have trouble during the deployment step.
Compilation step
在编译步骤中,我们将使用以下插件:
- Maven Compiler Plugin handling the compilation.
- Maven Surefire Plugin for automatically running unit tests after compilation. I can't talk enough about the importance of tests, so at least, write unit tests. Don't forget that you will release your artifact to the whole world so you should make sure it's bug free at most as possible.
- Maven Failsafe Plugin for running integration tests. Note that this plugin is optional depending on the nature of your project but I strongly recommend it if your artifact is to be used in a web application.
- Jacoco Maven Plugin for code coverage. You can also use Cobertura if you prefer.
- Maven Surefire Report Plugin for generating reports from your tests in formats like HTML, etc.
- Maven Source Plugin for creating a JAR archive containing the source code of your project. This is really helpful for other developers that will use your artifact. Of course modern IDEs will be able to decompile
.class
files for a peek into their code but it will be a lot easier if they can just download the original one. - Maven Javadoc Plugin for producing the javadoc pages 😂. Well, I'm laughing because I know most of us hate writing documentation and there is a lot of memes running wild on the Internet about it but you're going global (I love this term, it makes me feel like I'm doing something really big 🙃). So, you need to do as much as possible to help other developers use easily your artifact and producing a javadoc is part of that. Why not even go further by writing wiki pages in your repository ?!
首先,将其添加到您的pom.xml并进行相应调整:
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version.maven-compiler-plugin>3.8.1</version.maven-compiler-plugin>
<version.maven-surefire-plugin>2.22.2</version.maven-surefire-plugin>
<version.maven-source-plugin>3.1.0</version.maven-source-plugin>
<version.maven-javadoc-plugin>3.1.1</version.maven-javadoc-plugin>
<version.jacoco-maven-plugin>0.8.4</version.jacoco-maven-plugin>
<version.maven-surefire-report-plugin>2.22.2</version.maven-surefire-report-plugin>
<maven-failsafe-plugin.version>3.0.0-M3</maven-failsafe-plugin.version>
<jacoco.reportFolder>${project.build.directory}/jacoco</jacoco.reportFolder>
<jacoco.utReportFile>${jacoco.reportFolder}/test.exec</jacoco.utReportFile>
<jacoco.itReportFile>${jacoco.reportFolder}/integrationTest.exec</jacoco.itReportFile>
<project.testresult.directory>${project.build.directory}/test-results</project.testresult.directory>
<junit.utReportFolder>${project.testresult.directory}/test</junit.utReportFolder>
<junit.itReportFolder>${project.testresult.directory}/integrationTest</junit.itReportFolder>
</properties>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${version.maven-compiler-plugin}</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
<source>${java.version}</source>
<target>${java.version}</target>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${version.maven-surefire-plugin}</version>
<configuration>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${version.maven-source-plugin}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${version.maven-javadoc-plugin}</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<id>attach-javadoc</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${version.jacoco-maven-plugin}</version>
<executions>
<execution>
<id>pre-unit-tests</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<destFile>${jacoco.utReportFile}</destFile>
</configuration>
</execution>
<!-- Ensures that the code coverage report for unit tests is created after unit tests have been run -->
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${jacoco.utReportFile}</dataFile>
<outputDirectory>${jacoco.reportFolder}</outputDirectory>
</configuration>
</execution>
<execution>
<id>pre-integration-tests</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<destFile>${jacoco.itReportFile}</destFile>
</configuration>
</execution>
<!-- Ensures that the code coverage report for integration tests is created after integration tests have been run -->
<execution>
<id>post-integration-tests</id>
<phase>post-integration-test</phase>
<goals>
<goal>report-integration</goal>
</goals>
<configuration>
<dataFile>${jacoco.itReportFile}</dataFile>
<outputDirectory>${jacoco.reportFolder}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<configuration>
<!-- Only if using Spring Boot -->
<!-- Due to spring-boot repackage, without adding this property test classes are not found
See https://github.com/spring-projects/spring-boot/issues/6254 -->
<classesDirectory>${project.build.outputDirectory}</classesDirectory>
<!-- Force alphabetical order to have a reproducible build -->
<runOrder>alphabetical</runOrder>
<reportsDirectory>${junit.itReportFolder}</reportsDirectory>
<includes>
<include>**/*IT*</include>
<include>**/*IntTest*</include>
</includes>
</configuration>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Preparing the release
To prepare our release, we will use the Maven Release Plugin. As stated by it's documentation, the preparation will do the following:
- 检查源中是否有未提交的更改检查是否没有SNAPSHOT依赖项将POM中的版本从x-SNAPSHOT更改为新版本(系统将提示您使用该版本)在POM中转换SCM信息以包括标签的最终目的地针对修改后的POM运行项目测试,以确认一切正常提交修改后的POM用版本名称标记SCM中的代码(将提示输入)将POM中的版本绑定到新值y-SNAPSHOT(还将提示输入这些值)提交修改后的POM
Looking at this, you surely noticed that Maven will interact a lot with our SCM, so we will include the plugin Maven SCM Git Provider Git Executable Impl. since we are using git.
You'll need to adapt and add this lines to your pom.xml:
<properties>
...
<version.maven-release-plugin>2.5.3</version.maven-release-plugin>
<version.maven-scm-provider-gitexe>1.11.2</version.maven-scm-provider-gitexe>
...
</properties>
...
<scm>
<connection>scm:git:git://github.com/mlniang/spring-zabbix-api-client.git</connection>
<developerConnection>scm:git:git@github.com:mlniang/spring-zabbix-api-client.git</developerConnection>
<url>https://github.com/mlniang/spring-zabbix-api-client</url>
<tag>HEAD</tag>
</scm>
...
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>${version.maven-release-plugin}</version>
<configuration>
<localCheckout>true</localCheckout>
<pushChanges>false</pushChanges>
<mavenExecutorId>forked-path</mavenExecutorId>
<!--arguments>-Dgpg.passphrase=${gpg.passphrase}</arguments-->
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.scm</groupId>
<artifactId>maven-scm-api</artifactId>
<version>${version.maven-scm-provider-gitexe}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.scm</groupId>
<artifactId>maven-scm-provider-gitexe</artifactId>
<version>${version.maven-scm-provider-gitexe}</version>
</dependency>
</dependencies>
</plugin>
...
</plugins>
</build>
不用担心注释行,它将在最后一步中变得有用。 现在您可以运行mvn clean release:准备检查是否一切正常。
Deploying it
Once you've prepared your release, it's time to sign it and deploy it. Yes, you just read sign it and if you wonder why you need that, read this.
Getting access to Maven Central
Currently, you don't have rights to push your artifact to Maven Central. To get them, we will open an issue to Sonatype Issues and let them configure us an access. Create an account here if you don't have one yet and create an issue for new project hosting using this model.
支持将尽快为您提供指示,您应该在注释中遵循这些指示。 在等待它们时,让我们开始为签名生成GPG密钥。
GPG Key generation
If you already have a key linked to the mail you used for the Github account, you can skip this part.
Download GnuPG for you OS and if it comes with a GUI, feel free to use it to create a key and upload it to a public key server. If you prefer using the command line, here are some commands to help you:
$ gpg --version # Verify that GPG is installed and in your PATH
$ gpg --full-gen-key # Generate a key pair, follow the instructions.
在生成密钥对的过程中,它将提示您输入密码,输入一个(强烈建议),然后记住它。 现在让我们通知Maven关于在何处以及如何将其部署到Maven Central。
Maven Configuration
如您所知,我们将添加一个分销管理pom中指向我们的分发服务器的部分。 分发服务器将是Sonatype提供的服务器,它们将需要我们的Sonatype帐户凭据才能访问。 就像我们不应该以任何方式将凭据放入pom一样,我们会将其添加到伺服器我们的Maven部分settings.xml:
<servers>
...
<server>
<id>ossrh</id>
<username>sonatype_username</username>
<password>sonatype_password</password>
</server>
...
</servers>
在pom中:
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
We will add too a dependency for the Nexus staging plugin (you can read more about the Nexus staging plugin here) and a profile for automatically signing the artifact during the release.
<properties>
...
<version.nexus-staging-maven-plugin>1.6.8</version.nexus-staging-maven-plugin>
<version.maven-gpg-plugin>1.6</version.maven-gpg-plugin>
...
</properties>
...
<build>
<plugins>
...
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>${version.nexus-staging-maven-plugin}</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
...
<profiles>
...
<!-- GPG Signature on release -->
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${version.maven-gpg-plugin}</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Maven配置的最后一件事! 记住我们在pom中的注释行,去掉它的注释。 您会注意到,这与我们的GPG密钥的密码有关,应该将其存储在名为gpg.passphrase。 就像我们无法将密码短语放入pom一样,我们将在默认情况下在活动中添加一个全局配置文件settings.xml这将自动设置此属性:
<profiles>
...
<profile>
<id>ossrh</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>your_pgp_passphrase</gpg.passphrase>
</properties>
</profile>
</profiles>
💡目前,在Windows中,GPG安装不会自动将其bin目录添加到PATH环境变量中。 因此,该版本可能会因抱怨找不到可执行文件gpg.exe而失败。 只需将GPG bin目录添加到您的PATH或您的settings.xml中,在属性之前添加gpg.passphrase指定gpg可执行文件位置的属性:
<gpg.executable>C:\Program Files (x86)\GnuPG\bin\gpg.exe</gpg.executable>
Finalizing
在Sonatype的支持团队通知您可以推广您的第一个版本之后,请执行mvn版本:执行在将您创建的代码和修改后的代码推送到您的仓库之前。 完成后,请在评论中说声感谢Sonatype的支持,然后关闭票证。
A full example of a repository with an artifact in Maven Central is available at sprng-zabbix-api-client.
Welcome to the world of "known" Java developers and have fun 😎.