Utilizzo delle azioni Gradle e Github per pubblicare il progetto Java nel repository centrale di Sonatype Maven

In questo articolo, voglio dare un'occhiata più da vicino al processo di pubblicazione di un artefatto Java da zero tramite Github Actions nel Sonatype Maven Central Repository utilizzando il builder Gradle.

Ho deciso di scrivere questo articolo a causa della mancanza di un normale tutorial in un unico posto. Tutte le informazioni dovevano essere raccolte pezzo per pezzo da varie fonti, per di più non del tutto fresche. Chi se ne frega, benvenuto sotto cat.

Creazione di un repository in Sonatype

Il primo passo è creare un repository in Sonatype Maven Central. Per questo andiamo qui, registrati e crea una nuova attività, chiedendoci di creare un repository. Guidiamo nel nostro ID gruppo del progetto, URL del progetto collegamento al progetto e URL SCM un collegamento al sistema di controllo della versione in cui si trova il progetto. ID gruppo qui dovrebbe essere nella forma com.example, com.example.domain, com.example.testsupport e può anche essere nella forma di un collegamento al tuo github: github.com/tuonomeutente -> io.github.tuonomeutente. In ogni caso, dovrai verificare la proprietà di questo dominio o profilo. Se hai specificato un profilo github, ti verrà chiesto di creare un repository pubblico con il nome desiderato.

Qualche tempo dopo la conferma, il tuo GroupId verrà creato e potremo passare al passaggio successivo, la configurazione di Gradle.

Configurazione di Gradle

Al momento in cui scrivo, non ho trovato plugin Gradle che potessero aiutare con la pubblicazione dell'artefatto. Essa l'unico plugin che ho trovato, tuttavia, l'autore si è rifiutato di supportarlo ulteriormente. Pertanto, ho deciso di fare tutto da solo, poiché non è troppo difficile farlo.

La prima cosa da capire sono i requisiti di Sonatype per la pubblicazione. Sono i seguenti:

  • Disponibilità di codici sorgente e JavaDoc, ie. deve frequentare -sources.jar и-javadoc.jar File. Come indicato nella documentazione, se non è possibile fornire codici sorgente o documentazione, è possibile creare un manichino -sources.jar o -javadoc.jar con un semplice README all'interno per superare il test.
  • Tutti i file devono essere firmati con GPG/PGPE .asc per ogni file deve essere incluso il file contenente la firma.
  • disponibilità pom файла
  • Valori corretti groupId, artifactId и version. La versione può essere una stringa arbitraria e non può terminare con -SNAPSHOT
  • Presenza richiesta name, description и url
  • La presenza di informazioni sulla licenza, gli sviluppatori e il sistema di controllo della versione

Queste sono le regole di base che devono essere seguite durante la pubblicazione. Informazioni complete disponibili qui.

Implementiamo questi requisiti in build.gradle file. Innanzitutto, aggiungiamo tutte le informazioni necessarie su sviluppatori, licenze, sistema di controllo della versione e impostiamo anche l'URL, il nome e la descrizione del progetto. Scriviamo un metodo semplice per questo:

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

Successivamente, è necessario specificarlo durante l'assembly generato -sources.jar и-javadoc.jar File. Per questa sezione java è necessario aggiungere quanto segue:

java {
    withJavadocJar()
    withSourcesJar()
}

Passiamo all'ultimo requisito, impostare una firma GPG/PGP. Per fare ciò, collega il plugin signing:

plugins {
    id 'signing'
}

E aggiungi una sezione:

signing {
    sign publishing.publications
}

Infine, aggiungiamo una 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 sonatypeNomeutente и sonatypePassword variabili contenenti il ​​login e la password creati durante la registrazione su sonatype.org.

Così la finale build.gradle sarà simile a questo:

Codice build.gradle completo

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

Voglio notare che otteniamo la versione dalla variabile d'ambiente: System.getenv('RELEASE_VERSION'). Lo esporremo durante l'assemblaggio e lo prenderemo dal nome del tag.

Generazione di chiavi PGP

Uno dei requisiti di Sonatype è che tutti i file siano firmati con una chiave GPG/PGP. Per questo andiamo qui e scarica l'utility GnuPG per il tuo sistema operativo.

  • Generiamo una coppia di chiavi: gpg --gen-key, inserisci un nome utente, un'e-mail e imposta anche una password.
  • Lo scopriamo id la nostra chiave con il comando: gpg --list-secret-keys --keyid-format short. L'ID verrà specificato dopo la barra, ad esempio: rsa2048/9B695056
  • Pubblicazione della chiave pubblica sul server https://keys.openpgp.org per comando: gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056
  • Esportiamo la chiave segreta in un luogo arbitrario, ne avremo bisogno in futuro: gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg

Configurazione delle azioni Github

Passiamo alla fase finale, impostiamo la build e pubblichiamo automaticamente utilizzando Github Actions.
Github Actions è una funzionalità che consente di automatizzare il flusso di lavoro implementando un ciclo CI/CD completo. La compilazione, il test e la distribuzione possono essere attivati ​​da vari eventi: push del codice, creazione della versione o problemi. Questa funzionalità è assolutamente gratuita per i repository pubblici.

In questa sezione, ti mostrerò come configurare il codice di compilazione e push e distribuirlo nel repository Sonatype al momento del rilascio, oltre a impostare i segreti.

Stabiliamo segreti

Per l'assemblaggio e la distribuzione automatici, abbiamo bisogno di un numero di valori segreti, come l'id della chiave, la password che abbiamo inserito durante la generazione della chiave, la chiave PGP stessa e il login/password Sonatype. Puoi impostarli in una sezione speciale nelle impostazioni del repository:

Utilizzo delle azioni Gradle e Github per pubblicare il progetto Java nel repository centrale di Sonatype Maven

Impostiamo le seguenti variabili:

  • SONATYPE_USERNAME / SONATYPE_PASSWORD - login / password che abbiamo inserito durante la registrazione con Sonatype
  • SIGNING_KEYID/SIGNING_PASSWORD — ID chiave PGP e password impostati durante la generazione.

Voglio soffermarmi sulla variabile GPG_KEY_CONTENTS in modo più dettagliato. Il fatto è che per la pubblicazione abbiamo bisogno di una chiave PGP privata. Per pubblicarlo nei segreti, ho usato istruzione e inoltre ha fatto una serie di azioni.

  • Crittografiamo la nostra chiave con gpg: gpg --symmetric --cipher-algo AES256 9B695056.gpginserendo una password. Dovrebbe essere inserito in una variabile: SECRET_PASSPHRASE
  • Traduciamo la chiave crittografata ricevuta in un modulo di testo utilizzando base64: base64 9B695056.gpg.gpg > 9B695056.txt. Il contenuto verrà inserito nella variabile: GPG_KEY_CONTENTS.

Crea configurazione durante l'inserimento del codice e la creazione di PR

Per prima cosa devi creare una cartella nella root del tuo progetto: .github/workflows.

In esso, contrassegna il 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 durante il push ai rami master, dev и testing, anche durante la creazione di richieste pull.

La sezione lavori specifica i passaggi da eseguire sugli eventi specificati. In questo caso, costruiremo sull'ultima versione di Ubuntu, utilizzeremo Java 8 e utilizzeremo anche il plug-in per Gradle eskatos/gradle-command-action@v1che, utilizzando l'ultima versione del builder, eseguirà i comandi specificati in arguments. Variabili secrets.SONATYPE_USERNAME и secrets.SONATYPE_PASSWORD questi sono i segreti che abbiamo chiesto prima.

I risultati della compilazione si rifletteranno nella scheda Azioni:

Utilizzo delle azioni Gradle e Github per pubblicare il progetto Java nel repository centrale di Sonatype Maven

Distribuzione automatica quando viene rilasciata una nuova versione

Creiamo un file di flusso di lavoro separato per la distribuzione automatica 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}}

Il file è quasi identico al precedente, ad eccezione dell'evento in cui verrà attivato. In questo caso, questo è l'evento di creazione di un tag con un nome che inizia con v.

Prima della distribuzione, dobbiamo estrarre la chiave PGP dai segreti e posizionarla nella radice del progetto, oltre a decrittografarla. Successivamente, dobbiamo impostare una variabile d'ambiente speciale RELEASE_VERSION a cui facciamo riferimento gradle.build file. Tutto questo viene fatto nella sezione Prepare to publish. Otteniamo la nostra chiave dalla variabile GPG_KEY_CONTENTS, la traduciamo in un file gpg, quindi la decodifichiamo inserendola nel file secret.gpg.

Successivamente, passiamo a una variabile speciale GITHUB_REF, da cui possiamo ottenere la versione che abbiamo impostato durante la creazione del tag. Questa variabile è rilevante in questo caso. refs/tags/v0.0.2 da cui abbiamo tagliato i primi 11 caratteri per ottenere una versione specifica. Successivamente, usiamo i comandi Gradle standard per la pubblicazione: test publish

Controllo dei risultati della distribuzione nel repository Sonatype

Una volta creata la versione, dovrebbe iniziare il flusso di lavoro descritto nella sezione precedente. Per fare ciò, crea una versione:

Utilizzo delle azioni Gradle e Github per pubblicare il progetto Java nel repository centrale di Sonatype Maven

il nome del tag deve iniziare con v. Se, dopo aver fatto clic su Pubblica versione, il flusso di lavoro viene completato correttamente, possiamo passare a Nesso sonatipico assicurarsi:

Utilizzo delle azioni Gradle e Github per pubblicare il progetto Java nel repository centrale di Sonatype Maven

L'artefatto è apparso nel repository Staging. Appare subito nello stato Aperto, poi deve essere trasferito manualmente nello stato Chiudi premendo l'apposito pulsante. Dopo aver verificato che tutti i requisiti siano soddisfatti, l'artefatto passa allo stato Chiudi e non è più disponibile per la modifica. In questa forma, finirà in MavenCentral. Se tutto va bene, puoi premere il pulsante Rilasciaree l'artefatto finirà nel repository Sonatype.

Affinché l'artefatto entri in MavenCentral, devi richiederlo nell'attività che abbiamo creato all'inizio. Devi farlo solo una volta, quindi pubblichiamo per la prima volta. In tempi successivi, questo non è richiesto, tutto verrà sincronizzato automaticamente. Hanno attivato rapidamente la sincronizzazione per me, ma ci sono voluti circa 5 giorni prima che l'artefatto diventasse disponibile in MavenCentral.

Questo è tutto, abbiamo pubblicato il nostro artefatto in MavenCentral.

Link utili

  • Un simile articolo, pubblica solo tramite maven
  • Staging deposito sonatipo
  • Jira Sonatype in cui creare l'attività
  • esempio repository dove è tutto impostato

Fonte: habr.com