Używamy Gradle i Github Actions do publikacji projektu Java w Sonatype Maven Central Repository

W tym artykule chciałbym szczegółowo omówić proces publikacji artefaktu Java od podstaw przez Github Actions w repozytorium Sonatype Maven Central, używając Gradle jako kompilatora.

Postanowiłem napisać ten artykuł z powodu braku porządnego samouczka w jednym miejscu. Całe informacje musiałem zbierać z różnych źródeł, które nie były do końca aktualne. Kogo to interesuje, zapraszam do przeczytania.

Tworzenie repozytorium w Sonatype

Pierwszym krokiem jest stworzenie repozytorium w Sonatype Maven Central. W tym celu idziemy tutaj, rejestrujemy się i tworzymy nowe zadanie z prośbą o utworzenie repozytorium. Wpisujemy nasz GroupId projektu, Adres URL projektu link do projektu oraz adres SCM link do systemu kontroli wersji, w którym znajduje się projekt. GroupId Powinien mieć postać com.example, com.example.domain, com.example.testsupport, a także może być w postaci linku do Twojego Githuba: github.com/yourusername -> io.github.yourusername. W każdym razie, będziesz musiał potwierdzić posiadanie tej domeny lub profilu. Jeśli wskazałeś profil na Githubie, poproszą o utworzenie publicznego repozytorium o właściwej nazwie.

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

Konfigurujemy Gradle

W momencie pisania artykułu nie znalazłem wtyczek do Gradle, które mogłyby pomóc w publikacji artefaktu. To Jedyną wtyczką, którą znalazłem, autor zrezygnował z dalszego wsparcia. Dlatego postanowiłem zrobić wszystko samodzielnie, na szczęście nie jest to zbyt skomplikowane.

Pierwszą rzeczą, którą trzeba ustalić, są wymagania Sonatype do publikacji. Oto one:

  • Posiadanie kodu źródłowego i JavaDoc, tzn. muszą być obecne -sources.jar i-javadoc.jar pliki. Jak podano w dokumentacji, jeśli nie można dostarczyć kodów źródłowych lub dokumentacji, można stworzyć puste pliki -sources.jar lub -javadoc.jar z prostym README w środku, aby przejść weryfikację.
  • Wszystkie pliki muszą być podpisane za pomocą GPG/PGP, i .asc plik, który zawiera podpis, musi być dołączony do każdego pliku.
  • Obecność pom pliku
  • Prawidłowe wartości groupId, artifactId i version. Wersja może być dowolnym ciągiem znaków i nie może się kończyć -SNAPSHOT
  • Obecność name, opis i url
  • Obecność informacji o licencji, deweloperach i systemie kontroli wersji

To są podstawowe zasady, które muszą być przestrzegane podczas publikacji. Pełne informacje są dostępne tutaj.

Realizujemy te wymagania w build.gradle pliku. Na początek dodamy wszystkie potrzebne informacje o deweloperach, licencji, systemie kontroli wersji, a także ustalimy URL, nazwę i opis projektu. W tym celu napiszemy prostą metodę:

def customizePom(pom) {
    pom.withXml {
        def root = asNode()

        root.dependencies.removeAll { dep ->
            dep.scope == "test"
        }

        root.children().last() + {
            resolveStrategy = DELEGATE_FIRST

            description 'Opis artefaktu'
            name 'Nazwa artefaktu'
            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 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@dev.pl'
                }
            }
        }
    }
}

Następnie musimy wskazać, aby podczas budowy zostały wygenerowane -sources.jar i-javadoc.jar pliki. W tym celu do sekcji java należy dodać następujące:

java {
    withJavadocJar()
    withSourcesJar()
}

Przejdźmy do ostatniego wymagania, konfigurowania podpisu GPG/PGP. W tym celu podłączymy wtyczkę signing:

plugins {
    id 'signing'
}

I dodamy sekcję:

signing {
    sign publishing.publications
}

Na koniec dodamy 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 sonatypeUsername i sonatypePassword zmienne, które zawierają nazwę użytkownika i hasło, utworzone podczas rejestracji na sonatype.org.

W ten sposób, finalny build.gradle będzie wyglądać następująco:

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 'Artifact 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@dev.pl'
                }
            }
        }
    }
}

Chcę zauważyć, że wersję uzyskujemy z zmiennej środowiskowej: System.getenv('RELEASE_VERSION'). Będziemy ją ustawiać podczas budowy i brać z nazwy tagu.

Generowanie klucza PGP

Jednym z wymogów Sonatype jest podpisanie wszystkich plików za pomocą klucza GPG/PGP. W tym celu przechodzimy tutaj i pobieramy narzędzie GnuPG dla swojego systemu operacyjnego.

  • Generujemy parę kluczy: gpg --gen-key, wprowadzamy nazwę użytkownika, e-mail oraz hasło.
  • Wyjaśniamy id nasz klucz komendą: gpg --list-secret-keys --keyid-format short. Id będzie podany po ukośniku, na przykład: rsa2048/9B695056
  • Publikujemy klucz publiczny na serwerze https://keys.openpgp.org poleceniem: gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056
  • Eksportujemy klucz sekretu w dowolne miejsce, będzie nam potrzebny później: gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg

Konfigurujemy Github Actions

Przejdźmy do ostatniego etapu, skonfigurujmy budowę i automatyczną publikację, używając Github Actions.
Github Actions – funkcjonalność umożliwiająca automatyzację procesu roboczego, realizująca pełny cykl CI/CD. Budowanie, testowanie i wdrażanie mogą być wywoływane przez różne zdarzenia: push kodu, tworzenie wydania lub issue. Ta funkcjonalność jest całkowicie darmowa dla publicznych repozytoriów.

W tej sekcji pokażę, jak skonfigurować budowanie, push kodu i wdrażanie w repozytorium Sonatype podczas publikacji wydania oraz jak skonfigurować sekrety.

Ustalamy sekrety

Do automatycznego budowania i wdrażania będziemy potrzebować kilku sekretnych wartości, takich jak id klucza, hasło, które wprowadziliśmy podczas generowania klucza, sam klucz PGP, a także login/hasło do Sonatype. Można je ustalić w specjalnej sekcji w ustawieniach repozytorium:

Używamy Gradle i Github Actions do publikacji projektu Java w Sonatype Maven Central Repository

Ustalamy następujące zmienne:

  • SONATYPE_USERNAME/SONATYPE_PASSWORD — login/hasło, które wprowadziliśmy podczas rejestracji w Sonatype
  • SIGNING_KEYID/SIGNING_PASSWORD — id klucza PGP oraz hasło ustalone podczas generowania.

Chcę się zatrzymać na zmiennej GPG_KEY_CONTENTS. Chodzi o to, że do publikacji jest nam potrzebny prywatny klucz PGP. Aby umieścić go w sekretach, skorzystałem z instrukcją i dodatkowo wykonałem szereg działań.

  • Zaszyfrujmy nasz klucz za pomocą gpg: gpg --symmetric --cipher-algo AES256 9B695056.gpg, wprowadzając hasło. Należy umieścić je w zmiennej: SECRET_PASSPHRASE
  • Przekształćmy otrzymany zaszyfrowany klucz na tekstowy format za pomocą base64: base64 9B695056.gpg.gpg > 9B695056.txt. Zawartość umieścimy w zmiennej: GPG_KEY_CONTENTS.

Konfiguracja budowania przy pushu kodu i tworzeniu PR

Na początek należy stworzyć folder w głównym katalogu projektu: .github/workflows.

W nim umieścić plik, na przykład, gradle-ci-build.yml z następującą zawartoś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: Buduj za pomocą Gradle
        uses: eskatos/gradle-command-action@v1
        with:
          gradle-version: current
          arguments: build -PsonatypeUsername=${{secrets.SONATYPE_USERNAME}} -PsonatypePassword=${{secrets.SONATYPE_PASSWORD}}

Ten proces roboczy będzie wykonywany przy pushu do gałęzi master, dev i testing, jak również przy tworzeniu żądań pull.

W sekcji jobs podane są kroki, które powinny zostać wykonane przy wskazanych zdarzeniach. W tym przypadku będziemy budować na najnowszej wersji ubuntu, używać Java 8, a także korzystać z wtyczki dla Gradle eskatos/gradle-command-action@v1, która, korzystając z najnowszej wersji narzędzia, uruchomi komendy podane w arguments. Zmienne secrets.SONATYPE_USERNAME i secrets.SONATYPE_PASSWORD to są sekrety, które wcześniej ustawiliśmy.

Wyniki budowy będą odzwierciedlone na karcie Akcje:

Używamy Gradle i Github Actions do publikacji projektu Java w Sonatype Maven Central Repository

Autodeploy przy wydaniu nowej wersji

Aby zrealizować autodeploy, stworzymy osobny plik roboczy 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 praktycznie identyczny z poprzednim, z wyjątkiem zdarzenia, które go uruchomi. W tym przypadku jest to zdarzenie tworzenia tagu o nazwie zaczynającej się na v.

Przed wdrożeniem musimy wyciągnąć klucz PGP z sekretów i umieścić go w głównym katalogu projektu oraz go odszyfrować. Następnie musimy ustawić specjalną zmienną środowiskową RELEASE_VERSION do której odwołujemy się w gradle.build pliku. Wszystko to jest zrealizowane w sekcji Prepare to publish. Otrzymujemy nasz klucz z zmiennej GPG_KEY_CONTENTS, przekształcamy go w plik gpg, a następnie go odszyfrowujemy, umieszczając w pliku secret.gpg.

Następnie odwołujemy się do specjalnej zmiennej GITHUB_REF, z której możemy wyciągnąć wersję, którą ustaliliśmy podczas tworzenia tagu. Ta zmienna w tym przypadku ma wartość refs/tags/v0.0.2 z której odcinamy pierwsze 11 znaków, aby uzyskać konkretną wersję. Następnie standardowo używamy poleceń Gradle do publikacji: test publish

Sprawdzenie wyników wdrożenia w repozytorium Sonatype

Po stworzeniu wydania powinien się uruchomić proces roboczy opisany w poprzedniej sekcji. W tym celu tworzymy wydanie:

Używamy Gradle i Github Actions do publikacji projektu Java w Sonatype Maven Central Repository

przy czym nazwa tagu powinna zaczynać się na v. Jeśli po naciśnięciu Publish release, proces roboczy zakończy się sukcesem, możemy wejść w Sonatype Nexus aby się o tym przekonać:

Używamy Gradle i Github Actions do publikacji projektu Java w Sonatype Maven Central Repository

Artefakt pojawił się w repozytorium Staging. Od razu pojawia się w statusie Open, a następnie należy ręcznie zmienić jego status na Close, klikając odpowiedni przycisk. Po sprawdzeniu spełnienia wszystkich wymagań artefakt przechodzi do statusu Close i nie jest już dostępny do zmiany. W tej formie trafi do MavenCentral. Jeśli wszystko jest w porządku, można kliknąć przycisk Release, przy czym artefakt trafi do repozytorium Sonatype.

Aby artefakt trafił do MavenCentral, należy poprosić o to w zadaniu, które stworzyliśmy na początku. Trzeba to zrobić tylko raz, ponieważ publikujemy po raz pierwszy. W kolejnych razach nie jest to wymagane, wszystko będzie synchronizowane automatycznie. Synchronizacja została włączona szybko, ale aby artefakt stał się dostępny w MavenCentral, minęło około 5 dni.

To wszystko, opublikowaliśmy nasz artefakt w MavenCentral.

Przydatne linki

  • Podobna artykuł, tylko publikacja przez maven
  • Staging repozytorium Sonatype
  • Jira Sonatype, w którym należy stworzyć zadanie
  • Przykład repozytorium, w którym wszystko jest skonfigurowane

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster