W tym artykule chciałbym szczegółowo omówić proces publikacji artefaktu Java od podstaw przez Github Actions w repozytorium Sonatype Maven Central, używając Gradle jako kompilatora.
Postanowiłem napisać ten artykuł z powodu braku porządnego samouczka w jednym miejscu. Całe informacje musiałem zbierać z różnych źródeł, które nie były do końca aktualne. Kogo to interesuje, zapraszam do przeczytania.
Tworzenie repozytorium w Sonatype
Pierwszym krokiem jest stworzenie repozytorium w Sonatype Maven Central. W tym celu idziemy , rejestrujemy się i tworzymy nowe zadanie z prośbą o utworzenie repozytorium. Wpisujemy nasz GroupId projektu, Adres URL projektu link do projektu oraz adres SCM link do systemu kontroli wersji, w którym znajduje się projekt. GroupId Powinien mieć postać com.example, com.example.domain, com.example.testsupport, a także może być w postaci linku do Twojego Githuba: -> io.github.yourusername. W każdym razie, będziesz musiał potwierdzić posiadanie tej domeny lub profilu. Jeśli wskazałeś profil na Githubie, poproszą o utworzenie publicznego repozytorium o właściwej nazwie.
Po pewnym czasie po potwierdzeniu Twój GroupId zostanie utworzony i możemy przejść do następnego kroku, konfiguracji Gradle.
Konfigurujemy Gradle
W momencie pisania artykułu nie znalazłem wtyczek do Gradle, które mogłyby pomóc w publikacji artefaktu. Jedyną wtyczką, którą znalazłem, autor zrezygnował z dalszego wsparcia. Dlatego postanowiłem zrobić wszystko samodzielnie, na szczęście nie jest to zbyt skomplikowane.
Pierwszą rzeczą, którą trzeba ustalić, są wymagania Sonatype do publikacji. Oto one:
- Posiadanie kodu źródłowego i JavaDoc, tzn. muszą być obecne
-sources.jari-javadoc.jarpliki. Jak podano w dokumentacji, jeśli nie można dostarczyć kodów źródłowych lub dokumentacji, można stworzyć puste pliki-sources.jarlub-javadoc.jarz prostym README w środku, aby przejść weryfikację. - Wszystkie pliki muszą być podpisane za pomocą
GPG/PGP, i.ascplik, który zawiera podpis, musi być dołączony do każdego pliku. - Obecność
pompliku - Prawidłowe wartości
groupId,artifactIdiversion. Wersja może być dowolnym ciągiem znaków i nie może się kończyć-SNAPSHOT - Obecność
name,opisiurl - Obecność informacji o licencji, deweloperach i systemie kontroli wersji
To są podstawowe zasady, które muszą być przestrzegane podczas publikacji. Pełne informacje są dostępne .
Realizujemy te wymagania w build.gradle pliku. Na początek dodamy wszystkie potrzebne informacje o deweloperach, licencji, systemie kontroli wersji, a także ustalimy URL, nazwę i opis projektu. W tym celu napiszemy prostą metodę:
def customizePom(pom) {
pom.withXml {
def root = asNode()
root.dependencies.removeAll { dep ->
dep.scope == "test"
}
root.children().last() + {
resolveStrategy = DELEGATE_FIRST
description 'Opis artefaktu'
name 'Nazwa artefaktu'
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 '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.pl'
}
}
}
}
}Następnie musimy wskazać, aby podczas budowy zostały wygenerowane -sources.jar i-javadoc.jar pliki. W tym celu do sekcji java należy dodać następujące:
java {
withJavadocJar()
withSourcesJar()
}Przejdźmy do ostatniego wymagania, konfigurowania podpisu GPG/PGP. W tym celu podłączymy wtyczkę signing:
plugins {
id 'signing'
}I dodamy sekcję:
signing {
sign publishing.publications
}Na koniec dodamy sekcję 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
}
}
}
}Tutaj sonatypeUsername i sonatypePassword zmienne, które zawierają nazwę użytkownika i hasło, utworzone podczas rejestracji na .
W ten sposób, finalny build.gradle będzie wyglądać następująco:
Pełny kod 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.pl'
}
}
}
}
}Chcę zauważyć, że wersję uzyskujemy z zmiennej środowiskowej: System.getenv('RELEASE_VERSION'). Będziemy ją ustawiać podczas budowy i brać z nazwy tagu.
Generowanie klucza PGP
Jednym z wymogów Sonatype jest podpisanie wszystkich plików za pomocą klucza GPG/PGP. W tym celu przechodzimy i pobieramy narzędzie GnuPG dla swojego systemu operacyjnego.
- Generujemy parę kluczy:
gpg --gen-key, wprowadzamy nazwę użytkownika, e-mail oraz hasło. - Wyjaśniamy
idnasz klucz komendą:gpg --list-secret-keys --keyid-format short. Id będzie podany po ukośniku, na przykład: rsa2048/9B695056 - Publikujemy klucz publiczny na serwerze poleceniem:
gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056 - Eksportujemy klucz sekretu w dowolne miejsce, będzie nam potrzebny później:
gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg
Konfigurujemy Github Actions
Przejdźmy do ostatniego etapu, skonfigurujmy budowę i automatyczną publikację, używając Github Actions.
Github Actions – funkcjonalność umożliwiająca automatyzację procesu roboczego, realizująca pełny cykl CI/CD. Budowanie, testowanie i wdrażanie mogą być wywoływane przez różne zdarzenia: push kodu, tworzenie wydania lub issue. Ta funkcjonalność jest całkowicie darmowa dla publicznych repozytoriów.
W tej sekcji pokażę, jak skonfigurować budowanie, push kodu i wdrażanie w repozytorium Sonatype podczas publikacji wydania oraz jak skonfigurować sekrety.
Ustalamy sekrety
Do automatycznego budowania i wdrażania będziemy potrzebować kilku sekretnych wartości, takich jak id klucza, hasło, które wprowadziliśmy podczas generowania klucza, sam klucz PGP, a także login/hasło do Sonatype. Można je ustalić w specjalnej sekcji w ustawieniach repozytorium:

Ustalamy następujące zmienne:
- SONATYPE_USERNAME/SONATYPE_PASSWORD — login/hasło, które wprowadziliśmy podczas rejestracji w Sonatype
- SIGNING_KEYID/SIGNING_PASSWORD — id klucza PGP oraz hasło ustalone podczas generowania.
Chcę się zatrzymać na zmiennej GPG_KEY_CONTENTS. Chodzi o to, że do publikacji jest nam potrzebny prywatny klucz PGP. Aby umieścić go w sekretach, skorzystałem z i dodatkowo wykonałem szereg działań.
- Zaszyfrujmy nasz klucz za pomocą gpg:
gpg --symmetric --cipher-algo AES256 9B695056.gpg, wprowadzając hasło. Należy umieścić je w zmiennej: SECRET_PASSPHRASE - Przekształćmy otrzymany zaszyfrowany klucz na tekstowy format za pomocą base64:
base64 9B695056.gpg.gpg > 9B695056.txt. Zawartość umieścimy w zmiennej: GPG_KEY_CONTENTS.
Konfiguracja budowania przy pushu kodu i tworzeniu PR
Na początek należy stworzyć folder w głównym katalogu projektu: .github/workflows.
W nim umieścić plik, na przykład, gradle-ci-build.yml z następującą zawartością:
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: Buduj za pomocą Gradle
uses: eskatos/gradle-command-action@v1
with:
gradle-version: current
arguments: build -PsonatypeUsername=${{secrets.SONATYPE_USERNAME}} -PsonatypePassword=${{secrets.SONATYPE_PASSWORD}}Ten proces roboczy będzie wykonywany przy pushu do gałęzi master, dev i testing, jak również przy tworzeniu żądań pull.
W sekcji jobs podane są kroki, które powinny zostać wykonane przy wskazanych zdarzeniach. W tym przypadku będziemy budować na najnowszej wersji ubuntu, używać Java 8, a także korzystać z wtyczki dla Gradle eskatos/gradle-command-action@v1, która, korzystając z najnowszej wersji narzędzia, uruchomi komendy podane w arguments. Zmienne secrets.SONATYPE_USERNAME i secrets.SONATYPE_PASSWORD to są sekrety, które wcześniej ustawiliśmy.
Wyniki budowy będą odzwierciedlone na karcie Akcje:

Autodeploy przy wydaniu nowej wersji
Aby zrealizować autodeploy, stworzymy osobny plik roboczy 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}}Plik jest praktycznie identyczny z poprzednim, z wyjątkiem zdarzenia, które go uruchomi. W tym przypadku jest to zdarzenie tworzenia tagu o nazwie zaczynającej się na v.
Przed wdrożeniem musimy wyciągnąć klucz PGP z sekretów i umieścić go w głównym katalogu projektu oraz go odszyfrować. Następnie musimy ustawić specjalną zmienną środowiskową RELEASE_VERSION do której odwołujemy się w gradle.build pliku. Wszystko to jest zrealizowane w sekcji Prepare to publish. Otrzymujemy nasz klucz z zmiennej GPG_KEY_CONTENTS, przekształcamy go w plik gpg, a następnie go odszyfrowujemy, umieszczając w pliku secret.gpg.
Następnie odwołujemy się do specjalnej zmiennej GITHUB_REF, z której możemy wyciągnąć wersję, którą ustaliliśmy podczas tworzenia tagu. Ta zmienna w tym przypadku ma wartość refs/tags/v0.0.2 z której odcinamy pierwsze 11 znaków, aby uzyskać konkretną wersję. Następnie standardowo używamy poleceń Gradle do publikacji: test publish
Sprawdzenie wyników wdrożenia w repozytorium Sonatype
Po stworzeniu wydania powinien się uruchomić proces roboczy opisany w poprzedniej sekcji. W tym celu tworzymy wydanie:

przy czym nazwa tagu powinna zaczynać się na v. Jeśli po naciśnięciu Publish release, proces roboczy zakończy się sukcesem, możemy wejść w aby się o tym przekonać:

Artefakt pojawił się w repozytorium Staging. Od razu pojawia się w statusie Open, a następnie należy ręcznie zmienić jego status na Close, klikając odpowiedni przycisk. Po sprawdzeniu spełnienia wszystkich wymagań artefakt przechodzi do statusu Close i nie jest już dostępny do zmiany. W tej formie trafi do MavenCentral. Jeśli wszystko jest w porządku, można kliknąć przycisk Release, przy czym artefakt trafi do repozytorium Sonatype.
Aby artefakt trafił do MavenCentral, należy poprosić o to w zadaniu, które stworzyliśmy na początku. Trzeba to zrobić tylko raz, ponieważ publikujemy po raz pierwszy. W kolejnych razach nie jest to wymagane, wszystko będzie synchronizowane automatycznie. Synchronizacja została włączona szybko, ale aby artefakt stał się dostępny w MavenCentral, minęło około 5 dni.
To wszystko, opublikowaliśmy nasz artefakt w MavenCentral.
Przydatne linki
- Podobna , tylko publikacja przez maven
- Staging Sonatype
- Sonatype, w którym należy stworzyć zadanie
- repozytorium, w którym wszystko jest skonfigurowane
Źródło: habr.com
