ProHoster > Блог > адміністрування > Використовуємо Gradle та Github Actions для публікації Java проекту у Sonatype Maven Central Repository
Використовуємо Gradle та Github Actions для публікації Java проекту у Sonatype Maven Central Repository
У цій статті я хочу докладно розглянути процес публікації з нуля Java артефакту через Github Actions у Sonatype Maven Central Repository, використовуючи збирач Gradle.
Цю статтю вирішив написати через відсутність нормального туторіалу в одному місці. Всю інформацію доводилося збирати по шматках із різних джерел, причому не зовсім свіжих. Кому цікаво, ласкаво просимо під кат.
Створення репозиторію в Sonatype
Першим етапом нам потрібно створити репозиторій у Sonatype Maven Central. Для цього йдемо сюди, реєструємось та створюємо нове завдання, з проханням створити нам репозиторій. Вбиваємо свій GroupId проекту, URL-адреса проекту посилання на проект та SCM url посилання систему контролю версій, у якій проект лежить. GroupId тут має бути виду com.example, com.example.domain, com.example.testsupport, а також може бути у вигляді посилання на ваш гітхаб: github.com/yourusername -> io.github.yourusername. У будь-якому випадку вам потрібно буде підтвердити володіння даним доменом або профілем. Якщо ви зазначили профіль гітхаба, попросять створити публічний репозиторій із потрібним ім'ям.
Через деякий час після підтвердження вашої GroupId буде створено і ми можемо перейти до наступного кроку, конфігурації Gradle.
Конфігуруємо Gradle
На момент написання статті я не знайшов плагінів для Gradle, які б могли допомогти з публікацією артефакту. Це єдиний плагін, який я знайшов, але автор відмовився від його подальшої підтримки. Тому я вирішив зробити все самостійно, благо зробити це не надто важко.
Перше, що потрібно з'ясувати, це Sonatype вимоги для публікації. Вони такі:
Наявність вихідних кодів та JavaDoc, тобто. повинні бути присутніми -sources.jar и-javadoc.jar файли. Як сказано в документації, якщо ні можливо надати вихідні коди або документацію, можна зробити пустушку -sources.jar або -javadoc.jar c простим 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 'Some description of artifact'
name 'Artifct 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 '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 protected]'
}
}
}
}
}
Далі потрібно вказати, щоб при збиранні згенерувалися -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 змінні, що містять логін та пароль, створені при реєстрації на sonatype.org.
Таким чином, фінальний 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 '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 protected]'
}
}
}
}
}
Хочу зауважити, що версію ми отримуємо зі змінного середовища: System.getenv('RELEASE_VERSION'). Виставляти її ми будемо при складанні та брати з імені тега.
Генерація ключа PGP
Однією з вимог Sonatype є підписання всіх файлів за допомогою ключа GPG/PGP. Для цього йдемо сюди та качаємо утиліту GnuPG під свою операційну систему.
Генеруємо ключову пару: gpg --gen-key, вводимо ім'я користувача, e-mail, а також задаємо пароль.
З'ясовуємо id нашого ключа командою: gpg --list-secret-keys --keyid-format short. Id буде вказано після слішу, наприклад: rsa2048/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 и testing, також під час створення пулл реквестов.
У секції 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: 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}}
Файл практично ідентичний попередньому за винятком події, за якої він спрацьовуватиме. У цьому випадку ця подія створення тега з ім'ям, що починається на v.
Перед деплоєм нам потрібно витягнути PGP ключ із секретів та розмістити його в корені проекту, а також розшифрувати його. Далі нам потрібно виставити спеціальне змінне середовище RELEASE_VERSION до якої ми звертаємось у gradle.build файл. Все це зроблено у розділі Prepare to publish. Ми отримуємо наш ключ зі змінної GPG_KEY_CONTENTS, переводимо його в файл gpg, потім розшифровуємо його, поміщаючи у файл secret.gpg.
Далі ми звертаємось до спеціальної змінної GITHUB_REF, з якої можемо дістати версію, яку ми поставили під час створення тега. Ця змінна у разі має значення refs/tags/v0.0.2 з якої ми відрізаємо перші 11 символів, щоб дістати версію. Далі стандартно використовуємо команди Gradle для публікації: test publish
Перевірка результатів деплою в Sonatype репозиторій
Після створення релізу повинен запустити робочий процес, описаний у попередньому розділі. Для цього створюємо реліз:
у своїй ім'я тега має починатися з v. Якщо після натискання Publish release робочий процес успішно відпрацює, ми можемо зайти в Сонатип Nexus щоб у цьому переконатися:
Артефакт виник у Staging репозиторії. Відразу він з'являється у статусі Open, далі його необхідно вручну перевести у статус Close, натиснувши відповідну кнопку. Після перевірки виконання всіх вимог артефакт переходить у статус Close і більше не доступний для зміни. У такому вигляді він потрапить до MavenCentral. Якщо все гаразд, можна натиснути кнопку ВідпустітьПри цьому артефакт потрапить до репозиторію Sonatype.
Для того, щоб артефакт потрапив до MavenCentral, потрібно попросити про це завдання, яке ми створили на самому початку. Зробити це потрібно лише один раз, тому ми публікуємо вперше. У наступні рази це робити не потрібно, все синхронізуватиметься автоматично. Включили мені синхронізацію швидко, але щоб артефакт став доступним у MavenCentral пройшло близько 5 днів.
На цьому все, ми опублікували наш артефакт у MavenCentral.