qry

2024년 상반기에 issues.sonatype.org가 폐기됨을 발표했습니다 https://central.sonatype.org/faq/what-happened-to-issues-sonatype-org/#what-happened-to-issuessonatypeorg

기존의 지라 티켓과 Nexus repository(OSSRH)를 통해 업로드하는 방식은, 기존 사용자는 그대로 이용가능하지만 신규 사용자는 Maven Central portal 을 통해 새로운 방식으로 라이브러리를 업로드 해야합니다. 이 포스팅에서는 Maven Central portal 에 계정을 만들고, 여러 방법 중 본인의 github 계정을 이용해서 라이브러리를 배포하는 방법을 소개합니다.

현재(2024.06.16), Maven Central 에 Central Publishing Portal 을 이용해서 publish 하는 공식 gradle plugin 은 없습니다. link.

대신에, vanniktech plugin 을 사용합니다.

Prerequisites

Maven Central 계정 생성

먼저 계정을 생성합니다. https://central.sonatype.com/

Namespace 생성 및 검증

qry

Add Namespace 버튼을 클릭하고 다음과 같은 규칙으로 namespace 를 생성합니다.

본인의 github 계정이 example 이라면 -> io.github.example

그렇게 Unverified Namespace 가 생성이 됩니다.

qry

Verification Key 를 복사하여 자신의 github repository 에 verification key 와 동일한 이름으로 레포지토리를 생성합니다.

verification key 가 rdaqyyjnic 이라면, rdaqyyjnic 라는 public repository 를 생성합니다.
즉, github.com/example/rdaqyyjnic 라는 url 이 유효하게 됩니다.

이후 verify namespace 버튼을 클릭하여 검증을 완료합니다. verified 후에는 레포지토리를 삭제해도 괜찮습니다.

Generate Token

id 와 password 대신 사용할 토큰을 생성해야 합니다. Generate User Token 버튼을 클릭하여 토큰을 생성합니다.

qry

다음과 같이 노출되는 토큰의 아이디와 패스워드를 복사하여 둡니다.

qry

Generate GPG Key

먼저 GPG 키를 생성하기 위한 gnupg 를 설치합니다.

$ brew install gnupg
$ gpg --full-generate-key --command-fd 0 --status-fd 1 --pinentry-mode loopback

만들어진 GPG 키를 확인합니다.

$ gpg --list-keys --keyid-format LONG

[keyboxd]
---------
pub   rsa4096/F7FAB2CA 2024-05-25 [SC]
      DB506D8D4302948FB17BB5370886B370F7FAB2CA
uid         [ultimate] cocoslime <dongmen94@gmail.com>
sub   rsa4096/CF4E7347 2024-05-25 [E]

public key 를 key server 에 업로드합니다.

gpg --keyserver keyserver.ubuntu.com --send-keys F7FAB2CA

F7FAB2CA 는 Key id 입니다. 자신의 key id 로 바꾸어서 명령어를 입력해 주세요.

gpg 키 파일 생성

gpg --keyring secring.gpg --export-secret-keys > ~/.gnupg/secring.gpg

Publish

build.gradle.kts

build.gradle.kts 에 com.vanniktech.maven.publish plugin 를 통해 maven central 에 publish 하는 스크립트를 작성합니다.

먼저 plugin 을 적용합니다.

plugins {
    id("com.vanniktech.maven.publish") version "0.28.0"
}

다음은 Maven Central 에 게시를 활성화하고, GPG 서명을 활성화합니다.

import com.vanniktech.maven.publish.SonatypeHost

mavenPublishing {
  publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)

  signAllPublications()
}

POM 을 configure 합니다. pom 은 프로젝트와 함께 게시되며 프로젝트 좌표와 URL 및 사용된 라이선스와 같은 프로젝트에 대한 일반적인 정보를 나타냅니다.

mavenPublishing {
  coordinates("com.example.mylibrary", "mylibrary-runtime", "1.0.3-SNAPSHOT")

  pom {
    name.set("My Library")
    description.set("A description of what my library does.")
    inceptionYear.set("2020")
    url.set("https://github.com/username/mylibrary/")
    licenses {
      license {
        name.set("The Apache License, Version 2.0")
        url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
        distribution.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
      }
    }
    developers {
      developer {
        id.set("username")
        name.set("User Name")
        url.set("https://github.com/username/")
      }
    }
    scm {
      url.set("https://github.com/username/mylibrary/")
      connection.set("scm:git:git://github.com/username/mylibrary.git")
      developerConnection.set("scm:git:ssh://git@github.com/username/mylibrary.git")
    }
  }
}

이렇게 완성된 build.gradle.kts 의 예시입니다.

import com.vanniktech.maven.publish.SonatypeHost

plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    id("com.vanniktech.maven.publish")
}

android {
   // .. 생략
}

dependencies {
   // ... 생략
}

mavenPublishing {
    publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)

    signAllPublications()

    coordinates("io.github.cocoslime", "watermark-cover", "0.0.1")

    pom {
        name = "WatermarkCover"
        description = "Cover view/compose with Watermark Text"
        url = "https://github.com/cocoslime/watermark-cover"
        inceptionYear = "2024"

        licenses {
            license {
                name = "The Apache License, Version 2.0"
                url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
            }
        }
        developers {
            developer {
                id = "cocoslime"
                name = "cocoslime"
                url = "https://github.com/cocoslime"
            }
        }

        scm {
            connection = "scm:git:github.com/cocoslime/watermark-cover.git"
            developerConnection.set("scm:git:ssh://github.com:cocoslime/watermark-cover.git")
            url = "https://github.com/cocoslime/watermark-cover/tree/master"
        }
    }
}

gradle.properties

프로젝트 루트의 gradle.properties 혹은 ~/.gradle/gradle.properties 에 다음 내용을 추가합니다. 다음 내용이 공개 레포지토리에 푸시되지 않도록 주의하세요.

mavenCentralUsername=TOKENUSERNAME # Maven Central token 의 username 
mavenCentralPassword=TOKENPASSWORD # Maven Central token 의 password 
signing.keyId=F7FAB2CA # key 의 id
signing.password=paswword # key 의 패스워드 
signing.secretKeyRingFile=/Users/guest/.gnupg/secring.gpg # secring.gpg 의 경로

publish task

다음 command 를 통해 maven central 에 publish 합니다.

./gradlew publishAllPublicationsToMavenCentralRepository --no-configuration-cache

이제, Build 성공 메시지가 나오면 끝입니다!

...
BUILD SUCCESSFUL in 23s

Maven central 포탈 사이트에서 사이트의 Deployments 를 확인해봅니다. Drop 버튼과 Publish 버튼 모두 활성화 되어 있다면, Publish 버튼을 눌러서 활성화합니다.

몇 분이내에 배포가 완료되어 포탈에서 검색을 통해 라이브러리 페이지에 접근이 가능하며, 라이브러리 import 가 가능합니다.

qry

트러블 슈팅

Invalid token 에러

publish 태스크에서 다음과 같은 에러가 발생할 수 있습니다.

Upload failed: {"error":{"message":"Invalid token"}}

포탈의 토큰의 유효기간이 만료되었을 수 있으므로 토큰을 재발급하여 다시 토큰 정보를 기입합니다. 혹은, 토큰 정보가 제대로 기입되어 있는지(gradle.properties) 확인합니다.

Deployments 에서 Dependency version information is missing 에러

publish task 는 성공 했는데, “Dependency version information is missing” 메시지와 함께 에러가 발생한다면, build.gradle.kts 의 dependencies 블록을 확인합니다.

저는 compose-bom 을 사용해서 의존성을 관리하고 있었고, 아래 에러 메시지가 발생했습니다.

qry

compose-bom 은 Google maven 저장소에 존재하여, Maven central 에서 찾지 못하는 문제입니다. compose-bom 대신 직접 의존성 버전을 명시하여 해결할 수 있습니다.

p.s. Ian Wagner 님의 코멘트 에 의하면, compose-bom 을 사용하기 위해선 pom 블록에 다음을 작성해 maven central 에 google maven repo 에 대한 정보를 알려주어 해결할 수 있습니다. 이 코드는 생성되는 POM 파일에 Google Maven 저장소 정보를 추가합니다.


mavenPublishing {
    ...

    pom {
        ...

        withXml {
          def repo = asNode().appendNode('repositories').appendNode('repository')
          repo.appendNode('name', 'Google')
          repo.appendNode('id', 'google')
          repo.appendNode('url', ' https://maven.google.com/')
        }

하지만 가능한 모든 의존성을 같은 Maven Central 에서 해결할 수 있도록 하는 것이 더 좋은 방향이라고 생각이 되어, 추천드리는 방식은 아닙니다.

Leave a comment