En este artículo, quiero examinar en detalle el proceso de publicación desde cero de un artefacto Java a través de Github Actions en el Repositorio Central de Maven de Sonatype usando el compilador Gradle.
Decidí escribir este artículo debido a la falta de un tutorial adecuado en un solo lugar. Tuve que reunir toda la información por partes de diversas fuentes, y no todas eran muy recientes. A quienes les interese, bienvenidos bajo el corte.
Creación de un repositorio en Sonatype
El primer paso es crear un repositorio en Sonatype Maven Central. Para esto, vamos , nos registramos y creamos una nueva tarea, solicitando que nos creen un repositorio. Llenamos nuestro GroupId del proyecto, URL del proyecto enlace al proyecto y SCM url enlace al sistema de control de versiones en el que se encuentra el proyecto. GroupId aquí debe tener el formato com.example, com.example.domain, com.example.testsupport, y también puede ser un enlace a su github: -> io.github.yourusername. En cualquier caso, necesitarás confirmar la propiedad de este dominio o perfil. Si proporcionaste un perfil de github, te pedirán crear un repositorio público con el nombre requerido.
Después de un tiempo, tras la confirmación, tu GroupId será creado y podemos pasar al siguiente paso, la configuración de Gradle.
Configurando Gradle
Hasta el momento de escribir este artículo, no encontré plugins para Gradle que pudieran ayudar con la publicación del artefacto. el único plugin que encontré, sin embargo, su autor renunció a su mantenimiento. Así que decidí hacerlo todo yo mismo, afortunadamente, no es demasiado complicado.
Lo primero que hay que averiguar son los requisitos de Sonatype para la publicación. Son los siguientes:
- La disponibilidad de los códigos fuente y JavaDoc, es decir, deben estar presentes
-sources.jary-javadoc.jararchivos. Como se indica en la documentación, si no es posible proporcionar los códigos fuente o la documentación, se puede hacer un artefacto vacío-sources.jaro-javadoc.jarcon un simple README dentro, para pasar la verificación. - Todos los archivos deben estar firmados con
GPG/PGPcomo.ascel archivo que contiene la firma debe incluirse para cada archivo. - La existencia
pomarchivos - Valores correctos
groupId,artifactIdyversion. La versión puede ser una cadena arbitraria y no puede terminar en-SNAPSHOT - Es necesario que esté presente
name,descripciónyurl - La presencia de información sobre la licencia, desarrolladores y sistema de control de versiones
Estas son las reglas básicas que deben seguirse al publicar. La información completa está disponible .
Implementemos estos requisitos en build.gradle archivo. Para comenzar, agreguemos toda la información necesaria sobre los desarrolladores, la licencia, el sistema de control de versiones, así como definamos la URL, el nombre y la descripción del proyecto. Para ello, escribiremos un método simple:
def customizePom(pom) {
pom.withXml {
def root = asNode()
root.dependencies.removeAll { dep ->
dep.scope == "test"
}
root.children().last() + {
resolveStrategy = DELEGATE_FIRST
description 'Una descripción del artefacto'
name 'Nombre del artefacto'
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 'La Licencia Apache, Versión 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 'NombreDelDesarrollador'
email 'email@dev.ru'
}
}
}
}
}A continuación, es necesario especificar que durante la construcción se generen -sources.jar y-javadoc.jar archivos. Para ello, en la sección java debes agregar lo siguiente:
java {
withJavadocJar()
withSourcesJar()
}Pasemos al último requisito, la configuración de la firma GPG/PGP. Para ello, conectaremos el plugin signing:
plugins {
id 'signing'
}Y añadiremos la sección:
signing {
sign publishing.publications
}Finalmente, agreguemos la sección 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
}
}
}
}Aquí sonatypeUsername y sonatypePassword variables que contienen el nombre de usuario y la contraseña creadas al registrarse en .
Así, el resultado final build.gradle se verá de la siguiente manera:
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 'Una descripción del artefacto'
name 'Nombre del artefacto'
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 'La Licencia Apache, Versión 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 'NombreDesarrollador'
email 'email@dev.ru'
}
}
}
}
}Quiero señalar que obtenemos la versión de la variable de entorno: System.getenv('RELEASE_VERSION'). La estableceremos durante la compilación y la tomaremos del nombre de la etiqueta.
Generación de clave PGP
Uno de los requisitos de Sonatype es firmar todos los archivos con una clave GPG/PGP. Para esto, vamos y descargamos la utilidad GnuPG para nuestro sistema operativo.
- Generamos un par de claves:
gpg --gen-key, introduciendo el nombre de usuario, el correo electrónico y también establecemos una contraseña. - Determinamos
idnuestra clave con el comando:gpg --list-secret-keys --keyid-format short. El id se indicará después de la barra, por ejemplo: rsa2048/9B695056 - Publicamos la clave pública en el servidor con el comando:
gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056 - Exportamos la clave secreta a un lugar arbitrario, la necesitaremos más adelante:
gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg
Configuramos GitHub Actions
Pasemos a la etapa final, configurando la compilación y la publicación automática utilizando GitHub Actions.
Github Actions: una funcionalidad que permite automatizar los flujos de trabajo, implementando un ciclo completo de CI/CD. La construcción, prueba y despliegue pueden ser activados por diversos eventos: un push de código, la creación de un lanzamiento o issues. Esta funcionalidad es completamente gratuita para repositorios públicos.
En esta sección, mostraré cómo configurar la construcción, el push de código y el despliegue en el repositorio de Sonatype al lanzar una nueva versión, así como la configuración de secretos.
Configuración de secretos
Para la construcción automática y el despliegue, necesitaremos una serie de valores secretos, como el id de la clave, la contraseña que ingresamos al generar la clave, el propio PGP clave, así como el login/contraseña para Sonatype. Estos pueden configurarse en una sección especial en la configuración del repositorio:

Configuramos las siguientes variables:
- SONATYPE_USERNAME/SONATYPE_PASSWORD: el login/contraseña que ingresamos al registrarnos en Sonatype
- SIGNING_KEYID/SIGNING_PASSWORD: el id de la clave PGP y la contraseña establecida durante la generación.
Quiero detenerme un momento en la variable GPG_KEY_CONTENTS. La razón es que para publicar necesitamos la clave PGP privada. Para incluirla en los secretos, utilicé y realicé una serie de acciones adicionales.
- Ciframos nuestra clave usando gpg:
gpg --symmetric --cipher-algo AES256 9B695056.gpg, ingresando la contraseña. Esta debe ser colocada en la variable: SECRET_PASSPHRASE. - Convertiremos la clave cifrada obtenida a formato de texto usando base64:
base64 9B695056.gpg.gpg > 9B695056.txt. Colocaremos el contenido en la variable: GPG_KEY_CONTENTS.
Configuración de la construcción al hacer push de código y crear un PR
Primero, necesitamos crear una carpeta en la raíz de tu proyecto: .github/workflows.
Dentro de ella, coloca un archivo, por ejemplo, gradle-ci-build.yml con el siguiente contenido:
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 con Gradle
uses: eskatos/gradle-command-action@v1
with:
gradle-version: current
arguments: build -PsonatypeUsername=${{secrets.SONATYPE_USERNAME}} -PsonatypePassword=${{secrets.SONATYPE_PASSWORD}}Este flujo de trabajo se ejecutará al hacer push en las ramas master, dev y testing, así como al crear solicitudes de extracción.
En la sección jobs, se indican los pasos que deben realizarse en los eventos especificados. En este caso, vamos a compilar en la última versión de ubuntu, usar Java 8 y también utilizar el plugin para Gradle eskatos/gradle-command-action@v1, que, usando la última versión del compilador, ejecutará los comandos especificados en arguments. Variables secrets.SONATYPE_USERNAME y secrets.SONATYPE_PASSWORD Estos son secretos que hemos configurado anteriormente.
Los resultados de la compilación se reflejarán en la pestaña Actions:

Autodespliegue al lanzar una nueva versión
Para el autodespliegue, crearemos un archivo de flujo de trabajo 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 con 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}}El archivo es prácticamente idéntico al anterior, excepto por el evento que lo activará. En este caso, es el evento de creación de una etiqueta cuyo nombre comienza con v.
Antes del despliegue, necesitamos extraer la clave PGP de los secretos y colocarla en la raíz del proyecto, así como descifrarla. A continuación, necesitamos establecer una variable de entorno especial RELEASE_VERSION a la que accedemos en gradle.build archivo. Todo esto se realiza en la sección Preparar para publicar. Obtenemos nuestra clave de la variable GPG_KEY_CONTENTS, la convertimos en un archivo gpg, y luego la desciframos, colocándola en el archivo secret.gpg.
A continuación, accedemos a una variable especial GITHUB_REF, de la cual podemos extraer la versión que configuramos al crear la etiqueta. Esta variable en este caso tiene el valor refs/tags/v0.0.2 de la cual cortamos los primeros 11 caracteres para obtener la versión específica. Luego usamos los comandos estándar de Gradle para publicar: test publish
Verificación de los resultados del despliegue en el repositorio de Sonatype
Después de crear la versión, debe iniciarse el flujo de trabajo descrito en la sección anterior. Para esto, creamos la versión:

asegurándonos de que el nombre de la etiqueta comience con v. Si después de presionar Publicar versión, el flujo de trabajo se ejecuta correctamente, podemos ingresar a para confirmarlo:

El artefacto ha aparecido en el repositorio Staging. Inicialmente, aparece con el estado Abierto, luego debe ser manualmente cambiado a estado Cerrado, haciendo clic en el botón correspondiente. Después de verificar el cumplimiento de todos los requisitos, el artefacto pasa a estado Cerrado y ya no está disponible para cambios. De esta forma, llegará a MavenCentral. Si todo está bien, se puede pulsar el botón Release, de este modo el artefacto irá al repositorio Sonatype.
Para que el artefacto aparezca en MavenCentral, es necesario solicitarlo en la tarea que creamos al principio. Esto solo se debe hacer una vez, ya que es nuestra primera publicación. En las siguientes ocasiones no será necesario, todo se sincronizará automáticamente. Me incluyeron en la sincronización rápidamente, pero para que el artefacto estuviera disponible en MavenCentral pasaron alrededor de 5 días.
Con esto, hemos publicado nuestro artefacto en MavenCentral.
Enlaces útiles
- Similar , solo que la publicación se realiza a través de maven
- Staging Sonatype
- Sonatype, en la que es necesario crear una tarea
- del repositorio, donde todo está configurado
Fuente: habr.com
