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

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

Ho deciso di scrivere questo articolo a causa dell'assenza di un tutorial adeguato in un unico luogo. Tutte le informazioni sono state raccolte a pezzi da diverse fonti, per di più non del tutto recenti. A chi è interessato, benvenuti sotto il cat.

Creazione di un repository in Sonatype

La prima fase consiste nella creazione di un repository nel Sonatype Maven Central. Per fare ciò, andiamo qui, ci registriamo e creiamo un nuovo ticket, richiedendo di crearci un repository. Inseriamo il nostro GroupId del progetto, URL del progetto link al progetto e SCM url link al sistema di controllo versione in cui si trova il progetto. GroupId qui dovrebbe 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 dalla conferma, il tuo GroupId sarà creato e possiamo passare al passo successivo, la configurazione di Gradle.

Configuriamo Gradle

Al momento della scrittura 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, dato che non è troppo difficile.

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

  • Presenza del codice sorgente e JavaDoc, cioè devono essere presenti -sources.jar e-javadoc.jar file. Come indicato nella documentazione, se non puoi fornire il codice sorgente o la documentazione, puoi creare un vuoto -sources.jar o -javadoc.jar con un semplice README all'interno, per superare il controllo.
  • Tutti i file devono essere firmati tramite GPG/PGP, e .asc il file contenente la firma deve essere incluso per ogni file.
  • La presenza di pom del file
  • Valori corretti groupId, artifactId e versione. La versione può essere una stringa arbitraria e non può terminare con -SNAPSHOT
  • È necessaria la presenza di name, description e url
  • Presenza di informazioni su licenze, sviluppatori e sistema di controllo versione

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

Implementiamo questi requisiti in build.gradle file. Per iniziare, aggiungiamo tutte le informazioni necessarie sugli sviluppatori, la licenza, il sistema di controllo delle versioni, e definiamo l'url, il nome e la descrizione del progetto. A tale scopo, 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 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 '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@dev.ru'
                }
            }
        }
    }
}

Dopo, è necessario indicare che durante la compilazione devono essere generati -sources.jar e-javadoc.jar file. A tale scopo, nella sezione java è necessario aggiungere quanto segue:

java {
    withJavadocJar()
    withSourcesJar()
}

Passiamo all'ultimo requisito, la configurazione della firma GPG/PGP. Per questo, collega 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 durante la registrazione su sonatype.org.

Quindi, il finale build.gradle sarà strutturato come segue:

Codice completo build.gradle

plugin {
    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 descrizione dell'articolo'
            name 'Nome dell'articolo'
            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 'NomeDev'
                    email 'email@dev.ru'
                }
            }
        }
    }
}

Voglio notare che la versione la otteniamo dalla variabile di 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 questo, andiamo qui e scarichiamo l'utilità GnuPG per il nostro sistema operativo.

  • Generiamo una coppia di chiavi: gpg --gen-key, inseriamo il nome utente, l'e-mail e impostiamo una password.
  • Scopriamo id la nostra chiave con il comando: gpg --list-secret-keys --keyid-format short. L'Id sarà indicato dopo lo slash, per esempio: rsa2048/9B695056
  • Pubblica 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 piacere, ne avremo bisogno in seguito: gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg

Impostiamo Github Actions

Passiamo alla fase finale, impostiamo la build e la pubblicazione automatica utilizzando Github Actions.
Github Actions – una funzionalità che consente di automatizzare il flusso di lavoro, realizzando un ciclo CI/CD completo. La costruzione, i test e il deploy possono essere attivati da vari eventi: push di codice, creazione di un release o di issue. Questa funzionalità è completamente gratuita per i repository pubblici.

In questa sezione mostrerò come configurare la costruzione e il push di codice e il deploy nel repository Sonatype al momento del rilascio, così come la configurazione dei segreti.

Impostiamo i segreti

Per la costruzione automatica e il deploy avremo 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 anche il login/password per Sonatype. Questi possono essere impostati in una sezione speciale nelle 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 — login/password inseriti durante la registrazione in Sonatype
  • SIGNING_KEYID/SIGNING_PASSWORD — id della chiave PGP e password impostata durante la generazione.

Vorrei soffermarmi sulla variabile GPG_KEY_CONTENTS. Infatti, per la pubblicazione abbiamo bisogno della chiave PGP privata. Per inserirla nei segreti, ho utilizzato le istruzioni e inoltre ho messo in atto alcune azioni.

  • Criptiamo la nostra chiave utilizzando gpg: gpg --symmetric --cipher-algo AES256 9B695056.gpg, inserendo la password. Essa deve essere collocata nella variabile: SECRET_PASSPHRASE
  • Convertiamo la chiave criptata ottenuta in un formato di testo utilizzando base64: base64 9B695056.gpg.gpg > 9B695056.txt. Il contenuto verrà collocato nella variabile: GPG_KEY_CONTENTS.

Configurazione della costruzione al push di codice e creazione di PR

Per iniziare, è necessario creare una cartella nella root del tuo progetto: .github/workflows.

All'interno, 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 push nelle branche master, dev e testing, così come alla creazione di richieste di pull.

Nella sezione jobs sono elencati i passaggi che devono essere eseguiti in base agli eventi specificati. In questo caso, compileremo sulla versione più recente di ubuntu, utilizzeremo Java 8 e utilizzeremo anche il plugin per Gradle eskatos/gradle-command-action@v1, che utilizzando l'ultima versione del costruttore eseguirà i comandi specificati in arguments. Variabili secrets.SONATYPE_USERNAME e secrets.SONATYPE_PASSWORD questo è un segreto che abbiamo impostato in precedenza.

I risultati della build saranno riflessi 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 workflow 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 per la pubblicazione
        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 al precedente eccetto per l'evento che lo attiverà. In questo caso, è l'evento di creazione di un tag il cui nome inizia con v.

Prima del deploy, dobbiamo estrarre la chiave PGP dai segreti e posizionarla nella radice del progetto, oltre a decrittarla. Inoltre, dobbiamo impostare una variabile ambientale speciale RELEASE_VERSION alla quale accediamo in gradle.build file. Tutto ciò è fatto nella sezione Preparati per la pubblicazione. Estraiamo la nostra chiave dalla variabile GPG_KEY_CONTENTS, la traduciamo in un file gpg, poi la decrittiamo, ponendola nel file secret.gpg.

Dopodiché ci riferiamo 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 tagliamo i primi 11 caratteri per ottenere specificamente la versione. Poi utilizziamo normalmente i comandi Gradle per la pubblicazione: test publish

Verifica i risultati del deploy nel repository Sonatype

Dopo la creazione del rilascio, dovrebbe avviarsi il workflow descritto nella sezione precedente. Per farlo, creiamo un rilascio:

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

mentre il nome del tag deve iniziare con v. Se, dopo aver cliccato su Pubblica rilascio, il workflow completa correttamente, possiamo andare su Sonatype Nexus per verificarlo:

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

L'artefatto è apparso nel repository di Staging. All'inizio si presenta con stato Aperto, poi deve essere manualmente cambiato in stato Chiuso, premendo il pulsante corrispondente. Dopo aver verificato il soddisfacimento di tutti i requisiti, l'artefatto passa allo stato Chiuso e non è più disponibile per le modifiche. In questo modo verrà inviato a MavenCentral. Se tutto è a posto, puoi premere il pulsante Release, in questo modo l'artefatto verrà inviato al repository Sonatype.

Per far sì che l'artefatto venga inviato a MavenCentral, è necessario fare richiesta in un'attività, che abbiamo creato all'inizio. Questo deve essere fatto solo una volta, poiché è la nostra prima pubblicazione. Nei successivi invii non è necessario farlo, tutto sarà sincronizzato automaticamente. Mi hanno attivato la sincronizzazione rapidamente, ma affinché l'artefatto diventasse disponibile in MavenCentral ci sono voluti circa 5 giorni.

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

Link utili

  • Simile su Habr., solo pubblicazione tramite maven
  • Staging repository Sonatype
  • Jira Sonatype, dove è necessario creare un'attività
  • Esempio repository, dove è tutto configurato

Fonte: habr.com

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