Używanie akcji Gradle i Github do publikowania projektu Java w centralnym repozytorium Sonatype Maven

W tym artykule chcę szczegółowo przyjrzeć się procesowi publikowania artefaktu Javy od podstaw przez Github Actions do Sonatype Maven Central Repository przy użyciu narzędzia Gradle.

Zdecydowałem się napisać ten artykuł ze względu na brak normalnego poradnika w jednym miejscu. Wszystkie informacje trzeba było zbierać kawałek po kawałku z różnych źródeł, zresztą nie do końca świeżych. Kogo to obchodzi, witamy pod kotem.

Tworzenie repozytorium w Sonatype

Pierwszym krokiem jest utworzenie repozytorium w Sonatype Maven Central. W tym celu idziemy tutaj, zarejestruj się i utwórz nowe zadanie, prosząc nas o utworzenie repozytorium. Jeździmy w naszym Identyfikator grupy projekt, URL projektu link do projektu i Adres SCM link do systemu kontroli wersji, w którym znajduje się projekt. Identyfikator grupy tutaj powinien mieć postać com.example, com.example.domain, com.example.testsupport, może też mieć postać linku do Twojego githuba: github.com/twojanazwaużytkownika -> io.github.twojanazwaużytkownika. W każdym przypadku musisz zweryfikować własność tej domeny lub profilu. Jeśli określiłeś profil github, zostaniesz poproszony o utworzenie publicznego repozytorium o żądanej nazwie.

Jakiś czas po potwierdzeniu Twój GroupId zostanie utworzony i możemy przejść do następnego kroku, konfiguracji Gradle.

Konfigurowanie Gradle'a

W chwili pisania tego tekstu nie znalazłem wtyczek Gradle, które mogłyby pomóc w opublikowaniu artefaktu. To jedyna wtyczka, jaką znalazłem, jednak autor odmówił jej dalszego wspierania. Dlatego postanowiłem zrobić wszystko sam, ponieważ nie jest to zbyt trudne.

Pierwszą rzeczą do ustalenia są wymagania Sonatype dotyczące publikowania. Są to:

  • Dostępność kodów źródłowych i JavaDoc, tj. musi uczestniczyć -sources.jar и-javadoc.jar akta. Jak podano w dokumentacji, jeśli nie jest możliwe dostarczenie kodów źródłowych lub dokumentacji, można zrobić atrapę -sources.jar lub -javadoc.jar z prostym plikiem README w środku, aby przejść test.
  • Wszystkie pliki muszą być podpisane GPG/PGPi .asc plik zawierający podpis musi być dołączony do każdego pliku.
  • dostępność pom plik
  • Poprawne wartości groupId, artifactId и version. Wersja może być dowolnym ciągiem znaków i nie może kończyć się na -SNAPSHOT
  • Wymagana obecność name, description и url
  • Obecność informacji o licencji, programistach i systemie kontroli wersji

To podstawowe zasady, których należy przestrzegać podczas publikowania. Dostępne pełne informacje tutaj.

Realizujemy te wymagania w build.gradle plik. Najpierw dodajmy wszystkie niezbędne informacje o programistach, licencjach, systemie kontroli wersji, a także ustawmy adres URL, nazwę i opis projektu. Napiszmy w tym celu prostą metodę:

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]'
                }
            }
        }
    }
}

Następnie musisz to określić podczas generowanego zespołu -sources.jar и-javadoc.jar akta. Dla tej sekcji java musisz dodać następujące:

java {
    withJavadocJar()
    withSourcesJar()
}

Przejdźmy do ostatniego wymagania, czyli ustawienia podpisu GPG/PGP. Aby to zrobić, podłącz wtyczkę signing:

plugins {
    id 'signing'
}

I dodaj sekcję:

signing {
    sign publishing.publications
}

Na koniec dodajmy 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 sonatypeNazwa użytkownika и hasło sonatype zmienne zawierające login i hasło utworzone podczas rejestracji w serwisie sonatype.org.

Tym samym finał build.gradle będzie wyglądać tak:

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 '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]'
                }
            }
        }
    }
}

Chcę zauważyć, że wersję otrzymujemy ze zmiennej środowiskowej: System.getenv('RELEASE_VERSION'). Wyeksponujemy to podczas montażu i weźmiemy z nazwy tagu.

Generowanie kluczy PGP

Jednym z wymagań Sonatype jest podpisywanie wszystkich plików kluczem GPG/PGP. W tym celu idziemy tutaj i pobierz narzędzie GnuPG dla swojego systemu operacyjnego.

  • Generujemy parę kluczy: gpg --gen-key, wprowadź nazwę użytkownika, adres e-mail, a także ustaw hasło.
  • Dowiadywać się id nasz klucz z poleceniem: gpg --list-secret-keys --keyid-format short. Identyfikator zostanie podany po ukośniku, na przykład: rsa2048/9B695056
  • Publikowanie klucza publicznego na serwerze https://keys.openpgp.org Komenda: gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056
  • Eksportujemy tajny klucz w dowolne miejsce, będziemy go potrzebować w przyszłości: gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg

Konfigurowanie akcji Github

Przejdźmy do ostatniego etapu, skonfiguruj kompilację i automatyczną publikację za pomocą Github Actions.
Github Actions to funkcja, która pozwala zautomatyzować przepływ pracy poprzez wdrożenie pełnego cyklu CI/CD. Kompilowanie, testowanie i wdrażanie mogą być wyzwalane przez różne zdarzenia: wypychanie kodu, tworzenie wersji lub problemy. Ta funkcjonalność jest całkowicie bezpłatna dla repozytoriów publicznych.

W tej sekcji pokażę, jak skonfigurować kod kompilacji i wypychania oraz wdrożyć go w repozytorium Sonatype w momencie wydania, a także skonfigurować sekrety.

Ustalamy tajemnice

Do automatycznego składania i wdrażania potrzebujemy szeregu tajnych wartości, takich jak identyfikator klucza, hasło, które wprowadziliśmy podczas generowania klucza, sam klucz PGP oraz login/hasło Sonatype. Możesz je ustawić w specjalnej sekcji w ustawieniach repozytorium:

Używanie akcji Gradle i Github do publikowania projektu Java w centralnym repozytorium Sonatype Maven

Ustawiamy następujące zmienne:

  • SONATYPE_USERNAME / SONATYPE_PASSWORD – login/hasło, które podaliśmy podczas rejestracji w Sonatype
  • SIGNING_KEYID/SIGNING_PASSWORD — Identyfikator i hasło klucza PGP ustawione podczas generowania.

Chcę bardziej szczegółowo omówić zmienną GPG_KEY_CONTENTS. Faktem jest, że do publikacji potrzebujemy prywatnego klucza PGP. Aby opublikować go w tajemnicach, użyłem instrukcja a dodatkowo wykonał szereg działań.

  • Zaszyfrujmy nasz klucz za pomocą gpg: gpg --symmetric --cipher-algo AES256 9B695056.gpgwprowadzając hasło. Należy go umieścić w zmiennej: SECRET_PASSPHRASE
  • Przetłumaczmy otrzymany zaszyfrowany klucz na postać tekstową za pomocą base64: base64 9B695056.gpg.gpg > 9B695056.txt. Treść zostanie umieszczona w zmiennej: GPG_KEY_CONTENTS.

Zbuduj konfigurację podczas wypychania kodu i tworzenia PR

Najpierw musisz utworzyć folder w katalogu głównym swojego projektu: .github/workflows.

Zaznacz w nim plik, np. gradle-ci-build.yml o następującej treś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: Build with Gradle
        uses: eskatos/gradle-command-action@v1
        with:
          gradle-version: current
          arguments: build -PsonatypeUsername=${{secrets.SONATYPE_USERNAME}} -PsonatypePassword=${{secrets.SONATYPE_PASSWORD}}

Ten przepływ pracy zostanie wykonany podczas wypychania do gałęzi master, dev и testing, również podczas tworzenia żądań ściągnięcia.

Sekcja jobs określa kroki, które mają zostać wykonane w przypadku określonych zdarzeń. W tym przypadku będziemy budować na najnowszej wersji ubuntu, używać Javy 8, a także używać wtyczki do Gradle eskatos/gradle-command-action@v1który przy użyciu najnowszej wersji konstruktora uruchomi polecenia określone w arguments. Zmienne secrets.SONATYPE_USERNAME и secrets.SONATYPE_PASSWORD to są tajemnice, o które pytaliśmy wcześniej.

Wyniki kompilacji zostaną odzwierciedlone na karcie Akcje:

Używanie akcji Gradle i Github do publikowania projektu Java w centralnym repozytorium Sonatype Maven

Automatyczne wdrażanie po wydaniu nowej wersji

Utwórzmy osobny plik przepływu pracy do automatycznego wdrażania 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 prawie identyczny jak poprzedni, z wyjątkiem zdarzenia, w którym zostanie wywołany. W tym przypadku jest to zdarzenie polegające na utworzeniu tagu o nazwie rozpoczynającej się na literę v.

Przed wdrożeniem musimy wyodrębnić klucz PGP z sekretów i umieścić go w katalogu głównym projektu, a także go odszyfrować. Następnie musimy ustawić specjalną zmienną środowiskową RELEASE_VERSION do którego się odnosimy gradle.build plik. Wszystko to odbywa się w sekcji Prepare to publish. Otrzymujemy nasz klucz ze zmiennej GPG_KEY_CONTENTS, tłumaczymy go na plik gpg, a następnie odszyfrowujemy go umieszczając w pliku secret.gpg.

Następnie zwracamy się do specjalnej zmiennej GITHUB_REF, z którego możemy pobrać wersję, którą ustawiliśmy podczas tworzenia tagu. Ta zmienna jest istotna w tym przypadku. refs/tags/v0.0.2 z którego odcinamy pierwszych 11 znaków, aby otrzymać konkretną wersję. Następnie używamy standardowych poleceń Gradle do publikowania: test publish

Sprawdzanie wyników wdrożenia w repozytorium Sonatype

Po utworzeniu wydania powinien rozpocząć się przepływ pracy opisany w poprzedniej sekcji. Aby to zrobić, utwórz wydanie:

Używanie akcji Gradle i Github do publikowania projektu Java w centralnym repozytorium Sonatype Maven

nazwa znacznika musi zaczynać się od v. Jeśli po kliknięciu Publikuj wydanie przepływ pracy zakończy się pomyślnie, możemy przejść do Sonatype Nexus upewniać się:

Używanie akcji Gradle i Github do publikowania projektu Java w centralnym repozytorium Sonatype Maven

Artefakt pojawił się w repozytorium Staging. Od razu pojawia się w stanie Otwarty, następnie musi być ręcznie przeniesiony do stanu Zamknięty poprzez naciśnięcie odpowiedniego przycisku. Po sprawdzeniu spełnienia wszystkich wymagań artefakt przechodzi w stan Zamknięty i nie jest już dostępny do modyfikacji. W tej formie trafi do MavenCentral. Jeśli wszystko jest w porządku, możesz nacisnąć przycisk Wydanie, a artefakt trafi do repozytorium Sonatype.

Aby artefakt dostał się do MavenCentral, musisz o niego poprosić w zadaniu, które stworzyliśmy na samym początku. Wystarczy to zrobić tylko raz, więc publikujemy po raz pierwszy. W kolejnych czasach nie jest to wymagane, wszystko zostanie zsynchronizowane automatycznie. Szybko włączyli mi synchronizację, ale minęło około 5 dni, zanim artefakt stał się dostępny w MavenCentral.

To wszystko, opublikowaliśmy nasz artefakt w MavenCentral.

Przydatne linki

  • Podobny artykuł, publikuj tylko przez maven
  • Inscenizacja magazyn Sonatyp
  • Jira Sonatype, w którym ma zostać utworzone zadanie
  • Przykład repozytorium, w którym wszystko jest skonfigurowane

Źródło: www.habr.com