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 праекту, Project 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, працоўны працэс паспяхова адпрацуе, мы можам зайсці ў Sonatype Nexus каб у гэтым пераканацца:
Артэфакт з'явіўся ў Staging рэпазітары. Адразу ён з'яўляецца ў статуце Open, далей яго неабходна ўручную перавесці ў статут Close, націснуўшы адпаведную кнопку. Пасля праверкі выканання ўсіх патрабаванняў, артэфакт пераходзіць у статут Close і больш не даступны для змены. У такім выглядзе ён патрапіць у MavenCentral. Калі ўсё добра, можна націснуць кнопку Адпусціце, пры гэтым артэфакт патрапіць у рэпазітар Sonatype.
Для таго, каб артэфакт патрапіў у MavenCentral, трэба папрасіць аб гэтым у задачы, якую мы стварылі ў самым пачатку. Зрабіць гэта трэба толькі адзін раз, дык мы публікуем у першы раз. У наступныя разы гэта рабіць не патрабуецца, усё будзе сінхранізавацца аўтаматычна. Уключылі мне сінхранізацыю хутка, але каб артэфакт стаў даступны ў MavenCentral прайшло каля 5 дзён.
На гэтым усё, мы апублікавалі наш артэфакт у MavenCentral.