Използване на действия на Gradle и Github за публикуване на Java проект в Sonatype Maven Central Repository

В тази статия искам да разгледам по-подробно процеса на публикуване на Java артефакт от нулата чрез Github Actions в Sonatype Maven Central Repository с помощта на Gradle builder.

Реших да напиша тази статия поради липсата на нормален урок на едно място. Цялата информация трябваше да се събира част по част от различни източници, при това не съвсем свежи. На кого му пука, добре дошъл под кат.

Създаване на хранилище в Sonatype

Първата стъпка е да създадете хранилище в Sonatype Maven Central. За това отиваме тук, регистрирайте се и създайте нова задача, като поискате от нас да създадем хранилище. Ние караме в нашите GroupId проект, URL адрес на проекта връзка към проекта и URL адрес на SCM връзка към системата за контрол на версиите, в която се намира проектът. GroupId тук трябва да бъде във формата com.example, com.example.domain, com.example.testsupport и може също да бъде под формата на връзка към вашия github: github.com/вашето потребителско име -> io.github.вашето потребителско име. Във всеки случай ще трябва да потвърдите собствеността върху този домейн или профил. Ако сте посочили профил в 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 '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
            }
        }
    }
}

Тук sonatypeПотребителско име и 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, въведете потребителско име, имейл и задайте парола.
  • Разбрахме id нашия ключ с командата: gpg --list-secret-keys --keyid-format short. Id ще бъде посочен след наклонената черта, например: rsa2048/9B695056
  • Публикуване на публичния ключ на сървъра https://keys.openpgp.org с командата: 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 цикъл. Изграждането, тестването и внедряването могат да бъдат задействани от различни събития: натискане на код, създаване на версия или проблеми. Тази функционалност е абсолютно безплатна за публични хранилища.

В този раздел ще ви покажа как да настроите код за изграждане и натискане и да разположите в хранилището на Sonatype при пускане, както и да настроите тайни.

Ние поставяме тайни

За автоматично сглобяване и разгръщане се нуждаем от редица секретни стойности, като идентификатора на ключа, паролата, която сме въвели при генерирането на ключа, самия PGP ключ и вход/парола на Sonatype. Можете да ги зададете в специален раздел в настройките на хранилището:

Използване на действия на Gradle и Github за публикуване на Java проект в Sonatype Maven Central Repository

Задаваме следните променливи:

  • 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, също и при създаване на заявки за изтегляне.

Разделът за задания определя стъпките, които трябва да бъдат изпълнени при посочените събития. В този случай ще изградим най-новата версия на ubuntu, ще използваме Java 8 и ще използваме плъгина за Gradle eskatos/gradle-command-action@v1който, използвайки най-новата версия на конструктора, ще изпълнява командите, посочени в arguments. Променливи secrets.SONATYPE_USERNAME и secrets.SONATYPE_PASSWORD това са тайните, които попитахме по-рано.

Резултатите от изграждането ще бъдат отразени в раздела Действия:

Използване на действия на Gradle и Github за публикуване на Java проект в Sonatype Maven Central Repository

Автоматично внедряване при пускане на нова версия

Нека създадем отделен файл на работен поток за автоматично разгръщане 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

След като изданието бъде създадено, работният процес, описан в предишния раздел, трябва да започне. За да направите това, създайте издание:

Използване на действия на Gradle и Github за публикуване на Java проект в Sonatype Maven Central Repository

името на етикета трябва да започва с v. Ако след като щракнете върху Публикуване на изданието, работният процес завърши успешно, можем да отидем на Сонатип Nexus да се уверите:

Използване на действия на Gradle и Github за публикуване на Java проект в Sonatype Maven Central Repository

Артефактът се появи в хранилището на Staging. Веднага се появява в състояние Отворено, след което трябва ръчно да се прехвърли в състояние Затвори чрез натискане на съответния бутон. След проверка дали всички изисквания са изпълнени, артефактът преминава в състояние Затворено и вече не е достъпен за модификация. В тази форма той ще завърши в MavenCentral. Ако всичко е наред, можете да натиснете бутона Освободете, и артефактът ще се озове в хранилището на Sonatype.

За да може артефактът да влезе в MavenCentral, трябва да го поискате в задачата, която създадохме в самото начало. Трябва да направите това само веднъж, затова публикуваме за първи път. В следващите пъти това не е необходимо, всичко ще се синхронизира автоматично. Бързо ми включиха синхронизацията, но отне около 5 дни, докато артефактът стане достъпен в MavenCentral.

Това е всичко, ние публикувахме нашия артефакт в MavenCentral.

Полезни връзки

  • Подобен статия, публикувайте само чрез maven
  • Постановка хранилище Сонатип
  • Jira Sonatype, в който да създадете задачата
  • Пример хранилище, където всичко е настроено

Източник: www.habr.com