Utilizziamo Gradle e Github Actions per pubblicare il progetto Java nel Sonatype Maven Central Repository

In questo articolo voglio esaminare dettagliatamente il processo di pubblicazione da zero di un artefatto Java tramite Github Actions nel Sonatype Maven Central Repository utilizzando il build tool Gradle.

Ho deciso di scrivere questo articolo a causa della mancanza di un buon tutorial in un unico luogo. Tutte le informazioni sono state raccolte a pezzi da diverse fonti, tra l'altro non sempre aggiornate. Se ti interessa, benvenuto nel resto dell'articolo.

Creazione di un repository in Sonatype

La prima fase consiste nel creare un repository in Sonatype Maven Central. Per fare ciò, dobbiamo qui, registrarci e creare un nuovo ticket per richiedere la creazione del repository. Inseriamo il nostro GroupId del progetto, URL del progetto link al progetto e SCM url link al sistema di controllo versioni in cui si trova il progetto. GroupId qui deve avere il formato com.example, com.example.domain, com.example.testsupport, e può anche essere un link al tuo GitHub: github.com/yourusername -> io.github.yourusername. In ogni caso, dovrai confermare la proprietà di questo dominio o profilo. Se hai indicato un profilo GitHub, ti verrà chiesto di creare un repository pubblico con il nome richiesto.

Dopo un po' di tempo dall'approvazione, il tuo GroupId verrà creato e possiamo passare alla fase successiva, la configurazione di Gradle.

Configurazione di Gradle

Al momento della stesura dell'articolo, non ho trovato plugin per Gradle che potessero aiutare con la pubblicazione dell'artefatto. Questo L'unico plugin che ho trovato, tuttavia, l'autore ha smesso di supportarlo. Quindi ho deciso di fare tutto da solo, per fortuna non è troppo complicato.

La prima cosa da chiarire sono i requisiti di Sonatype per la pubblicazione. Essi sono i seguenti:

  • La presenza dei codici sorgente e del JavaDoc, ovvero devono essere presenti -sources.jar e-javadoc.jar file. Come indicato nella documentazione, se non è possibile fornire i codici sorgente o la documentazione, si può creare un file vuoto -sources.jar o -javadoc.jar con un semplice README all'interno, per superare la verifica.
  • Tutti i file devono essere firmati tramite GPG/PGP, e .asc il file contenente la firma deve essere incluso per ogni file.
  • Disponibilità pom file
  • Valori corretti groupId, artifactId e version. La versione può essere una stringa arbitraria e non può terminare -SNAPSHOT
  • È necessario includere name, descrizione e url
  • La presenza di informazioni sulla licenza, sugli sviluppatori e sul sistema di controllo versioni

Queste sono le regole principali che devono essere rispettate durante la pubblicazione. Informazioni complete sono disponibili qui.

Implementiamo questi requisiti nel build.gradle file. Per iniziare, aggiungiamo tutte le informazioni necessarie sugli sviluppatori, sulla licenza, sul sistema di controllo versioni, e impostiamo url, nome e descrizione del progetto. Per questo scriviamo un semplice metodo:

def customizePom(pom) {
    pom.withXml {
        def root = asNode()

        root.dependencies.removeAll { dep ->
            dep.scope == "test"
        }

        root.children().last() + {
            resolveStrategy = DELEGATE_FIRST

            description 'Una descrizione dell'artefatto'
            name 'Nome dell'artefatto'
            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 Licenza Apache, Versione 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 'NomeSviluppatore'
                    email 'email@dev.ru'
                }
            }
        }
    }
}

Dopo, dobbiamo specificare che durante la build devono essere generati -sources.jar e-javadoc.jar file. Per fare ciò, nella sezione java dobbiamo aggiungere quanto segue:

java {
    withJavadocJar()
    withSourcesJar()
}

Passiamo all'ultimo requisito, la configurazione della firma GPG/PGP. Per fare questo, colleghiamo il plugin signing:

plugins {
    id 'signing'
}

E aggiungiamo la sezione:

signing {
    sign publishing.publications
}

Infine, aggiungiamo la sezione 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
            }
        }
    }
}

Qui sonatypeUsername e sonatypePassword variabili che contengono il nome utente e la password creati al momento della registrazione su sonatype.org.

Pertanto, il codice finale build.gradle sarà simile a questo:

Codice completo di 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@dev.ru'
                }
            }
        }
    }
}

Voglio notare che otteniamo la versione dalla variabile ambiente: System.getenv('RELEASE_VERSION'). Imposteremo questa durante la build e la prenderemo dal nome del tag.

Generazione della chiave PGP

Uno dei requisiti di Sonatype è la firma di tutti i file utilizzando una chiave GPG/PGP. Per fare questo, andiamo qui e scarichiamo l'utilità GnuPG per il nostro sistema operativo.

  • Generiamo una coppia di chiavi: gpg --gen-key, inserendo nome utente, e-mail, e impostando una password.
  • Scopriamo id la nostra chiave con il comando: gpg --list-secret-keys --keyid-format short. L'Id sarà indicato dopo la barra, ad esempio: rsa2048/9B695056
  • Pubbliciamo la chiave pubblica sul server https://keys.openpgp.org con il comando: gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056
  • Esportiamo la chiave segreta in un luogo a nostra scelta, ne avremo bisogno in seguito: gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg

Configurazione di Github Actions

Passiamo all'ultima fase, configuriamo la build e la pubblicazione automatica utilizzando Github Actions.
Github Actions è una funzionalità che consente di automatizzare il flusso di lavoro, implementando l'intero ciclo CI/CD. Build, test e deployment possono essere attivati da vari eventi: push di codice, creazione di release o issues. Questa funzionalità è completamente gratuita per i repository pubblici.

In questa sezione mostrerò come configurare la build e il push del codice e il deployment nel repository Sonatype al momento del rilascio, oltre alla configurazione dei segreti.

Impostiamo i segreti

Per la build e il deployment automatici abbiamo bisogno di una serie di valori segreti, come l'id della chiave, la password inserita durante la generazione della chiave, la chiave PGP stessa e le credenziali di accesso a Sonatype. Possono essere impostati in una sezione speciale delle impostazioni del repository:

Utilizziamo Gradle e Github Actions per pubblicare il progetto Java nel Sonatype Maven Central Repository

Impostiamo le seguenti variabili:

  • SONATYPE_USERNAME/SONATYPE_PASSWORD — credenziali utilizzate durante la registrazione in Sonatype
  • SIGNING_KEYID/SIGNING_PASSWORD — id della chiave PGP e password impostata durante la generazione.

Vorrei soffermarmi un attimo sulla variabile GPG_KEY_CONTENTS. Il fatto è che per la pubblicazione abbiamo bisogno della chiave PGP segreta. Per posizionarla nei segreti, ho utilizzato istruzione e ho compiuto una serie di azioni supplementari.

  • Criptiamo la nostra chiave con gpg: gpg --symmetric --cipher-algo AES256 9B695056.gpg, inserendo la password. Questa deve essere memorizzata nella variabile: SECRET_PASSPHRASE
  • Convertiamo la chiave criptata in formato testo usando base64: base64 9B695056.gpg.gpg > 9B695056.txt. Il contenuto sarà memorizzato nella variabile: GPG_KEY_CONTENTS.

Configurazione della build al momento del push del codice e della creazione della PR

Per iniziare, bisogna creare una cartella nella radice del progetto: .github/workflows.

In essa posizionare un file, ad esempio, gradle-ci-build.yml con il seguente contenuto:

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}}

Questo flusso di lavoro verrà eseguito al momento del push nei rami master, dev e testing, così come alla creazione di richieste di pull.

Nella sezione jobs sono indicati i passi che devono essere eseguiti agli eventi specificati. In questo caso, compileremo sulla versione più recente di ubuntu, utilizzeremo Java 8 e useremo il plugin per Gradle eskatos/gradle-command-action@v1, che, utilizzando l'ultima versione del builder, eseguirà i comandi indicati in argomenti. Le variabili secrets.SONATYPE_USERNAME e secrets.SONATYPE_PASSWORD questi sono i segreti che abbiamo impostato in precedenza.

I risultati della build saranno visualizzati nella scheda Azioni:

Utilizziamo Gradle e Github Actions per pubblicare il progetto Java nel Sonatype Maven Central Repository

Autodeploy al rilascio di una nuova versione

Per l'autodeploy creeremo un file di flusso di lavoro separato gradle-ci-publish.yml:

name: publish

on:
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Imposta JDK 8
        uses: actions/setup-java@v1
        with:
          java-version: 8

      - name: Preparati a pubblicare
        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: Pubblica 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}}

Il file è praticamente identico a quello precedente, ad eccezione dell'evento che lo attiverà. In questo caso, si tratta di un evento di creazione di un tag con un nome che inizia con v.

Prima del deploy, dobbiamo estrarre la chiave PGP dai segreti e posizionarla nella radice del progetto, nonché decriptarla. Successivamente, dobbiamo impostare una variabile ambientale speciale RELEASE_VERSION alla quale accediamo in gradle.build file. Tutto questo viene fatto nella sezione Preparati a pubblicare. Otteniamo la nostra chiave dalla variabile GPG_KEY_CONTENTS, la convertiamo in un file gpg, quindi la decriptiamo, posizionandola in un file secret.gpg.

Successivamente, accediamo a una variabile speciale GITHUB_REF, da cui possiamo estrarre la versione che abbiamo impostato durante la creazione del tag. Questa variabile in questo caso ha il valore refs/tags/v0.0.2 da cui ritagliamo i primi 11 caratteri per ottenere specificamente la versione. Successivamente, utilizziamo normalmente i comandi Gradle per la pubblicazione: test publish

Controlla i risultati del deploy nel repository Sonatype

Dopo aver creato il rilascio, dovrebbe avviarsi il flusso di lavoro descritto nella sezione precedente. Per fare ciò, creiamo un rilascio:

Utilizziamo Gradle e Github Actions per pubblicare il progetto Java nel Sonatype Maven Central Repository

e il nome del tag deve iniziare con v. Se dopo aver premuto Pubblica rilascio, il flusso di lavoro è completato con successo, possiamo accedere a Sonatype Nexus per verificare:

Utilizziamo Gradle e Github Actions per pubblicare il progetto Java nel Sonatype Maven Central Repository

L'artefatto è apparso nel repository Staging. Inizialmente appare con stato Aperto, successivamente deve essere trasferito manualmente allo stato Chiuso, premendo il pulsante corrispondente. Dopo aver verificato il rispetto di tutti i requisiti, l'artefatto passa allo stato Chiuso e non è più disponibile per modifiche. In questo modo passerà a MavenCentral. Se tutto è in ordine, possiamo premere il pulsante Release, e in questo modo l'artefatto sarà inviato al repository Sonatype.

Per far sì che l'artefatto venga incluso in MavenCentral, è necessario richiederlo nella task che abbiamo creato all'inizio. Questo deve essere fatto solo una volta, poiché pubblichiamo per la prima volta. Negli accessi successivi non è necessaria, tutto sarà sincronizzato automaticamente. Hanno attivato la sincronizzazione rapidamente, ma affinché l'artefatto fosse disponibile in MavenCentral sono trascorsi circa 5 giorni.

E con questo è tutto, abbiamo pubblicato il nostro artefatto in MavenCentral.

Link utili

  • Simile articolo, solo pubblicazione tramite maven
  • Staging repository Sonatype
  • Jira Sonatype, in cui è necessario creare una task
  • Esempio repository, dove tutto questo è configurato

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster