В тази статия искам подробно да разгледам процеса на публикуване от нулата на Java артефакт чрез Github Actions в хранилището Sonatype Maven Central, използвайки сборщика Gradle.
Реших да напиша тази статия поради липсата на нормален туториал на едно място. Цялата информация трябваше да се събира на парчета от различни източници, и то не съвсем актуални. На всеки, който се интересува, добре дошли под кат.
Създаване на хранилище в Sonatype
На първия етап трябва да създадем хранилище в Sonatype Maven Central. За целта отиваме , регистрираме се и създаваме нова заявка с молба да ни създадат хранилище. Въвеждаме нашия GroupId проект, Проектен URL линк към проекта и SCM url линк към системата за контрол на версиите, в която се намира проектът. GroupId тук трябва да е от вида com.example, com.example.domain, com.example.testsupport, а също така може да бъде и под формата на линк към вашия GitHub: -> io.github.yourusername. Във всеки случай ще трябва да потвърдите собствеността на този домейн или профил. Ако сте посочили профила на GitHub, ще поискат да създадете публично хранилище с необходимото име.
След известно време след потвърждението вашият GroupId ще бъде създаден и можем да преминем към следващата стъпка, конфигурация на Gradle.
Конфигурираме Gradle
Към момента на написване на статията не намерих плъгини за Gradle, които да помогнат с публикуването на артефакта. единственият плъгин, който намерих, обаче авторът отказа да продължи поддръжката му. Затова реших да направя всичко сам, тъй като не е твърде сложно.
Първото, което трябва да изясните, е изискванията на Sonatype за публикуване. Те са следните:
- Наличие на изходен код и JavaDoc, т.е. трябва да присъстват
-sources.jarи-javadoc.jarфайлове. Както е посочено в документацията, ако не е възможно да предоставите изходен код или документация, можете да направите празно-sources.jarили-javadoc.jarс прост README вътре, за да преминете проверката. - Всички файлове трябва да бъдат подписани с помощта на
GPG/PGP, и.ascфайл, съдържащ подписа, трябва да бъде включен за всеки файл. - Наличието на
pomфайла - Коректни стойности
groupId,artifactIdиversion. Версията може да бъде произволен низ и не може да завършва-SNAPSHOT - Необходимо е присъствието на
name,descriptionиurl - Присъствието на информация за лиценз, разработчици и система за контрол на версиите
Това са основните правила, които трябва да бъдат спазвани при публикуване. Пълна информация е налична .
Реализираме тези изисквания в build.gradle файле. Първо добавяме необходимата информация за разработчиците, лиценза, системата за контрол на версиите, а също така задаваме URL, име и описание на проекта. За целта ще напишем прост метод:
def customizePom(pom) {
pom.withXml {
def root = asNode()
root.dependencies.removeAll { dep ->
dep.scope == "test"
}
root.children().last() + {
resolveStrategy = DELEGATE_FIRST
description 'Някакво описание на артефакта'
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 'Лиценз на Apache, версия 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'
}
}
}
}
}След това трябва да посочим, че при изграждането ще се генерират -sources.jar и-javadoc.jar файлове. За целта в секцията java трябва да добавим следното:
java {
withJavadocJar()
withSourcesJar()
}Да преминем към последното изискване, настройката на GPG/PGP подпис. За целта ще активираме плугина signing:
plugins {
id 'signing'
}И добавяме секция:
signing {
sign publishing.publications
}Накрая, добавяме секция 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
}
}
}
}Тук sonatypeUsername и sonatypePassword променливи, съдържащи името на потребителя и паролата, създадени при регистрация на .
Така финалният build.gradle ще изглежда по следния начин:
Пълен код 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 'Небольшое описание артефакта'
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 'Лицензия Apache, версия 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.bg'
}
}
}
}
}Искам да подчертая, че версията я получаваме от променливата на средата: System.getenv('RELEASE_VERSION'). Ще я зададем по време на компилация и ще я вземем от името на тага.
Генериране на PGP ключ
Едно от изискванията на Sonatype е подписването на всички файлове с GPG/PGP ключ. За целта отиваме и сваляме утилитата GnuPG за операционната си система.
- Генерираме ключова двойка:
gpg --gen-key, въвеждаме име на потребителя, e-mail, както и задаваме парола. - Разучаваме
idнашия ключ с командата:gpg --list-secret-keys --keyid-format short. Идентфикаторът ще бъде посочен след наклонената черта, например: rsa2048/9B695056 - Публикуваме публичния ключ на сървъра с командата:
gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056 - Експортираме секретния ключ на произволно място, той ще ни трябва по-късно:
gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg
Настройваме Github Actions
Преминаваме към последния етап, настройваме компилацията и автоматичната публикация, използвайки Github Actions.
Github Actions – функционал, който позволява автоматизиране на работния процес, реализирайки пълен цикъл CI/CD. Сборката, тестването и деплойментът могат да бъдат задействани от различни събития: качване на код, създаване на релийз или issues. Тази функционалност е абсолютно безплатна за публични хранилища.
В този раздел ще покажа как да настроим сборка и качване на код, а също така и деплоймент в Sonatype хранилище при издаване на релийз, както и настройване на секрети.
Задаваме секрети
За автоматична сборка и деплоймент ще ни трябват няколко секретни стойности, като id на ключа, паролата, която въведохме при генерирането на ключа, самият PGP ключ, както и логин/парола за Sonatype. Можем да ги зададем в специален раздел в настройките на хранилището:

Задаваме следните променливи:
- SONATYPE_USERNAME/SONATYPE_PASSWORD — логин/парола, които въведохме при регистрацията в Sonatype
- SIGNING_KEYID/SIGNING_PASSWORD — id на PGP ключа и паролата, зададена при генерирането.
На променливата GPG_KEY_CONTENTS искам да се спра по-подробно. Факт е, че за публикуване ни е необходим затворен PGP ключ. За да го поставим в секретите, използвах и допълнително направих редица действия.
- Шифроваме нашия ключ с помощта на gpg:
gpg --symmetric --cipher-algo AES256 9B695056.gpg, въвеждайки паролата. Тя трябва да бъде поставена в променливата: SECRET_PASSPHRASE - Ще преобразуваме получения шифрован ключ в текстов формат с помощта на base64:
base64 9B695056.gpg.gpg > 9B695056.txt. Съдържанието ще поставим в променливата: GPG_KEY_CONTENTS.
Настройка на сборката при качване на код и създаване на PR
Първо трябва да създадете папка в корена на вашия проект: .github/workflows.
В нея поставете файл, например, gradle-ci-build.yml със следното съдържание:
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}}Този работен процес ще се изпълнява при качване в клоновете master, dev и тест, също така и при създаване на пулл реквестове.
В секцията jobs са посочени стъпките, които трябва да бъдат изпълнени при посочените събития. В този случай ще извършваме сборка на последната версия на ubuntu, ще използваме Java 8, а също така ще ползваме плъгина за Gradle eskatos/gradle-command-action@v1, който, използвайки последната версия на сборщика, ще стартира командите, посочени в arguments. Променливи secrets.SONATYPE_USERNAME и secrets.SONATYPE_PASSWORD това са тайните, които зададохме по-рано.
Резултатите от сглобяването ще бъдат отразени в раздела Actions:

Автоматично разгръщане при освобождаване на нова версия
За автоматичното разгръщане ще създадем отделен файл с работен процес gradle-ci-publish.yml:
name: publish
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Настройка на JDK 8
uses: actions/setup-java@v1
with:
java-version: 8
- name: Подготовка за публикуване
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: Публикуване с 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}}Файлът е практически идентичен на предишния, с изключение на събитието, при което ще се задейства. В случая това е събитие за създаване на таг с име, започващо с v.
Преди разгръщането трябва да извлечем PGP ключа от тайните и да го поставим в основната директория на проекта, както и да го дешифрираме. След това трябва да зададем специална променлива на средата RELEASE_VERSION към която се обръщаме в gradle.build файла. Всичко това е направено в раздела Подготовка за публикуване. Получаваме нашия ключ от променливата GPG_KEY_CONTENTS, превеждаме го в gpg файл, след това го дешифрираме, поставяйки го в файл secret.gpg.
След това се обръщаме към специалната променлива GITHUB_REF, от която можем да извлечем версията, която задрахме при създаването на тага. Тази променлива в случая има стойност refs/tags/v0.0.2 , от която отрязваме първите 11 символа, за да извлечем конкретно версията. След това стандартно използваме командите на Gradle за публикуване: test publish
Проверка на резултатите от разгръщането в хранилището на Sonatype
След създаването на версията трябва да се стартира работният процес, описан в предишния раздел. За целта създаваме версия:

като името на тага трябва да започва с v. Ако след натискане на Publish release, работният процес успешно завърши, можем да влезем в за да се уверим в това:

Артефакт се появява в Staging репозитория. Веднага той е с статус Open, след което трябва ръчно да бъде преведен в статус Close, като натиснете съответния бутон. След проверка на изпълнението на всички изисквания, артефактът преминава в статус Close и повече не е достъпен за промяна. В такъв вид той ще попадне в MavenCentral. Ако всичко е наред, можете да натиснете бутона Release, като в този случай артефактът ще попадне в репозитория Sonatype.
За да попадне артефактът в MavenCentral, трябва да поискате това в задачата, която създадохме в самото начало. Това трябва да се направи само един път, тъй като публикуваме за първи път. При следващите пъти не е необходимо, всичко ще се синхронизира автоматично. Синхронизацията ми беше включена бързо, но за да стане артефактът достъпен в MavenCentral, минаха около 5 дни.
С това приключваме, публикувахме нашият артефакт в MavenCentral.
Полезни връзки
- Подобна , само че публикуването е през maven
- Staging Sonatype
- Sonatype, в който трябва да създадете задача
- репозитория, където всичко е настроено
Източник: habr.com
