In this article, I want to take a closer look at the process of publishing a Java artifact from scratch through Github Actions in the Sonatype Maven Central Repository using the Gradle collector.
I decided to write this article due to the lack of a normal tutorial in one place. All the information had to be collected in pieces from various sources, while not entirely fresh. Who cares, welcome to cat.
Creating a repository in Sonatype
The first step is to create a repository in Sonatype Maven Central. To do this, go here , register and create a new task, asking us to create a repository for us. We drive in our GroupId of the project, Project URL link to the project and SCM url link to the version control system in which the project lies. The GroupId here should be of the form com.example, com.example.domain, com.example.testsupport, and it can also be a link to your github: github.com/yourusername -> io.github.yourusername. In any case, you will need to confirm ownership of this domain or profile. If you specified a github profile, they will ask you to create a public repository with the desired name.
Some time after confirmation, your GroupId will be created and we can proceed to the next step, Gradle configuration.
Configuring Gradle
At the time of this writing, I did not find any plugins for Gradle that could help with the publication of the artifact. This is the only plugin I have found, however the author has refused his further support. Therefore, I decided to do everything myself, since itβs not too difficult to do it.
The first thing to find out is Sonatype's publishing requirements. They are as follows:
- The presence of source codes and JavaDoc, i.e. must be present
-sources.jar
and -javadoc.jar
files. As stated in the documentation, if it is not possible to provide source codes or documentation, you can make a dummy -sources.jar
or -javadoc.jar
with a simple README inside to pass the test. GPG/PGP
, .asc
, , .pom
groupId
, artifactId
version
. -SNAPSHOT
name
, description
url
- ,
, . .
build.gradle
. , , , url, . :
def customizePom(pom) {
pom.withXml {
def root = asNode()
root.dependencies.removeAll { dep ->
dep.scope == "test"
}
root.children().last() + {
resolveStrategy = DELEGATE_FIRST
description 'Some description of artifact'
name 'Artifct name'
url 'https://github.com/login/projectname'
organization {
name 'com.github.login'
url 'https://github.com/login'
}
issueManagement {
system 'GitHub'
url 'https://github.com/login/projectname/issues'
}
licenses {
license {
name 'The Apache License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
scm {
url 'https://github.com/login/projectname'
connection 'scm:https://github.com/login/projectname.git'
developerConnection 'scm:git://github.com/login/projectname.git'
}
developers {
developer {
id 'dev'
name 'DevName'
email 'email@dev.ru'
}
}
}
}
}
, -sources.jar
-javadoc.jar
. java
:
java {
withJavadocJar()
withSourcesJar()
}
, GPG/PGP . signing
:
plugins {
id 'signing'
}
:
signing {
sign publishing.publications
}
, publishing
:
publishing {
publications {
mavenJava(MavenPublication) {
customizePom(pom)
groupId group
artifactId archivesBaseName
version version
from components.java
}
}
repositories {
maven {
url "https://oss.sonatype.org/service/local/staging/deploy/maven2"
credentials {
username sonatypeUsername
password sonatypePassword
}
}
}
}
sonatypeUsername sonatypePassword , , sonatype.org.
, build.gradle
:
build.gradleplugins {
id 'java'
id 'maven-publish'
id 'signing'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withJavadocJar()
withSourcesJar()
}
group 'io.github.githublogin'
archivesBaseName = 'projectname'
version = System.getenv('RELEASE_VERSION') ?: "0.0.1"
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
}
test {
useJUnitPlatform()
}
jar {
from sourceSets.main.output
from sourceSets.main.allJava
}
signing {
sign publishing.publications
}
publishing {
publications {
mavenJava(MavenPublication) {
customizePom(pom)
groupId group
artifactId archivesBaseName
version version
from components.java
}
}
repositories {
maven {
url "https://oss.sonatype.org/service/local/staging/deploy/maven2"
credentials {
username sonatypeUsername
password sonatypePassword
}
}
}
}
def customizePom(pom) {
pom.withXml {
def root = asNode()
root.dependencies.removeAll { dep ->
dep.scope == "test"
}
root.children().last() + {
resolveStrategy = DELEGATE_FIRST
description 'Some description of artifact'
name 'Artifct name'
url 'https://github.com/login/projectname'
organization {
name 'com.github.login'
url 'https://github.com/githublogin'
}
issueManagement {
system 'GitHub'
url 'https://github.com/githublogin/projectname/issues'
}
licenses {
license {
name 'The Apache License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
scm {
url 'https://github.com/githublogin/projectname'
connection 'scm:https://github.com/githublogin/projectname.git'
developerConnection 'scm:git://github.com/githublogin/projectname.git'
}
developers {
developer {
id 'dev'
name 'DevName'
email 'email@dev.ru'
}
}
}
}
}
, : System.getenv('RELEASE_VERSION')
. .
PGP
Sonatype GPG/PGP . GnuPG .
- :
gpg --gen-key
, , e-mail, . id
: gpg --list-secret-keys --keyid-format short
. Id , : rsa2048/9B695056- https://keys.openpgp.org :
gpg --keyserver https://keys.openpgp.org/ --send-keys 9B695056
- , :
gpg --export-secret-key 9B695056 > D:\\gpg\\9B695056.gpg
Github Actions
, , Github Actions.
Github Actions β , , CI/CD. , : , issues. .
Sonatype , .
, id , , , PGP , / Sonatype. :

:
- SONATYPE_USERNAME/SONATYPE_PASSWORD β /, Sonatype
- SIGNING_KEYID/SIGNING_PASSWORD β id PGP , .
GPG_KEY_CONTENTS . , PGP . , , .
- gpg:
gpg --symmetric --cipher-algo AES256 9B695056.gpg
, . : SECRET_PASSPHRASE - base64:
base64 9B695056.gpg.gpg > 9B695056.txt
. : GPG_KEY_CONTENTS.
PR
: .github/workflows
.
, , gradle-ci-build.yml
:
name: build
on:
push:
branches:
- master
- dev
- testing
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
java-version: 8
- name: Build with Gradle
uses: eskatos/gradle-command-action@v1
with:
gradle-version: current
arguments: build -PsonatypeUsername=${{secrets.SONATYPE_USERNAME}} -PsonatypePassword=${{secrets.SONATYPE_PASSWORD}}
master
, dev
testing
, .
jobs , . ubuntu, Java 8, Gradle eskatos/gradle-command-action@v1
, , arguments
. secrets.SONATYPE_USERNAME
secrets.SONATYPE_PASSWORD
, .
Actions:

gradle-ci-publish.yml
:
name: publish
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
java-version: 8
- name: Prepare to publish
run: |
echo '${{secrets.GPG_KEY_CONTENTS}}' | base64 -d > publish_key.gpg
gpg --quiet --batch --yes --decrypt --passphrase="${{secrets.SECRET_PASSPHRASE}}" \
--output secret.gpg publish_key.gpg
echo "::set-env name=RELEASE_VERSION::${GITHUB_REF:11}"
- name: Publish with Gradle
uses: eskatos/gradle-command-action@v1
with:
gradle-version: current
arguments: test publish -Psigning.secretKeyRingFile=secret.gpg -Psigning.keyId=${{secrets.SIGNING_KEYID}} -Psigning.password=${{secrets.SIGNING_PASSWORD}} -PsonatypeUsername=${{secrets.SONATYPE_USERNAME}} -PsonatypePassword=${{secrets.SONATYPE_PASSWORD}}
, . , v.
PGP , . RELEASE_VERSION
gradle.build
. Prepare to publish
. GPG_KEY_CONTENTS, gpg , , secret.gpg
.
GITHUB_REF
, , . refs/tags/v0.0.2
11 , . Gradle : test publish
Sonatype
, . :

v. Publish release, , Sonatype Nexus :

Staging . Open, Close, . , Close . MavenCentral. , Release, Sonatype.
, MavenCentral, , . , . , . , MavenCentral 5 .
That's all, we published our artifact in MavenCentral.
useful links
- Similar article , only publishing via maven
- Staging Repository Sonatype
- Jira Sonatype, in which you need to create a task
- An example repository where this is all set up