Usamos Gradle y Github Actions para publicar un proyecto Java en el Sonatype Maven Central Repository

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 : uno de los indicadores más importantes sobre DEX)., 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: github.com/yourusername -> 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. Es 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.jar y-javadoc.jar archivos. 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.jar o -javadoc.jar con un simple README dentro, para pasar la verificación.
  • Todos los archivos deben estar firmados con GPG/PGPcomo .asc el archivo que contiene la firma debe incluirse para cada archivo.
  • La existencia pom archivos
  • Valores correctos groupId, artifactId y version. La versión puede ser una cadena arbitraria y no puede terminar en -SNAPSHOT
  • Es necesario que esté presente name, descripción y url
  • 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 aquí.

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 sonatype.org.

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 : uno de los indicadores más importantes sobre DEX). 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 id nuestra 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 https://keys.openpgp.org 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:

Usamos Gradle y Github Actions para publicar un proyecto Java en el Sonatype Maven Central Repository

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é las instrucciones 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:

Usamos Gradle y Github Actions para publicar un proyecto Java en el Sonatype Maven Central Repository

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:

Usamos Gradle y Github Actions para publicar un proyecto Java en el Sonatype Maven Central Repository

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 Sonatype Nexus para confirmarlo:

Usamos Gradle y Github Actions para publicar un proyecto Java en el Sonatype Maven Central Repository

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 Windows, solo que la publicación se realiza a través de maven
  • Staging principal Sonatype
  • Jira Sonatype, en la que es necesario crear una tarea
  • Ejemplo del repositorio, donde todo está configurado

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster