Usamos Gradle e Github Actions para publicar o projeto Java no Sonatype Maven Central Repository

Neste artigo, quero detalhar o processo de publicação de um artefato Java do zero através do Github Actions no Repositório Maven Central da Sonatype utilizando o Gradle.

Decidi escrever este artigo devido à falta de um tutorial adequado em um só lugar. Tive que reunir todas as informações fragmentadas de várias fontes, que não eram exatamente recentes. Para quem se interessa, sejam bem-vindos.

Criando um repositório na Sonatype

A primeira etapa é criar um repositório no Maven Central da Sonatype. Para isso, vamos aqui, nos registrar e criar uma nova tarefa, solicitando a criação do repositório. Preencha seu GroupId projeto, URL do Projeto link para o projeto e URL do SCM endereço do sistema de controle de versão onde o projeto está localizado. GroupId deve ter o formato com.example, com.example.domain, com.example.testsupport, ou pode ser um link para seu GitHub: github.com/yourusername -> io.github.yourusername. De qualquer forma, você precisará confirmar a propriedade desse domínio ou perfil. Se você indicou um perfil do GitHub, será solicitado que crie um repositório público com o nome desejado.

Após algum tempo depois da confirmação, seu GroupId será criado e podemos avançar para a próxima etapa, a configuração do Gradle.

Configurando o Gradle

No momento em que escrevo este artigo, não encontrei plugins para Gradle que pudessem ajudar na publicação do artefato. Isso é O único plugin que encontrei, no entanto, foi abandonado pelo autor. Portanto, decidi fazer tudo sozinho, felizmente isso não é muito complicado.

A primeira coisa que precisamos esclarecer são os requisitos da Sonatype para publicação. São os seguintes:

  • A presença dos códigos fonte e JavaDoc, ou seja, devem estar presentes -sources.jar e-javadoc.jar arquivos. Como mencionado na documentação, se não for possível fornecer os códigos fonte ou a documentação, é possível criar um arquivo vazio -sources.jar ou -javadoc.jar com um README simples dentro, para passar pela verificação.
  • Todos os arquivos devem ser assinados com GPG/PGP, quanto .asc o arquivo contendo a assinatura deve ser incluído para cada arquivo.
  • A presença de pom do arquivo
  • Valores corretos groupId, artifactId e version. A versão pode ser qualquer string e não pode terminar com -SNAPSHOT
  • É necessário ter nome, descrição e url
  • A presença de informações sobre a licença, desenvolvedores e sistema de controle de versão

Estas são as regras básicas que devem ser seguidas durante a publicação. Informações completas estão disponíveis aqui.

Implementamos esses requisitos em build.gradle arquivo. Para começar, vamos adicionar todas as informações necessárias sobre os desenvolvedores, a licença, o sistema de controle de versões, além de definir a url, o nome e a descrição do projeto. Para isso, escreveremos um método simples:

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

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

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

            description 'Alguma descrição do artefato'
            name 'Nome do Artefato'
            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 'Nome do Dev'
                    email 'email@dev.ru'
                }
            }
        }
    }
}

Em seguida, é necessário especificar que, durante a construção, serão gerados -sources.jar e-javadoc.jar arquivos. Para isso, na seção java é necessário adicionar o seguinte:

java {
    withJavadocJar()
    withSourcesJar()
}

Vamos passar para o último requisito, a configuração da assinatura GPG/PGP. Para isso, conectaremos o plugin signing:

plugins {
    id 'signing'
}

E adicionaremos a seção:

signing {
    sign publishing.publications
}

Finalmente, vamos adicionar a seção 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
            }
        }
    }
}

Aqui sonatypeUsername e sonatypePassword variáveis que contêm o login e a senha, criadas ao se registrar em sonatype.org.

Assim, o final build.gradle deve se parecer com o seguinte:

Código completo 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 'Alguma descrição do artefato'
            name 'Nome do Artefato'
            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 'A Licença Apache, Versão 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 'Nome do Dev'
                    email 'email@dev.ru'
                }
            }
        }
    }
}

Quero observar que estamos obtendo a versão da variável de ambiente: System.getenv('RELEASE_VERSION'). Vamos defini-la durante a construção e pegar a partir do nome da tag.

Geração de chave PGP

Uma das exigências do Sonatype é assinar todos os arquivos com uma chave GPG/PGP. Para isso, vamos aqui baixar a ferramenta GnuPG para o seu sistema operacional.

  • Geramos um par de chaves: gpg --gen-key, inserimos o nome de usuário, e-mail e também definimos uma senha.
  • Vamos verificar id nossa chave com o comando: gpg --list-secret-keys --keyid-format short. O Id será indicado após a barra, por exemplo: rsa2048/9B695056
  • Publicamos a chave pública no servidor https://keys.openpgp.org o comando: gpg --keyserver [https://keys.openpgp.org](https://keys.openpgp.org/) --send-keys 9B695056
  • Exportamos a chave secreta para um local à sua escolha, pois precisaremos dela mais tarde: gpg --export-secret-key 9B695056 > D:\gpg\9B695056.gpg

Configurando o Github Actions

Vamos para a fase final, configurar a construção e a publicação automática, usando o Github Actions.
Github Actions – funcionalidade que permite automatizar o fluxo de trabalho, implementando o ciclo completo de CI/CD. A construção, o teste e o deploy podem ser acionados por vários eventos: push de código, criação de lançamento ou issues. Esta funcionalidade é totalmente gratuita para repositórios públicos.

Nesta seção, vou mostrar como configurar a construção, o push de código e o deploy no repositório Sonatype ao lançar uma nova versão, além de como configurar os segredos.

Definindo segredos

Para a construção e o deploy automáticos, precisamos de uma série de valores secretos, como o id da chave, a senha utilizada ao gerar a chave, a própria chave PGP, além do login/senha para o Sonatype. É possível definir esses valores em uma seção especial nas configurações do repositório:

Usamos Gradle e Github Actions para publicar o projeto Java no Sonatype Maven Central Repository

Definindo as seguintes variáveis:

  • SONATYPE_USERNAME/SONATYPE_PASSWORD — login/senha que usamos ao nos registrarmos no Sonatype
  • SIGNING_KEYID/SIGNING_PASSWORD — id da chave PGP e a senha definida durante a geração.

Vou aprofundar-me na variável GPG_KEY_CONTENTS. O ponto é que, para a publicação, precisamos da chave PGP privada. Para armazená-la nos segredos, utilizei as instruções e também realicei várias ações adicionais.

  • Vamos criptografar nossa chave usando gpg: gpg --symmetric --cipher-algo AES256 9B695056.gpg, digitando a senha. Esta deve ser colocada na variável: SECRET_PASSPHRASE
  • Vamos converter a chave criptografada para um formato de texto usando base64: base64 9B695056.gpg.gpg > 9B695056.txt. O conteúdo deve ser colocado na variável: GPG_KEY_CONTENTS.

Configurando a construção ao realizar o push de código e ao criar PR

Primeiro, crie uma pasta na raiz do seu projeto: .github/workflows.

Nela, coloque um arquivo, por exemplo, gradle-ci-build.yml com o seguinte conteúdo:

name: build

on:
  push:
    branches:
      - master
      - dev
      - testing
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Configurar JDK 8
        uses: actions/setup-java@v1
        with:
          java-version: 8

      - name: Construir com Gradle
        uses: eskatos/gradle-command-action@v1
        with:
          gradle-version: current
          arguments: build -PsonatypeUsername=${{secrets.SONATYPE_USERNAME}} -PsonatypePassword=${{secrets.SONATYPE_PASSWORD}}

Este fluxo de trabalho será executado ao fazer push para os ramos master, dev e testing, assim como ao criar pull requests.

Na seção jobs, estão os passos que devem ser executados nos eventos especificados. Neste caso, vamos compilar na versão mais recente do ubuntu, usar Java 8 e também utilizar o plugin para Gradle eskatos/gradle-command-action@v1, que, utilizando a versão mais recente do compilador, executará os comandos especificados em arguments. Variáveis secrets.SONATYPE_USERNAME e secrets.SONATYPE_PASSWORD estes são os segredos que definimos anteriormente.

Os resultados da construção serão refletidos na aba Actions:

Usamos Gradle e Github Actions para publicar o projeto Java no Sonatype Maven Central Repository

Autodeploy ao lançar uma nova versão

Para autodeploy, criaremos um arquivo de fluxo de trabalho separado gradle-ci-publish.yml:

name: publish

on:
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Configurar JDK 8
        uses: actions/setup-java@v1
        with:
          java-version: 8

      - name: Preparar para publicar
        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: Publicar com 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}}

O arquivo é praticamente idêntico ao anterior, exceto pelo evento que o acionará. Neste caso, é o evento de criação de uma tag cujo nome começa com 'v'.

Antes do deploy, precisamos extrair a chave PGP dos segredos e colocá-la na raiz do projeto, além de descriptografá-la. Em seguida, precisamos definir uma variável de ambiente especial RELEASE_VERSION a qual acessamos no arquivo gradle.build. Tudo isso é feito na seção Prepare to publish . Obtemos nossa chave da variável GPG_KEY_CONTENTS, a convertendo para um arquivo gpg, e depois descriptografamos, colocando no arquivosecret.gpg Em seguida, acessamos uma variável especial.

GITHUB_REF , da qual podemos extrair a versão que definimos ao criar a tag. Esta variável, neste caso, tem o valorrefs/tags/v0.0.2 da qual cortamos os primeiros 11 caracteres para obter a versão exata. Em seguida, usamos os comandos padrão do Gradle para publicação: test publish Verificando os resultados do deploy no repositório Sonatype

Após a criação do lançamento, o fluxo de trabalho descrito na seção anterior deve ser iniciado. Para isso, criamos o lançamento:

onde o nome da tag deve começar com 'v'. Se após clicar em Publicar lançamento, o fluxo de trabalho for bem-sucedido, podemos acessar o

Usamos Gradle e Github Actions para publicar o projeto Java no Sonatype Maven Central Repository

Sonatype Nexus para confirmar isso: para ter certeza disso:

Usamos Gradle e Github Actions para publicar o projeto Java no Sonatype Maven Central Repository

O artefato apareceu no repositório Staging. Ele inicia com o status Open e deve ser manualmente alterado para o status Close, pressionando o botão correspondente. Após verificar o cumprimento de todos os requisitos, o artefato muda para o status Close e não pode mais ser alterado. Assim, ele será enviado para o MavenCentral. Se tudo estiver correto, você pode pressionar o botão Release, e o artefato será enviado para o repositório Sonatype.

Para que o artefato seja enviado para o MavenCentral, é necessário solicitar isso na tarefa que criamos no início. Isso deve ser feito apenas uma vez, pois estamos publicando pela primeira vez. Nas próximas vezes, isso não é necessário; tudo será sincronizado automaticamente. A sincronização foi ativada rapidamente, mas levou cerca de 5 dias para que o artefato se tornasse disponível no MavenCentral.

Isso é tudo, publicamos nosso artefato no MavenCentral.

Links Úteis

  • Semelhante artigo, apenas a publicação através do maven
  • Staging repositório Sonatype
  • Jira Sonatype, onde é necessário criar uma tarefa
  • Exemplo repositório, onde tudo isso está configurado

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster