그래프 라이브러리 6. Maven Central에 배포하기
Last updated: Dec 13, 2023
이번에 부스트캠프 그룹 프로젝트를 하면서 그래프 라이브러리를 구현한 과정을 정리해보았다. 이번 포스팅에서는 완성한 그래프 라이브러리를 Maven Central Repository에 배포하는 과정에 대해 설명하고자 한다. (project repo, library repo)
Sonatype 가입, 이슈 생성
우선 계정이 없다면 https://issues.sonatype.org/에 들어가서 회원가입을 한다.

회원가입이 완료되었으면, 로그인한 페이지에 이슈를 하나 생성해야 된다.

이슈는 새로 만드는 프로젝트이기 때문에 New Project로 선택을 한다.
Summary에는 보통 라이브러리 이름을 넣는다. Description도 크게 신경쓸 필요가 없다. 다른 필수 입력 란은 다 채워넣으면 되는데 가장 중요한 부분이 group id 이다.
이는 흔히 우리가 gradle 파일에서 라이브러리를 import 할때 implementation("packagename:version")
과 같이 사용하는데, 여기서 package name에 해당하는 부분이다. 보통 자바 패키지 네이밍 컨벤션과 같이 본인이 컨트롤 하는 도메인 이름을 뒤집어서 사용하는데, 라이브러리명 이전까지의 도메인 주소가 group id 이다. 예를 들어서 com.example.materialchart
라는 라이브러리가 있으면, 라이브러리 식별은 materialchart
로 하게 되고 group id는 com.example
이 된다.
이 부분이 중요한 이유는 현재 배포하려고 하는 라이브러리의 패키지 이름이 본인이 컨트롤하는 도메인의 역순서여야만 한다는 거다. 이슈가 생성되면 시스템에서 자동으로 DNS 레코드를 조회해서 인증하는 방식이다. 따라서 라이브러리를 배포할때 소스에서 패키지 이름이 본인이 컨트롤하는 도메인이 아닌 경우 지금 바꿔주도록 하자.
우리 팀은 priceguard.app
이라는 도메인을 소유하고 있고, 페키지명은 app.priceguard.materialchart
로 설정해두었기 때문에, group id는 app.priceguard
라고 입력했다.
만약 본인이 소유하고 있는 도메인이 없으면, GitHub Pages를 이용하여 인증하는 방법이 있으니 group id를 io.github.<USERNAME>
으로 설정하자. (참고)
Issue를 생성했으면, group id가 본인 소유가 맞는지 인증을 해야 Issue가 정상적으로 닫히고, 본격적으로 패키지를 repository에 올릴 수 있다.
group id 인증은 domain root에 TXT 레코드로 해당 이슈번호를 추가해놓으면 자동으로 인증된다.

인증이 완료되면 Issue에 자동으로 댓글이 달리면서 Issue가 닫힌다.

프로젝트 배포 설정
이제 프로젝트 빌드 설정 및 배포 설정을 해야한다.
우선 배포를 용이하게 하기 위해 Maven Publish Plugin을 사용할 예정이다. 원래 Maven Central에 업로드를 하려면 여러 요구사항을 만족하고 pom.xml과 같은 파일도 작성해서 한번에 업로드를 해야 한다. 하지만 해당 플러그인을 사용하게 되면 gradle에서 설정한 값들로 필요한 정보와 파일들을 알아서 넣어준다.
우선 library쪽 build.gradle 파일에 다음과 같이 플러그인을 추가한다. 이따가 패키지 서명에 사용할 Signing 플러그인도 같이 추가해주자.
plugins {
`maven-publish`
signing
}
groupId는 아까 위에서 인증할 때 썼던 도메인의 역순서이고, artifactId는 이 라이브러리의 패키지 이름이다. 실제로 배포되었을때 사용자들이 import를 할때는 groupId:artifactId:version
와 같은 문자열로 가져온다. 우리같은 경우에는 groupId는 app.priceguard
, artifactId는 materialchart
로 import를 할때는 implementation("app.priceguard:materialchart:<버전>")
으로 사용하게 된다.
publishing {
publications {
afterEvaluate {
create<MavenPublication>("release") {
from(components["release"])
groupId = groupName
artifactId = packageName
version = versionCode
pom {
name.set(artifactId)
description.set("라이브러리 간단 설명")
url.set("https://github.com/라이브러리_레포_주소")
licenses {
license {
name.set("MIT License")
url.set("https://opensource.org/license/mit/")
}
}
developers {
developer {
name.set("배포자 이름")
email.set("연락 받을 이메일 주소")
}
}
scm {
url.set(pom.url.get())
connection.set("scm:git:${url.get()}.git")
developerConnection.set("scm:git:${url.get()}.git")
}
}
}
}
}
repositories {
maven {
name = "SonatypeSnapshot"
setUrl("https://s01.oss.sonatype.org/content/repositories/snapshots/")
credentials {
username = getExtraString("ossrhUsername")
password = getExtraString("ossrhPassword")
}
}
maven {
name = "Sonatype"
setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = getExtraString("ossrhUsername")
password = getExtraString("ossrhPassword")
}
}
}
}
Repositories에서 설정한 SonatypeSnapshot과 Sonatype는 따지면 서로 다른 레포지토리이다. Snapshot은 해당 라이브러리의 snapshot 버전을 올리는 곳이고 Sonatype은 Release 빌드를 올리는 곳이다. Snapshot 같은 경우에는 versionCode가 무조건 SNAPSHOT으로 끝나지 않으면 추후에 업로드 할때 오류가 난다. 반대로도 마찬가지이다. Release repo에 SNAPSHOT을 올리려고 하면 오류가 나니 versionCode를 설정할때 주의하자.
getExtraString
함수는 local.properties를 파싱해서 값을 가져오게끔 설정하였다. ossrhUsername과 Password는 아까 Sonatype에서 로그인할때 썼던 credential을 입력하면 된다.
extra["signing.keyId"] = null
ext["signing.password"] = null
ext["signing.secretKeyRingFile"] = null
ext["ossrhUsername"] = null
ext["ossrhPassword"] = null
val secretPropsFile = project.rootProject.file("local.properties")
if (secretPropsFile.exists()) {
secretPropsFile.reader().use {
Properties().apply {
load(it)
}
}.onEach { (name, value) ->
ext[name.toString()] = value
}
} else {
ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID")
ext["signing.password"] = System.getenv("SIGNING_PASSWORD")
ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE")
ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME")
ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD")
println(ext["ossrhUsername"])
}
fun getExtraString(name: String) = ext[name]?.toString()
때마침 local.properties에서 SIGNING_KEY 관련 내용이 나오니 SIGNING_KEY에 대한 것도 추가해준다. GPG로 Signing Key를 만드는 방법은 공식 문서에 상세하게 설명되어 있다. Key를 생성하고 꼭 Public Key를 Key Server에 보내주는것도 잊지 말자. 이는 나중에 해당 키로 패키지 사인을 했을때 외부에서 Public Key로 검증을 하기 때문에 꼭 필요하다.
키를 생성했으면 keyId에는 gpg --keyid-format SHORT --list-keys
에서 Siging 할때 사용할 공개키 앞에 뜨는 8자리 ID 값을 입력하면 된다. 아래 예시에선 F36F5BBD
가 된다.
pub rsa4096/F36F5BBD 2023-12-07 [SC]
DDA6DA5453AC1A14260621A9A5764C8DF36F5BBD
secretKeyRingFile은 gpg --export-secret-keys [keyid]
명령어를 이용해 .gpg
파일을 export한 후, 그 key의 위치를 절대경로로 입력하면 된다.
마무리로 gradle에서 release 빌드를 할때는 sign을 하게끔 설정해준다.
signing {
afterEvaluate {
sign(publishing.publications["release"])
}
}
Nexus에 업로드
위 설정들을 끝마쳤으면 아래 명령어를 실행하여 Nexus Repository에 올려준다. 해당 커맨드를 실행하면 빌드 후 repository에 업로드까지 알아서 해준다.
./gradlew publishReleasePublicationToSonatypeRepository


업로드가 성공적으로 완료되었으면, https://s01.oss.sonatype.org/에 들어가서 Staging Repositories에 위와 같이 올라갔는지 확인해본다.
만약 성공적으로 올라가서 해당 업로드 된 버전을 배포하고 싶다 -> Close
실수가 있어 배포하고 싶지 않다 -> Drop
을 선택하면 된다. 처음에 용어가 헷갈려서 왜 업로드를 했는데 배포가 안되는지 궁금해서 몇십분씩 기다렸는데, Close를 해야 실제로 Release Repository에 반영할 수 있게끔 검사를 한다.
배포
위에서 Repository를 Close처리하면 해당 패키지가 Maven Central Repository에 들어가기에 적합한지 유효성 검사를 한다. 유효하지 않음이 확인되면 이런식으로 Close 하는 도중에 오류가 난다.

만약 성공적으로 Close가 됐다면, 아까 Stage Repository쪽 옵션에서 Release 버튼이 활성화 되어 나온다. Release 버튼을 누르면 Release Repository에 반영된다. 하지만 이는 Maven Central Repository와 Sync를 하는데 시간이 걸리기 때문에, 바로 확인이 불가능할 수도 있다.

성공적으로 배포가 되었다면, 좌측 Artifact Search에서 패키지명을 검색해서 확인할 수 있다.

조금 있다가 Maven Central에서 패키지명을 검사해보면, 성공적으로 올라와있음을 확인할 수 있다. Maven Central에 정상적으로 올라온것이 확인되면, 이제 어디서든 import해서 사용할 수 있다.