Neste artigo, quero detalhar o processo de publicação de um artefato Java do zero através do Github Actions no Repositório Maven Central da Sonatype utilizando o Gradle.
Decidi escrever este artigo devido à falta de um tutorial adequado em um só lugar. Tive que reunir todas as informações fragmentadas de várias fontes, que não eram exatamente recentes. Para quem se interessa, sejam bem-vindos.
Criando um repositório na Sonatype
A primeira etapa é criar um repositório no Maven Central da Sonatype. Para isso, vamos , nos registrar e criar uma nova tarefa, solicitando a criação do repositório. Preencha seu GroupId projeto, URL do Projeto link para o projeto e URL do SCM endereço do sistema de controle de versão onde o projeto está localizado. GroupId deve ter o formato com.example, com.example.domain, com.example.testsupport, ou pode ser um link para seu GitHub: -> io.github.yourusername. De qualquer forma, você precisará confirmar a propriedade desse domínio ou perfil. Se você indicou um perfil do GitHub, será solicitado que crie um repositório público com o nome desejado.
Após algum tempo depois da confirmação, seu GroupId será criado e podemos avançar para a próxima etapa, a configuração do Gradle.
Configurando o Gradle
No momento em que escrevo este artigo, não encontrei plugins para Gradle que pudessem ajudar na publicação do artefato. O único plugin que encontrei, no entanto, foi abandonado pelo autor. Portanto, decidi fazer tudo sozinho, felizmente isso não é muito complicado.
A primeira coisa que precisamos esclarecer são os requisitos da Sonatype para publicação. São os seguintes:
- A presença dos códigos fonte e JavaDoc, ou seja, devem estar presentes
-sources.jare-javadoc.jararquivos. Como mencionado na documentação, se não for possível fornecer os códigos fonte ou a documentação, é possível criar um arquivo vazio-sources.jarou-javadoc.jarcom um README simples dentro, para passar pela verificação. - Todos os arquivos devem ser assinados com
GPG/PGP, quanto.asco arquivo contendo a assinatura deve ser incluído para cada arquivo. - A presença de
pomdo arquivo - Valores corretos
groupId,artifactIdeversion. A versão pode ser qualquer string e não pode terminar com-SNAPSHOT - É necessário ter
nome,descriçãoeurl - A presença de informações sobre a licença, desenvolvedores e sistema de controle de versão
Estas são as regras básicas que devem ser seguidas durante a publicação. Informações completas estão disponíveis .
Implementamos esses requisitos em build.gradle arquivo. Para começar, vamos adicionar todas as informações necessárias sobre os desenvolvedores, a licença, o sistema de controle de versões, além de definir a url, o nome e a descrição do projeto. Para isso, escreveremos um método simples:
def customizePom(pom) {
pom.withXml {
def root = asNode()
root.dependencies.removeAll { dep ->
dep.scope == "test"
}
root.children().last() + {
resolveStrategy = DELEGATE_FIRST
description 'Alguma descrição do artefato'
name 'Nome do Artefato'
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 'Nome do Dev'
email 'email@dev.ru'
}
}
}
}
}Em seguida, é necessário especificar que, durante a construção, serão gerados -sources.jar e-javadoc.jar arquivos. Para isso, na seção java é necessário adicionar o seguinte:
java {
withJavadocJar()
withSourcesJar()
}Vamos passar para o último requisito, a configuração da assinatura GPG/PGP. Para isso, conectaremos o plugin signing:
plugins {
id 'signing'
}E adicionaremos a seção:
signing {
sign publishing.publications
}Finalmente, vamos adicionar a seção 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
}
}
}
}Aqui sonatypeUsername e sonatypePassword variáveis que contêm o login e a senha, criadas ao se registrar em .
Assim, o final build.gradle deve se parecer com o seguinte:
Código completo build.gradle
plugins {
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 'Alguma descrição do artefato'
name 'Nome do Artefato'
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 'A Licença Apache, Versão 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 'Nome do Dev'
email 'email@dev.ru'
}
}
}
}
}Quero observar que estamos obtendo a versão da variável de ambiente: System.getenv('RELEASE_VERSION'). Vamos defini-la durante a construção e pegar a partir do nome da tag.
Geração de chave PGP
Uma das exigências do Sonatype é assinar todos os arquivos com uma chave GPG/PGP. Para isso, vamos baixar a ferramenta GnuPG para o seu sistema operacional.
- Geramos um par de chaves:
gpg --gen-key, inserimos o nome de usuário, e-mail e também definimos uma senha. - Vamos verificar
idnossa chave com o comando:gpg --list-secret-keys --keyid-format short. O Id será indicado após a barra, por exemplo: rsa2048/9B695056 - Publicamos a chave pública no servidor o comando:
gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056 - Exportamos a chave secreta para um local à sua escolha, pois precisaremos dela mais tarde:
gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg
Configurando o Github Actions
Vamos para a fase final, configurar a construção e a publicação automática, usando o Github Actions.
Github Actions – funcionalidade que permite automatizar o fluxo de trabalho, implementando o ciclo completo de CI/CD. A construção, o teste e o deploy podem ser acionados por vários eventos: push de código, criação de lançamento ou issues. Esta funcionalidade é totalmente gratuita para repositórios públicos.
Nesta seção, vou mostrar como configurar a construção, o push de código e o deploy no repositório Sonatype ao lançar uma nova versão, além de como configurar os segredos.
Definindo segredos
Para a construção e o deploy automáticos, precisamos de uma série de valores secretos, como o id da chave, a senha utilizada ao gerar a chave, a própria chave PGP, além do login/senha para o Sonatype. É possível definir esses valores em uma seção especial nas configurações do repositório:

Definindo as seguintes variáveis:
- SONATYPE_USERNAME/SONATYPE_PASSWORD — login/senha que usamos ao nos registrarmos no Sonatype
- SIGNING_KEYID/SIGNING_PASSWORD — id da chave PGP e a senha definida durante a geração.
Vou aprofundar-me na variável GPG_KEY_CONTENTS. O ponto é que, para a publicação, precisamos da chave PGP privada. Para armazená-la nos segredos, utilizei e também realicei várias ações adicionais.
- Vamos criptografar nossa chave usando gpg:
gpg --symmetric --cipher-algo AES256 9B695056.gpg, digitando a senha. Esta deve ser colocada na variável: SECRET_PASSPHRASE - Vamos converter a chave criptografada para um formato de texto usando base64:
base64 9B695056.gpg.gpg > 9B695056.txt. O conteúdo deve ser colocado na variável: GPG_KEY_CONTENTS.
Configurando a construção ao realizar o push de código e ao criar PR
Primeiro, crie uma pasta na raiz do seu projeto: .github/workflows.
Nela, coloque um arquivo, por exemplo, gradle-ci-build.yml com o seguinte conteúdo:
name: build
on:
push:
branches:
- master
- dev
- testing
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Configurar JDK 8
uses: actions/setup-java@v1
with:
java-version: 8
- name: Construir com Gradle
uses: eskatos/gradle-command-action@v1
with:
gradle-version: current
arguments: build -PsonatypeUsername=${{secrets.SONATYPE_USERNAME}} -PsonatypePassword=${{secrets.SONATYPE_PASSWORD}}Este fluxo de trabalho será executado ao fazer push para os ramos master, dev e testing, assim como ao criar pull requests.
Na seção jobs, estão os passos que devem ser executados nos eventos especificados. Neste caso, vamos compilar na versão mais recente do ubuntu, usar Java 8 e também utilizar o plugin para Gradle eskatos/gradle-command-action@v1, que, utilizando a versão mais recente do compilador, executará os comandos especificados em arguments. Variáveis secrets.SONATYPE_USERNAME e secrets.SONATYPE_PASSWORD estes são os segredos que definimos anteriormente.
Os resultados da construção serão refletidos na aba Actions:

Autodeploy ao lançar uma nova versão
Para autodeploy, criaremos um arquivo de fluxo de trabalho separado gradle-ci-publish.yml:
name: publish
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Configurar JDK 8
uses: actions/setup-java@v1
with:
java-version: 8
- name: Preparar para publicar
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: Publicar com 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}}O arquivo é praticamente idêntico ao anterior, exceto pelo evento que o acionará. Neste caso, é o evento de criação de uma tag cujo nome começa com 'v'.
Antes do deploy, precisamos extrair a chave PGP dos segredos e colocá-la na raiz do projeto, além de descriptografá-la. Em seguida, precisamos definir uma variável de ambiente especial RELEASE_VERSION a qual acessamos no arquivo gradle.build. Tudo isso é feito na seção Prepare to publish . Obtemos nossa chave da variável GPG_KEY_CONTENTS, a convertendo para um arquivo gpg, e depois descriptografamos, colocando no arquivosecret.gpg Em seguida, acessamos uma variável especial.
GITHUB_REF , da qual podemos extrair a versão que definimos ao criar a tag. Esta variável, neste caso, tem o valorrefs/tags/v0.0.2 da qual cortamos os primeiros 11 caracteres para obter a versão exata. Em seguida, usamos os comandos padrão do Gradle para publicação: test publish Verificando os resultados do deploy no repositório Sonatype
Após a criação do lançamento, o fluxo de trabalho descrito na seção anterior deve ser iniciado. Para isso, criamos o lançamento:
onde o nome da tag deve começar com 'v'. Se após clicar em Publicar lançamento, o fluxo de trabalho for bem-sucedido, podemos acessar o

Sonatype Nexus para ter certeza disso:

O artefato apareceu no repositório Staging. Ele inicia com o status Open e deve ser manualmente alterado para o status Close, pressionando o botão correspondente. Após verificar o cumprimento de todos os requisitos, o artefato muda para o status Close e não pode mais ser alterado. Assim, ele será enviado para o MavenCentral. Se tudo estiver correto, você pode pressionar o botão Release, e o artefato será enviado para o repositório Sonatype.
Para que o artefato seja enviado para o MavenCentral, é necessário solicitar isso na tarefa que criamos no início. Isso deve ser feito apenas uma vez, pois estamos publicando pela primeira vez. Nas próximas vezes, isso não é necessário; tudo será sincronizado automaticamente. A sincronização foi ativada rapidamente, mas levou cerca de 5 dias para que o artefato se tornasse disponível no MavenCentral.
Isso é tudo, publicamos nosso artefato no MavenCentral.
Links Úteis
- Semelhante , apenas a publicação através do maven
- Staging Sonatype
- Sonatype, onde é necessário criar uma tarefa
- repositório, onde tudo isso está configurado
Fonte: habr.com
