본문 바로가기
Programming/Java

[Java] Java 라이브러리 패키지를 Github Actions를 이용하여 Maven central repository에 배포하는 방법

by SpiralMoon 2024. 1. 2.
반응형

Java 라이브러리 패키지를 Git Actions를 이용하여 Maven central repository에 배포하는 방법

자바 라이브러리를 Github Actions을 이용하여 쉽고 빠르게 Maven central에 배포하는 환경을 구축해보자.

주의!

해당 문서는 sonatype의 정책 변경으로 인해 2024년3월12일 이후에는 유효하지 않습니다.


시리즈

2023.12.08 - [Programming/Javascript] - [Javascript] Javascript 라이브러리 패키지를 Github Actions를 이용하여 npm에 배포하는 방법

2023.12.15 - [Programming/C#] - [C#] C# 라이브러리 패키지를 Git Actions를 이용하여 NuGet에 배포하는 방법

2024.02.23 - [Programming/Python] - [Python] Python 라이브러리 패키지를 Github Actions를 이용하여 pypi에 배포하는 방법


선행 작업

이 글은 Maven central에 배포하려는 자바 라이브러리 프로젝트가 미리 github repository에 업로드 되어있어야 한다.


작업 순서

  1. Sonatype Jira에 이슈 등록 및 도메인 소유권 인증
  2. GPG Key 발급
  3. PGP Key server에 key 등록
  4. build.gradle 작성
  5. Github Actions Secrets 설정값 등록
  6. Github Actions Workflow 작성
  7. 패키지 배포

Sonatype Jira에 이슈 등록 및 도메인 소유권 인증

Maven central repository에 패키지를 업로드하기 위해서는 sonatype jira에 업로드 권한을 요청해야하는데, 권한을 얻기 위해서는 sonatype jira에 계정을 등록하고 issue 생성 및 도메인 소유권 인증 과정이 필요하다.

Sonatype Jira에 접속하여 회원가입을 한다.

 

(자체 도메인이 없는 경우 github 주소로 대체 가능)

 

상단 메뉴의 Create 버튼 클릭

 

issue 등록 화면

작성되어야 하는 항목은 다음과 같다.

  • Project : Community Support - Open Source Project Repository Hosting (OSSRH)
  • Issue Type : New Project
  • Summary : 이슈 제목 (본인은 패키지 이름으로 넘김)
  • Description : 패키지 요약 설명
  • Group Id : 패키지를 소유한 개인/단체/회사의 도메인 역순 (자바 패키지 네이밍 규칙 준수)
    • 도메인을 소유중인 경우 ex) example.com → com.example
    • 도메인이 없어 github 주소로 대체할 경우 ex) io.github.[Your github name]
  • Project URL : 패키지와 관련된 정보를 제공하는 페이지
  • SCM url : 패키지의 소스코드 repository 주소
  • Username : 자신의 Jira Username

이제 모두 입력했으면 Create 버튼을 눌러 이슈를 만든다.

 

이슈가 등록되면 위 화면이 표시된다. 상태가 Unresolved로 표시된다.

 

이슈를 등록되면 도메인 소유권을 인증하라는 안내 메일이 도착한다.

요약하자면 이슈를 등록할 때 입력했던 도메인(Group Id)의 DNS records에 특정 레코드를 추가하라는 내용이다.

 

레코드 값은 OSSRH-XXXXX (사진에서 모자이크된 부분)으로 되어있다. 해당 값을 복사한다.

 

자체 도메인을 입력한 경우와 github 도메인을 입력한 경우로 내용이 달라진다.


A. Group Id를 자체 도메인으로 등록한 경우

Cloudflare의 DNS record 관리 화면

도메인 주소를 관리하고 있는 DNS 관리시스템에 들어가 복사한 값을 TXT 레코드로 추가한다.


B. Group Id를 github 도메인으로 등록한 경우

복사한 값과 동일한 이름의 리포지토리를 github에 추가한다.


아까 생성했던 이슈 페이지에 들어가서 상태를 Respond로 변경하고 도메인이 인증될 때까지 기다린다. 도메인이 인증되기까지는 시간이 꽤 소요된다.

 

도메인 소유권이 인증되었다면 안내 메일이 또 도착한다.

Maven central repository에 업로드 하기 전 단계인 Nexus repository manager를 사용할 수 있게 되었다는 내용이다. 사진에 빨간 박스로 표시된 주소(s01.oss.sonatype.org)가 본인이 사용할 Nexus repository manager의 주소다.

 

다시 이슈 페이지를 확인하면 상태가 Resolved로 변경된 것을 확인할 수 있다. Nexus repository manager를 통해 Maven central repository로 Group Id가 일치하는 패키지를 업로드 할 수 있게 되었다.


GPG Key 생성하기

패키지를 업로드 할 수 있는 권한을 얻었음에도 패키지를 퍼블리싱 할 때 서명하지 않으면 업로드 할 수 없다. 패키지를 서명하기 위해서 GPG를 이용한 비밀키/공개키 방식을 사용할 것이다.

 

이 부분은 윈도우에서 UI 프로그램을 사용하는 방법으로 진행되며, mac OS에서는 터미널 명령어로 발급받으면 된다. (어짜피 핵심은 인증서 생성, 공개키/비밀키 발급이다.)

 

아래 링크에서 프로그램을 설치한다.

 

Gpg4win - Download Gpg4win

Gpg4win 4.2.0 (Released: 2023-07-14) You can download the full version (including the Gpg4win compendium) of Gpg4win 4.2.0 here: OpenPGP signature (for gpg4win-4.2.0.exe) SHA256: 829b5c8eb913fa383abdd4cf129a42e0f72d4e9924b2610134f593851f0ab119 Changelog Mo

gpg4win.org

 

Windows GPG Key 관리 프로그램 Kleopatra

새 OpenPGP 키 쌍 메뉴 클릭

 

인증서의 이름을 짓고 본인의 이메일 주소를 입력한다. 암구호 옵션을 활성화한 뒤 고급 설정 클릭

 

인증서의 암호화 방식을 선택하고 하단의 서명 옵션을 활성화한다. 인증서의 유효기간을 커스터마이징 할 수 있는데 한 곳에 박아두고 계속 쓸거면 기간 설정을 하지말자.

 

위에서 암구호 옵션을 활성화 했기 때문에 이 인증서에는 비밀번호(Passphrase)를 설정해야한다. 인증서에 사용할 비밀번호를 입력한다.

 

인증서가 생성되었다.

 

인증서를 우클릭하여 내보내기비밀 키 백업을 실행한 뒤 공개키와 비밀키를 파일로 저장한다.

 

공개키, 비밀키 파일

해당 키 파일들은 유실되지 않도록 잘 보관하도록 한다.


PGP Key server에 key 등록

위에서 발급한 인증서의 공개키를 PGP Key server에 등록해야한다.

아래는 일반적으로 사용되는 키 서버 목록이다. 이 글에서는 keyserver.ubuntu.com을 기준으로 진행한다.

 

Submit Key 클릭

 

공개키 입력 화면

이전 단계에서 발급받은 인증서의 공개키 내용을 여기에 입력하고 Submit Public Key 클릭

 

공개키 등록이 완료되었다.

 

다시 메인화면으로 돌아가서 인증서의 이름을 입력하고 검색하면 내 공개키가 정상적으로 업로드 되었는지 확인할 수 있다.

 

업로드된 공개키 검색결과


build.gradle 작성

Maven central에 패키지를 배포하려면 패키지의 요약 정보와 배포 설정을 build.gradle에 작성하여야 한다.

 

작성되어야하는 항목은 다음과 같다.

  • plugins : Gradle 플러그인. signing과 maven-publish를 반드시 포함
  • task sourceJar(), javadocJar() : jar 빌드 설정
  • publishing
    • publications : 패키지를 어떻게 배포할 것인지에 대한 설정
    • repositories : 패키지를 어디에 배포할 것인지에 대한 설정
  • signing : 패키지 서명 설정
plugins {
    id 'java-library'
    id 'signing'
    id 'maven-publish'
}

repositories {
    mavenCentral()
}

task sourcesJar(type: Jar) {
    archiveClassifier.set('sources')
    from sourceSets.main.allSource
}

task javadocJar(type: Jar) {
    archiveClassifier.set('javadoc')
    from tasks.javadoc.destinationDir
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
            artifact sourcesJar
            artifact javadocJar

            groupId = 'com.example'
            version = '1.0.0'
            artifactId = 'sample-package'

            pom {
                name.set('Sample package')
                description.set('This is sample.')
                url.set('https://github.com/your-github-name/repository-name')

                licenses {
                    license {
                        name.set('MIT License')
                        url.set('https://opensource.org/licenses/MIT')
                    }
                }

                developers {
                    developer {
                        id.set('Your id')
                        name.set('Your name')
                        email.set('???@???.???')
                    }
                }

                scm {
                    connection.set('scm:git:git://github.com/your-github-name/repository-name.git')
                    developerConnection.set('scm:git:ssh://github.com/your-github-name/repository-name.git')
                    url.set('https://github.com/your-github-name/repository-name')
                }
            }
        }
    }

    repositories {
        maven {
            name 'OSSRH'
            url uri('https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/')
            credentials {
                username = project.findProperty('ossrhUsername')?.toString()
                password = project.findProperty('ossrhPassword')?.toString()
            }
        }
    }
}

def gpgSecretKey = project.findProperty('gpgSecretKey')
def gpgPassphrase = project.findProperty('gpgPassphrase')

signing {
    useInMemoryPgpKeys(gpgSecretKey, gpgPassphrase)
    sign publishing.publications.mavenJava
}

 

위 내용에는 ossrhUsername, ossrhPassword, gpgSecretKey, gpgPassphrase 총 4가지의 파라미터가 포함되었다. 즉, 패키지 빌드 명령어 실행 시 4개의 파라미터를 입력해주어야 정상적으로 실행될 것이다.


Github Actions Secrets 설정값 등록

Github Actions에서 배포를 진행할 것이기 때문에 패키지 프로젝트가 미리 github repositoriy에 업로드 되어있어야 한다.

repository에 접속하여 Settings 탭을 클릭하여 Secrets and variables 항목의 Actions를 클릭한다.

 

이 곳에서는 Github Actions Workflow에서 사용할 환경변수를 만들 수 있다. New repository secret 클릭

 

환경변수의 이름을 짓고 Secret 칸에는 내용을 입력한 후 Add secret을 클릭한다.

생성해야하는 환경변수의 목록은 다음과 같다.

  • OSSRH_USERNAME : 위에서 가입한 Sonatype Jira 계정의 Username
  • OSSRH_PASSWORD : 위에서 가입한 Sonatype Jira 계정의 Password
  • GPG_SECRET_KEY : GPG 인증서의 비밀 키 내용
  • GPG_PASSPHRASE : GPG 인증서를 생성할 때 지정한 비밀번호

※ 단, GPG_SECRET_KEY의 경우 아래 예시처럼 개행문자를 텍스트 \n 으로 직접 변경해서 입력해야한다. (다음 단계에서 원인 설명)

 

변경 전
변경 후


Github Actions Workflow 작성

name: Build and Publish Java

on:
  workflow_dispatch:

jobs:
  build_and_publish_java:
    runs-on: ubuntu-latest
    env:
      OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
      OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
      GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
      GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Set up JDK
        uses: actions/setup-java@v2
        with:
          distribution: 'adopt'
          java-version: '8.0.392+8'

      - name: Build
        run: |
          echo "Building Java project"
          chmod +x ./gradlew
          ./gradlew build

      - name: Publish to Maven Repository
        run: |
          echo "ossrhUsername=${OSSRH_USERNAME}" >> gradle.properties
          echo "ossrhPassword=${OSSRH_PASSWORD}" >> gradle.properties
          echo "gpgSecretKey=${GPG_SECRET_KEY}" >>  gradle.properties
          echo "gpgPassphrase=${GPG_PASSPHRASE}" >> gradle.properties
          echo "Publishing to Maven repository"
          ./gradlew publish
        env:
          GRADLE_USER_HOME: ./gradle

 

프로젝트의 ./github/workflows 디렉토리에 deploy.yaml 파일을 작성하고 master 브랜치에 푸시한다.

 

버튼 클릭으로 실행할 것이기 때문에 on 트리거를 workflow_dispatch로 지정했다. 실행환경을 ubuntu-latest로 설정하고 환경변수 OSSRH_USERNAME, OSSRH_PASSWORD, GPG_SECRET_KEY, GPG_PASSPHRASE를 불러오도록 했다. 그리고 JDK의 버전을 지정해주었다.

 

빌드, 퍼블리싱을 순서대로 실행하도록 gradle 명령어 구성을 해주면 된다. (테스트 작업을 넣을 경우 빌드와 퍼블리싱 사이에 구성)

 

echo "ossrhUsername=${OSSRH_USERNAME}" >> gradle.properties
echo "ossrhPassword=${OSSRH_PASSWORD}" >> gradle.properties
echo "gpgSecretKey=${GPG_SECRET_KEY}" >>  gradle.properties
echo "gpgPassphrase=${GPG_PASSPHRASE}" >> gradle.properties

 

특히 퍼블리싱 단계에서는, 퍼블리싱 명령어를 실행하기 전에 프로젝트의 root 디렉토리에 gradle.properties 파일을 생성(createIfNotExist)하고 gradle.properties 파일 내용에 퍼블리싱 진행에 필요한 서명/계정을 작성하도록 해두었다.

이렇게 하는 이유는 repository에 서명/계정 정보를 노출시키지 않고 배포시에만 사용할 수 있도록 하기 위함이다.

 

(이전 단계에서 "GPG_SECRET_KEY의 경우 개행문자를 텍스트 \n 으로 직접 변경" 하라고 했던 이유는 gradle.properties가 개행문자를 인식하지 못하기 때문에 이를 해결하기 위함)

 

만약 실행 트리거를 자동으로 바꾸고 싶다면 on 트리거로 workflow_dispatch를 사용하지 말고

on:
  push:
    branches:
      - master

 

처럼 대신하면 된다.


패키지 배포

자바 패키지는 Sonatype nexus repository → Maven central repository 순으로 총 2단계로 배포가 진행된다.

 

repository에 접속하여 Actions 탭의 좌측 목록에서 위에서 작성한 workflow의 제목을 찾아서 클릭한다.

 

배포를 실행할 브랜치를 선택하고 Run workflow 클릭

 

 

모든 step이 정상적으로 종료되었으면 Sonatype nexus repository에 패키지가 배포된다.

 

Sonatype nexus repository manager에 접속한다.

 

로그인을 해야하는데 Nexus repository manager의 계정은 Sonatype Jira에 가입할 때 내부적으로 같이 생성된 것으로 보인다. Sonatype Jira의 계정정보를 입력하여 로그인한다.

 

좌측의 Staging Repositories 클릭하여 대시보드에 접속한다. 방금 전에 github actions를 통해 빌드된 패키지가 이 곳에 표시된다. (느려가지고 바로 표시가 안될 수 있음)

 

패키지를 선택하고 Close 버튼을 클릭. 그리고 잠시 대기하고 Refresh를 눌러준다.

 

다시 패키지를 선택하고 Release 버튼을 클릭하면 Maven central에 패키지가 업로드된다.

 

배포된 패키지는 Maven central에 검색하면 표시된다. 이제 java 기반 프로젝트에서 의존성 추가로 해당 패키지를 설치할 수 있다!


참조

 

Gradle

Gradle Deploying to OSSRH with Gradle - Introduction Just like Gradle can be easily configured to consume components from the Central Repository, it can be configured to publish to OSSRH. In order to deploy your components to OSSRH with Gradle, you have to

central.sonatype.org

 

Requirements

Why do we have Requirements Why Do We Have Requirements? In order to ensure a minimum level of quality of the components available in the Central Repository, we have established a number of requirements your deployment components have to meet. This allows

central.sonatype.org

 

The Signing Plugin

When signing publications, the resultant signature artifacts are automatically added to the corresponding publication. Thus, when publishing to a repository, e.g. by executing the publish task, your signatures will be distributed along with the other artif

docs.gradle.org

 

Gradle signArchives unable to read Secret Key

I am trying to publish my Java Library to Maven Central. A part of this involves using the signing gradle plugin to sign the artifacts. I need to sign it without using the keyring file as document ...

stackoverflow.com

 

Naming a Package (The Java™ Tutorials > Learning the Java Language > Packages)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

반응형

댓글