Uso de acciones de Gradle y Github para publicar un proyecto Java en el repositorio central de Sonatype Maven

En este artículo, quiero echar un vistazo detallado al proceso de publicación de un artefacto de Java desde cero a través de Github Actions en el repositorio central de Sonatype Maven usando el generador de Gradle.

Decidí escribir este artículo debido a la falta de un tutorial normal en un solo lugar. Toda la información tuvo que ser recopilada pieza por pieza de varias fuentes, además, no del todo frescas. A quién le importa, bienvenido bajo cat.

Crear un repositorio en Sonatype

El primer paso es crear un repositorio en Sonatype Maven Central. Para esto vamos aquí, registrarse y crear una nueva tarea, pidiéndonos que creemos un repositorio. Conducimos en nuestro Groupid proyecto URL del proyecto enlace del proyecto y URL de SCM un enlace al sistema de control de versiones en el que se encuentra el proyecto. Groupid aquí debe tener la forma com.example, com.example.domain, com.example.testsupport, y también puede tener la forma de un enlace a su github: github.com/tunombredeusuario -> io.github.tunombredeusuario. En cualquier caso, deberá verificar la propiedad de este dominio o perfil. Si especificó un perfil de github, se le pedirá que cree un repositorio público con el nombre deseado.

Algún tiempo después de la confirmación, se creará su GroupId y podremos pasar al siguiente paso, la configuración de Gradle.

Configuración de Gradle

Al momento de escribir este artículo, no encontré complementos de Gradle que pudieran ayudar con la publicación del artefacto. Lo el único complemento que encontré, sin embargo, el autor se negó a respaldarlo más. Por lo tanto, decidí hacer todo yo mismo, ya que no es demasiado difícil hacerlo.

Lo primero que hay que averiguar son los requisitos de publicación de Sonatype. Ellos son los siguientes:

  • Disponibilidad de códigos fuente y JavaDoc, es decir. deben asistir -sources.jar и-javadoc.jar archivos Como se indica en la documentación, si no es posible proporcionar códigos fuente o documentación, puede hacer un dummy -sources.jar o -javadoc.jar con un simple LÉAME dentro para pasar la prueba.
  • Todos los archivos deben estar firmados con GPG/PGPY .asc el archivo que contiene la firma debe incluirse para cada archivo.
  • disponibilidad pom expediente
  • Valores correctos groupId, artifactId и version. La versión puede ser una cadena arbitraria y no puede terminar con -SNAPSHOT
  • Presencia requerida name, description и url
  • La presencia de información sobre la licencia, los desarrolladores y el sistema de control de versiones.

Estas son las reglas básicas que se deben seguir a la hora de publicar. Información completa disponible aquí.

Implementamos estos requisitos en build.gradle archivo. Primero, agreguemos toda la información necesaria sobre los desarrolladores, las licencias, el sistema de control de versiones y también configuremos la URL, el nombre y la descripción del proyecto. Escribamos un método simple para esto:

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 protected]'
                }
            }
        }
    }
}

A continuación, debe especificar que durante el ensamblaje generado -sources.jar и-javadoc.jar archivos Para esta sección java necesitas agregar lo siguiente:

java {
    withJavadocJar()
    withSourcesJar()
}

Pasemos al último requisito, configurar una firma GPG/PGP. Para hacer esto, conecte el complemento signing:

plugins {
    id 'signing'
}

Y añade una sección:

signing {
    sign publishing.publications
}

Finalmente, agreguemos una 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
            }
        }
    }
}

es sonatypeUsuario и sonatypeContraseña variables que contienen el nombre de usuario y la contraseña creados durante el registro en sonatype.org.

Así la final build.gradle se verá así:

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 '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 protected]'
                }
            }
        }
    }
}

Quiero señalar que obtenemos la versión de la variable de entorno: System.getenv('RELEASE_VERSION'). Lo expondremos durante el montaje y lo tomaremos del nombre de la etiqueta.

Generación de claves PGP

Uno de los requisitos de Sonatype es que todos los archivos estén firmados con una clave GPG/PGP. Para esto vamos aquí y descargue la utilidad GnuPG para su sistema operativo.

  • Generamos un par de claves: gpg --gen-key, ingrese un nombre de usuario, correo electrónico y también establezca una contraseña.
  • Encontramos id nuestra clave con el comando: gpg --list-secret-keys --keyid-format short. El ID se especificará después de la barra inclinada, por ejemplo: rsa2048/9B695056
  • Publicación de la clave pública en el servidor https://keys.openpgp.org dominio: gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056
  • Exportamos la clave secreta a un lugar arbitrario, la necesitaremos en el futuro: gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg

Configuración de acciones de Github

Pasemos a la etapa final, configure la compilación y la publicación automática usando Github Actions.
Github Actions es una función que te permite automatizar el flujo de trabajo implementando un ciclo completo de CI/CD. La compilación, la prueba y la implementación pueden activarse mediante varios eventos: inserción de código, creación de versiones o problemas. Esta funcionalidad es absolutamente gratuita para los repositorios públicos.

En esta sección, le mostraré cómo configurar el código de compilación e inserción e implementarlo en el repositorio de Sonatype en el momento del lanzamiento, así como configurar secretos.

Establecemos secretos

Para el ensamblaje y la implementación automáticos, necesitamos una serie de valores secretos, como la identificación de la clave, la contraseña que ingresamos al generar la clave, la clave PGP en sí y el inicio de sesión/contraseña de Sonatype. Puede configurarlos en una sección especial en la configuración del repositorio:

Uso de acciones de Gradle y Github para publicar un proyecto Java en el repositorio central de Sonatype Maven

Establecemos las siguientes variables:

  • SONATYPE_USERNAME / SONATYPE_PASSWORD - nombre de usuario / contraseña que ingresamos al registrarnos en Sonatype
  • SIGNING_KEYID/SIGNING_PASSWORD: ID de clave PGP y contraseña establecidos durante la generación.

Quiero detenerme en la variable GPG_KEY_CONTENTS con más detalle. El hecho es que para la publicación necesitamos una clave PGP privada. Para publicarlo en los secretos, usé instrucción y además realizó una serie de acciones.

  • Encriptemos nuestra clave con gpg: gpg --symmetric --cipher-algo AES256 9B695056.gpgintroduciendo una contraseña. Debe colocarse en una variable: SECRET_PASSPHRASE
  • Traduzcamos la clave cifrada recibida a un formato de texto usando base64: base64 9B695056.gpg.gpg > 9B695056.txt. El contenido se colocará en la variable: GPG_KEY_CONTENTS.

Configuración de compilación al enviar código y crear relaciones públicas

Primero necesita crear una carpeta en la raíz de su proyecto: .github/workflows.

En él, marque el 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: 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}}

Este flujo de trabajo se ejecutará al empujar a las sucursales master, dev и testing, también al crear solicitudes de extracción.

La sección de trabajos especifica los pasos que se ejecutarán en los eventos especificados. En este caso, nos basaremos en la última versión de ubuntu, usaremos Java 8 y también usaremos el complemento para Gradle eskatos/gradle-command-action@v1que, utilizando la última versión del constructor, ejecutará los comandos especificados en arguments. Variables secrets.SONATYPE_USERNAME и secrets.SONATYPE_PASSWORD estos son los secretos que preguntamos antes.

Los resultados de la compilación se reflejarán en la pestaña Acciones:

Uso de acciones de Gradle y Github para publicar un proyecto Java en el repositorio central de Sonatype Maven

Implementación automática cuando se lanza una nueva versión

Vamos a crear un archivo de flujo de trabajo separado para la implementación automática 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}}

El archivo es casi idéntico al anterior, excepto por el evento en el que se activará. En este caso, este es el evento de crear una etiqueta con un nombre que comienza con v.

Antes de la implementación, debemos extraer la clave PGP de los secretos y colocarla en la raíz del proyecto, así como descifrarla. A continuación, debemos establecer una variable de entorno especial RELEASE_VERSION al que nos referimos gradle.build archivo. Todo esto se hace en la sección Prepare to publish. Obtenemos nuestra clave de la variable GPG_KEY_CONTENTS, la traducimos a un archivo gpg y luego la desciframos colocándola en el archivo. secret.gpg.

A continuación, pasamos a una variable especial. GITHUB_REF, de donde podemos obtener la versión que configuramos al crear la etiqueta. Esta variable es relevante en este caso. refs/tags/v0.0.2 del cual cortamos los primeros 11 caracteres para obtener una versión específica. A continuación, usamos los comandos estándar de Gradle para publicar: test publish

Comprobación de los resultados de la implementación en el repositorio de Sonatype

Después de crear la versión, debe comenzar el flujo de trabajo descrito en la sección anterior. Para ello, crea una versión:

Uso de acciones de Gradle y Github para publicar un proyecto Java en el repositorio central de Sonatype Maven

el nombre de la etiqueta debe comenzar con v. Si después de hacer clic en Publicar lanzamiento, el flujo de trabajo se completa con éxito, podemos ir a Sonatype Nexus asegurarse:

Uso de acciones de Gradle y Github para publicar un proyecto Java en el repositorio central de Sonatype Maven

El artefacto apareció en el repositorio Staging. Inmediatamente aparece en el estado Abierto, luego debe ser transferido manualmente al estado Cerrado presionando el botón correspondiente. Después de comprobar que se cumplen todos los requisitos, el artefacto pasa al estado Cerrar y ya no está disponible para su modificación. De esta forma, terminará en MavenCentral. Si todo está bien, puede presionar el botón tortugitas, y el artefacto terminará en el repositorio de Sonatype.

Para que el artefacto ingrese a MavenCentral, debe solicitarlo en la tarea que creamos al principio. Solo necesita hacer esto una vez, por lo que publicamos por primera vez. En tiempos posteriores, esto no es obligatorio, todo se sincronizará automáticamente. Activaron la sincronización para mí rápidamente, pero el artefacto tardó unos 5 días en estar disponible en MavenCentral.

Eso es todo, hemos publicado nuestro artefacto en MavenCentral.

Enlaces de interés

  • Similar artículo, solo publicar a través de maven
  • Staging repositorio Sonatipo
  • Jira Sonatype en el que crear la tarea
  • ejemplo repositorio donde está todo configurado

Fuente: habr.com