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 , 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: -> 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. 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.jare-javadoc.jarfile. Come indicato nella documentazione, se non è possibile fornire i codici sorgente o la documentazione, si può creare un file vuoto-sources.jaro-javadoc.jarcon un semplice README all'interno, per superare la verifica. - Tutti i file devono essere firmati tramite
GPG/PGP, e.ascil file contenente la firma deve essere incluso per ogni file. - Disponibilità
pomfile - Valori corretti
groupId,artifactIdeversion. La versione può essere una stringa arbitraria e non può terminare-SNAPSHOT - È necessario includere
name,descrizioneeurl - 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 .
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 .
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 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
idla 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 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:

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

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:

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 per verificare:

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 , solo pubblicazione tramite maven
- Staging Sonatype
- Sonatype, in cui è necessario creare una task
- repository, dove tutto questo è configurato
Fonte: habr.com
