În acest articol, vreau să examinez detaliat procesul de publicare a unui artifact Java de la zero prin Github Actions în Sonatype Maven Central Repository folosind Gradle ca build tool.
Am decis să scriu acest articol din cauza absenței unui tutorial adecvat într-un singur loc. Informațiile trebuiau adunate din diverse surse, care nu erau tocmai recente. Cei interesați sunt bineveniți să continue.
Crearea unui repository în Sonatype
Primul pas este să creăm un repository în Sonatype Maven Central. Pentru aceasta, mergem , ne înregistrăm și creăm o nouă cerere, solicitând crearea unui repository pentru noi. Introducem GroupId proiectului, URL-ul proiectului linkul către proiect și SCM url linkul către sistemul de control al versiunilor în care se află proiectul. GroupId aici ar trebui să fie de forma com.example, com.example.domain, com.example.testsupport, și poate fi și sub forma unui link către contul dvs. de GitHub: -> io.github.yourusername. În orice caz, va trebui să confirmați deținerea acestui domeniu sau profil. Dacă ați specificat un profil GitHub, va fi necesar să creați un repository public cu numele dorit.
După o vreme, după confirmare, GroupId-ul dvs. va fi creat și putem trece la pasul următor, configurarea Gradle.
Configurăm Gradle
La momentul scrierii acestui articol, nu am găsit pluginuri pentru Gradle care să poată ajuta cu publicarea artifactului. singurul plugin pe care l-am găsit, totuși, autorul s-a retras din sprijinul acestuia. Prin urmare, am decis să fac totul pe cont propriu, din fericire, acest lucru nu este prea complicat.
Primul lucru pe care trebuie să-l clarificăm sunt cerințele Sonatype pentru publicare. Acestea sunt următoarele:
- Existence of source codes and JavaDoc, meaning that there must be
-sources.jarși-javadoc.jarfiles. As stated in the documentation, if it's not possible to provide source codes or documentation, you can create a placeholder-sources.jarsau-javadoc.jarc with a simple README inside, just to pass the check. - All files must be signed using
GPG/PGP, și.ascthe file containing the signature must be included for each file. - Prezența
pomfișiere. - Correct values for
groupId,artifactIdșiversion. The version can be any string and cannot end with-SNAPSHOT - The presence of
name,descriptionșiurl - Presence of information regarding the license, developers, and version control system
These are the main rules that must be followed during publication. Complete information is available .
We implement these requirements in build.gradle fișier. În primul rând, vom adăuga toate informațiile necesare despre dezvoltatori, licență, sistemul de control al versiunilor, precum și vom define un url, un nume și o descriere a proiectului. Pentru aceasta, vom scrie o metodă simplă:
def customizePom(pom) {
pom.withXml {
def root = asNode()
root.dependencies.removeAll { dep ->
dep.scope == "test"
}
root.children().last() + {
resolveStrategy = DELEGATE_FIRST
description 'O descriere a artefactului'
name 'Numele artefactului'
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 'Licența Apache, Versiunea 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.ro'
}
}
}
}
}Apoi, trebuie să specificăm că, la compilare, trebuie generate -sources.jar și-javadoc.jar fișiere. Pentru aceasta, în secțiunea java trebuie adăugat următorul cod:
java {
withJavadocJar()
withSourcesJar()
}Să trecem la ultima cerință, configurarea semnăturii GPG/PGP. Pentru aceasta, vom conecta plugin-ul signing:
plugins {
id 'signing'
}Și vom adăuga secțiunea:
signing {
sign publishing.publications
}În final, vom adăuga secțiunea 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
}
}
}
}Aici sonatypeUsername și sonatypePassword variabile care conțin numele de utilizator și parola, create la înregistrarea pe .
Astfel, codul final build.gradle va arăta astfel:
Codul complet 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 'Artifact 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.ro'
}
}
}
}
}Vreau să menționez că versiunea o obținem din variabila de mediu: System.getenv('RELEASE_VERSION'). O vom seta în timpul compilării și o vom lua din numele etichetei.
Generarea cheii PGP
Una dintre cerințele Sonatype este semnarea tuturor fișierelor cu o cheie GPG/PGP. Pentru aceasta, mergem și descărcăm utilitarul GnuPG pentru sistemul nostru de operare.
- Generăm o pereche de chei:
gpg --gen-key, introducem numele de utilizator, e-mailul, precum și stabilim o parolă. - Stabilim
idcheia noastră cu comanda:gpg --list-secret-keys --keyid-format short. Id-ul va fi indicat după slash, de exemplu: rsa2048/9B695056 - Publicăm cheia publică pe server cu comanda:
gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056 - Exportăm cheia secretă într-un loc arbitrar, ne va fi necesară mai târziu:
gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg
Configurăm Github Actions
Trecem la etapa finală, vom configura compilarea și publicarea automată, folosind Github Actions.
Github Actions – o funcționalitate care permite automatizarea fluxului de lucru, implementând un ciclu complet CI/CD. Compilarea, testarea și desfășurarea pot fi declanșate de diferite evenimente: push de cod, crearea unei versiuni sau a unor probleme. Această funcționalitate este complet gratuită pentru repositiile publice.
În această secțiune vă voi arăta cum să configurați compilarea, push-ul de cod și desfășurarea în repositoul Sonatype la lansarea unei versiuni, precum și configurarea secretelor.
Setăm secretele
Pentru compilarea automată și desfășurare avem nevoie de o serie de valori secrete, cum ar fi ID-ul cheii, parola pe care am folosit-o la generarea cheii, cheia PGP în sine, precum și numele utilizatorului/parola pentru Sonatype. Acestea pot fi setate într-o secțiune specială din setările repositoului:

Setăm variabilele următoare:
- SONATYPE_USERNAME/SONATYPE_PASSWORD – numele utilizatorului/parola pe care le-am folosit la înregistrarea în Sonatype
- SIGNING_KEYID/SIGNING_PASSWORD – ID-ul cheii PGP și parola setată la generare.
Vreau să mă opresc în detaliu asupra variabilei GPG_KEY_CONTENTS. Problema este că pentru publicare avem nevoie de cheia PGP privată. Pentru a o introduce în secrete, am folosit și am efectuat o serie de acțiuni suplimentare.
- Să criptăm cheia noastră cu ajutorul GPG:
gpg --symmetric --cipher-algo AES256 9B695056.gpg, introducând parola. Aceasta trebuie plasată în variabila: SECRET_PASSPHRASE - Să convertim cheia criptată obținută într-un format text folosind base64:
base64 9B695056.gpg.gpg > 9B695056.txt. Informațiile vor fi plasate în variabila: GPG_KEY_CONTENTS.
Configurarea compilării la push-ul de cod și crearea PR-ului
Mai întâi trebuie să creați un folder în rădăcina proiectului dumneavoastră: .github/workflows.
În el, plasați un fișier, de exemplu, gradle-ci-build.yml cu următorul conținut:
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}}Acest flux de lucru va fi executat la push-ul în ramurile master, dev și testing, dar și la crearea cererilor de extragere.
În secțiunea jobs sunt indicate pașii care trebuie să fie executați la evenimentele menționate. În acest caz, vom compila pe cea mai recentă versiune de ubuntu, vom folosi Java 8 și, de asemenea, vom utiliza pluginul pentru Gradle eskatos/gradle-command-action@v1, care folosind cea mai recentă versiune a compilatorului va rula comenzile specificate în arguments. Variabilele secrets.SONATYPE_USERNAME și secrets.SONATYPE_PASSWORD acestea sunt secretele pe care le-am definit anterior.
Rezultatele compilării vor fi reflectate în tab-ul Actions:

Autodeploy la lansarea unei noi versiuni
Pentru autodeploy vom crea un fișier de lucru separat 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}}Fișierul este practic identic cu cel precedent, cu excepția evenimentului care îl va declanșa. În acest caz, este evenimentul de creare a unui tag cu un nume care începe cu v.
Înainte de deploy, trebuie să extragem cheia PGP din secrete și să o plasăm în rădăcina proiectului, precum și să o decriptăm. Apoi, trebuie să setăm o variabilă de mediu specială RELEASE_VERSION la care ne referim în gradle.build fișier. Toate acestea se fac în secțiunea Prepare to publish. Obținem cheia noastră din variabila GPG_KEY_CONTENTS, o convertim într-un fișier gpg, apoi o decriptăm, plasând-o în fișierul secret.gpg.
Apoi, ne referim la o variabilă specială GITHUB_REF, din care putem extrage versiunea pe care am definit-o la crearea tagului. Această variabilă are în acest caz valoarea refs/tags/v0.0.2 din care tăiem primele 11 caractere pentru a obține exact versiunea. Apoi, utilizăm în mod standard comenzile Gradle pentru publicare: test publish
Verificarea rezultatelor deploy-ului în depozitul Sonatype
După crearea versiunii, ar trebui să pornească procesul de lucru descris în secțiunea anterioară. Pentru aceasta, creăm o versiune:

în care numele tagului trebuie să înceapă cu v. Dacă după apăsarea Publish release, procesul de lucru reușește, putem accesa pentru a verifica acest lucru:

Artifactul a apărut în repository-ul Staging. Imediat acesta apare în statutul Open, ulterior trebuie să fie mutat manual în statutul Close, apăsând butonul corespunzător. După verificarea îndeplinirii tuturor cerințelor, artifactul trece în statutul Close și nu mai este disponibil pentru modificare. În această formă va ajunge în MavenCentral. Dacă totul este în regulă, puteți apăsa butonul Release, iar artifactul va ajunge în repository-ul Sonatype.
Pentru ca artifactul să ajungă în MavenCentral, este necesar să cerem acest lucru în taskul pe care l-am creat la început. Acest lucru trebuie făcut doar o dată, deoarece publicăm pentru prima dată. În următoarele ocazii acest lucru nu este necesar, totul se va sincroniza automat. Mi-au activat sincronizarea rapid, dar pentru ca artifactul să devină disponibil în MavenCentral a fost nevoie de aproximativ 5 zile.
Asta e tot, am publicat artifactul nostru în MavenCentral.
Linkuri utile
- Similară , doar publicarea prin maven
- Staging Sonatype
- Sonatype, în care trebuie creat un task
- repository, unde este totul configurat
Sursa: habr.com
