Utilisation des actions Gradle et Github pour publier un projet Java dans le référentiel central Sonatype Maven

Dans cet article, je souhaite examiner en détail le processus de publication d'un artefact Java à partir de zéro via les actions Github dans le référentiel central Sonatype Maven à l'aide du collecteur Gradle.


J'ai décidé d'écrire cet article en raison de l'absence d'un tutoriel normal en un seul endroit. Toutes les informations devaient être collectées en morceaux de diverses sources, mais pas entièrement fraîches. Peu importe, bienvenue au chat.


Création d'un référentiel dans Sonatype


La première étape consiste à créer un référentiel dans Sonatype Maven Central. Pour ce faire, allez ici , inscrivez-vous et créez une nouvelle tâche, en nous demandant de créer un référentiel pour nous. Nous lançons dans notre GroupId du projet, le lien URL du projet vers le projet et le lien URL SCM vers le système de contrôle de version dans lequel se trouve le projet. Le GroupId ici doit être de la forme com.example, com.example.domain, com.example.testsupport, et il peut également être un lien vers votre github: github.com/yourusername -> io.github.yourusername. Dans tous les cas, vous devrez confirmer la propriété de ce domaine ou profil. Si vous avez spécifié un profil github, ils vous demanderont de créer un référentiel public avec le nom souhaité.


Quelque temps après la confirmation, votre GroupId sera créé et nous pouvons passer à l'étape suivante, la configuration de Gradle.


Configuration de Gradle


Au moment d'écrire ces lignes, je n'ai trouvé aucun plugin pour Gradle qui pourrait aider à la publication de l'artefact. C'est le seul plugin que j'ai trouvé, mais l'auteur a refusé son soutien supplémentaire. J'ai donc décidé de tout faire moi-même, car ce n'est pas trop difficile de le faire.


La première chose à savoir est les exigences de publication de Sonatype. Ils sont les suivants:


  • La présence de codes sources et JavaDoc, c'est-à-dire doit être présent -sources.jaret -javadoc.jarfichiers. Comme indiqué dans la documentation, s'il n'est pas possible de fournir des codes sources ou de la documentation, vous pouvez créer un mannequin -sources.jarou -javadoc.jarun simple README à l'intérieur pour passer le test.
  • 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@dev.ru'
                }
            }
        }
    }
}

, -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@dev.ru'
                }
            }
        }
    }
}

, : 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
  • https://keys.openpgp.org : gpg --keyserver https://keys.openpgp.org/ --send-keys 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. , Release, Sonatype.


, MavenCentral, , . , . , . , MavenCentral 5 .


C'est tout, nous avons publié notre artefact dans MavenCentral.


Liens utiles


  • Article similaire , publication uniquement via maven
  • Type de son du référentiel intermédiaire
  • Jira Sonatype, dans lequel vous devez créer une tâche
  • Un exemple de référentiel où tout est configuré

All Articles