소프트웨어 개발, 특히 안드로이드와 같은 JVM(자바 가상 머신) 기반 생태계에서 의존성 관리(Dependency Management)는 프로젝트의 성패를 좌우하는 핵심 요소입니다. 개발자들은 필요한 라이브러리(Library)나 프레임워크(Framework)를 직접 다운로드하여 프로젝트에 포함시키는 대신, Gradle이나 Maven과 같은 빌드 자동화 도구를 사용하여 원격 저장소(Remote Repository)로부터 자동으로 내려받아 사용합니다. 이러한 원격 저장소의 역할은 수많은 개발자들이 만든 공개 라이브러리를 한곳에 모아두고, 체계적으로 버전을 관리하며, 누구나 쉽게 접근하여 사용할 수 있도록 하는 것입니다. 과거 안드로이드 개발의 중심에는 JCenter라는 저장소가 있었습니다. 하지만 2021년, JCenter 서비스를 제공하던 JFrog사의 서비스 종료 발표는 개발 생태계에 큰 파장을 일으켰고, 우리 모두에게 변화를 요구했습니다. 이 글에서는 JCenter의 시대가 저물고 MavenCentral이 다시 중심으로 떠오른 배경을 심도 있게 탐구하고, 현재 가장 이상적인 Gradle 저장소 구성 방법과 미래의 의존성 관리 전략에 대해 종합적으로 살펴보겠습니다.
JCenter의 시대는 끝났다: 왜 우리는 mavenCentral()
로 돌아가야 하는가
결론부터 말하자면, 이제 Gradle 설정에서 `jcenter()`는 즉시 제거해야 합니다. JCenter는 2022년 2월부로 완전히 서비스가 종료(Shutdown)되어 더 이상 라이브러리 조회 및 다운로드를 지원하지 않습니다. 만약 오래된 프로젝트의 `build.gradle` 파일에 `jcenter()` 선언이 남아있다면, 빌드 시 의존성을 찾지 못해 오류가 발생하거나, JFrog가 남겨둔 일부 캐시 서버에 의존하는 매우 불안정한 상태에 놓이게 됩니다. 이는 잠재적인 보안 위협과 빌드 실패의 원인이 되므로, 모든 프로젝트에서 `jcenter()`를 제거하고 JVM 생태계의 오랜 표준 저장소인 `mavenCentral()`로 이전하는 것은 더 이상 선택이 아닌 필수 사항이 되었습니다.
과거 안드로이드 스튜디오에서 새 프로젝트를 생성하면, `build.gradle` 파일에 다음과 같이 `jcenter()`가 기본으로 포함되어 있었습니다.
// 과거의 build.gradle (루트 프로젝트) // 경고: 이 설정은 현재 유효하지 않으며 사용해서는 안 됩니다. allprojects { repositories { google() jcenter() // <-- 바로 이 부분 } }
JCenter의 서비스 종료는 많은 개발자들에게 당혹감을 안겨주었지만, 이는 동시에 의존성 관리의 중요성을 되새기고, 더 안정적이고 표준화된 방식으로 전환하는 계기가 되었습니다. 이제 우리는 JCenter가 왜 그렇게 인기가 있었는지, 그리고 왜 역사의 뒤안길로 사라졌는지, 그 대안인 MavenCentral은 어떤 특징을 가지는지를 이해함으로써 보다 견고한 프로젝트를 구축할 수 있습니다.
Maven 저장소의 양대 산맥: JCenter와 MavenCentral의 역사
1. MavenCentral: JVM 생태계의 대들보
MavenCentral은 Apache Maven 프로젝트의 일부로 시작된, 자바 및 JVM 언어 생태계에서 가장 오래되고 권위 있는 중앙 저장소입니다. Sonatype이라는 회사에서 운영하고 있으며, 사실상 공개 라이브러리의 '공식' 저장소와 같은 역할을 수행해왔습니다. MavenCentral에 라이브러리를 배포하는 절차는 다소 엄격했습니다. 개발자는 다음과 같은 요구사항을 충족해야 했습니다.
- 고유한 Group ID: 일반적으로 소유권을 증명할 수 있는 도메인 이름의 역순(예: `com.google.android.material`)을 사용해야 했습니다.
- POM(Project Object Model) 파일: 프로젝트 정보, 의존성, 라이선스 등을 명시한 `pom.xml` 파일이 필수적이었습니다.
- GPG 서명: 배포되는 모든 아티팩트(jar, aar 파일 등)는 GPG(GNU Privacy Guard) 키로 서명되어, 배포자의 신원과 파일의 무결성을 보장해야 했습니다.
- 소스 및 Javadoc: 라이브러리 사용자들을 위해 소스 코드(`-sources.jar`)와 API 문서(`-javadoc.jar`)를 함께 배포하는 것이 강력히 권장되었습니다.
이러한 절차는 라이브러리의 신뢰성과 안정성을 높이는 데 크게 기여했지만, 개인 개발자나 소규모 팀에게는 배포 과정이 복잡하고 번거롭게 느껴지는 진입 장벽으로 작용하기도 했습니다. Sonatype의 OSSRH(Open Source Software Repository Hosting)와 JIRA 시스템을 통해 배포 권한을 얻는 과정 또한 직관적이지 않았습니다.
2. JCenter의 등장: 편리함으로 무장한 도전자
이러한 MavenCentral의 복잡성을 해결하기 위해 등장한 것이 바로 JCenter입니다. Bintray라는 플랫폼 위에서 JFrog사가 운영한 JCenter는 개발자 친화적인 경험을 무기로 빠르게 성장했습니다.
- 간편한 배포 절차: GPG 서명이 필수가 아니었고, 웹 UI를 통해 비교적 쉽게 라이브러리를 업로드하고 관리할 수 있었습니다.
- MavenCentral의 프록시(Proxy) 역할: JCenter는 자체 저장소 역할을 하면서 동시에 MavenCentral의 모든 콘텐츠를 포함하는 거대한 '상위 집합(Superset)'이었습니다. 즉, `jcenter()`만 선언해두면 JCenter 자체 라이브러리는 물론이고 MavenCentral에 있는 라이브러리까지 모두 접근할 수 있었습니다. 이 점이 JCenter가 폭발적인 인기를 얻게 된 결정적인 이유였습니다. 개발자들은 `mavenCentral()`을 굳이 함께 선언할 필요 없이 `jcenter()` 하나만으로 대부분의 의존성을 해결할 수 있었습니다.
- 안드로이드 스튜디오의 기본 채택: 구글은 안드로이드 스튜디오의 기본 프로젝트 템플릿에 JCenter를 기본 저장소로 채택했습니다. 이로 인해 수많은 안드로이드 개발자들이 자연스럽게 JCenter를 사용하게 되었고, JCenter는 명실상부한 안드로이드 생태계의 표준 저장소로 자리매김했습니다.
이러한 편리함 덕분에 JCenter는 수많은 라이브러리들의 보금자리가 되었고, 개발자들은 더 빠르고 쉽게 원하는 기능을 프로젝트에 통합할 수 있었습니다.
JCenter의 몰락: 예고된 작별과 그 영향
1. 서비스 종료 발표
영원할 것 같았던 JCenter의 시대는 2021년 2월 3일, JFrog사의 발표로 막을 내리게 됩니다. JFrog는 자사의 핵심 비즈니스인 Artifactory와 같은 유료 솔루션에 집중하기 위해 Bintray 및 JCenter 서비스를 단계적으로 종료한다고 발표했습니다. 주요 일정은 다음과 같았습니다.
- 2021년 3월 1일: 신규 라이브러리 제출 중단
- 2021년 5월 1일: Bintray 서비스 전체가 읽기 전용(Read-Only)으로 전환. 기존 라이브러리 다운로드는 가능하지만, 업데이트나 신규 업로드는 불가능해졌습니다.
- 2022년 2월 1일: JCenter 저장소 서비스 완전 종료. 더 이상 라이브러리 다운로드가 불가능해졌습니다.
2. 개발 생태계에 미친 파장
JCenter의 서비스 종료는 개발자들에게 두 가지 큰 과제를 안겨주었습니다.
- 저장소 설정 변경: 모든 프로젝트에서 `jcenter()`를 제거하고 `mavenCentral()`을 추가해야 했습니다. 이는 비교적 간단한 작업이었지만, 전 세계 수많은 프로젝트에 영향을 미치는 광범위한 변화였습니다.
- 'JCenter-Only' 라이브러리 문제: 더 심각한 문제는 JCenter에만 배포되고 MavenCentral로는 이전하지 않은 라이브러리들이었습니다. 많은 개인 개발자들이나 관리가 중단된 오픈소스 프로젝트들은 JCenter의 편리함 때문에 그곳에만 라이브러리를 배포해왔습니다. JCenter가 사라지면서 이 라이브러리들은 더 이상 빌드 시스템을 통해 접근할 수 없는 '고아 라이브러리(Orphaned Libraries)'가 되었습니다. 개발자들은 해당 라이브러리의 ▲대체재를 찾거나, ▲라이브러리 소스 코드를 직접 빌드하여 로컬에 포함시키거나, ▲누군가 다른 저장소(예: JitPack)로 옮겨주길 기다려야 하는 상황에 처했습니다.
이러한 혼란은 의존성 관리에서 특정 플랫폼이나 회사에 대한 과도한 의존이 얼마나 위험할 수 있는지를 보여주는 중요한 교훈이 되었습니다.
마이그레이션 실전 가이드: JCenter를 프로젝트에서 완전히 제거하기
이제 JCenter의 흔적을 프로젝트에서 지우고 안정적인 `mavenCentral()` 기반으로 전환하는 구체적인 방법을 알아보겠습니다. 이 과정은 오래된 프로젝트를 유지보수하거나, 과거의 튜토리얼을 참고할 때 특히 중요합니다.
1단계: 루트 `build.gradle` 또는 `build.gradle.kts` 파일 수정
프로젝트의 최상위 디렉토리에 있는 `build.gradle`(Groovy DSL) 또는 `build.gradle.kts`(Kotlin DSL) 파일을 엽니다. 이곳에는 모든 하위 모듈에 공통으로 적용되는 저장소 설정이 정의되어 있을 수 있습니다.
기존 설정 (제거 대상)
// build.gradle (Groovy) - 예전 방식 // 삭제해야 할 코드입니다. buildscript { repositories { google() jcenter() // <-- 이 줄을 삭제해야 합니다. } // ... } allprojects { repositories { google() jcenter() // <-- 이 줄도 삭제해야 합니다. } }
위 코드에서 `jcenter()`를 찾아서 모두 삭제합니다. 그리고 `mavenCentral()`이 선언되어 있는지 확인하고, 없다면 추가합니다. `google()` 저장소는 안드로이드 관련 라이브러리(AndroidX, Google Play Services 등)를 호스팅하므로 반드시 유지해야 합니다.
수정 후 권장 설정 (과거 방식)
이 방식은 여전히 유효하지만, 최신 Gradle에서는 `settings.gradle`을 사용하는 것이 더 권장됩니다(아래에서 자세히 설명).
// build.gradle (Groovy) - 수정된 방식 // 'jcenter()'를 제거하고 'mavenCentral()'을 명시합니다. buildscript { repositories { google() mavenCentral() // <-- 'jcenter()' 대신 추가 } // ... } allprojects { repositories { google() mavenCentral() // <-- 'jcenter()' 대신 추가 } }
만약 모듈 수준(예: `app/build.gradle`)에도 `repositories` 블록이 별도로 정의되어 있다면, 그곳에서도 `jcenter()`를 찾아 동일하게 수정해주어야 합니다.
2단계: 빌드 후 문제 해결
`jcenter()`를 제거하고 프로젝트를 다시 빌드(Sync 또는 Rebuild)했을 때, 일부 의존성을 찾지 못한다는 오류가 발생할 수 있습니다. 이는 해당 라이브러리가 'JCenter-Only' 라이브러리였음을 의미합니다.
> Could not find com.example.some-library:some-library:1.0.0. Searched in the following locations: - https://dl.google.com/dl/android/maven2/ - https://repo.maven.apache.org/maven2/
이런 오류가 발생하면 다음 해결책을 순서대로 고려해봐야 합니다.
- 최신 버전 확인: 해당 라이브러리의 GitHub나 공식 문서를 방문하여 최신 버전이 MavenCentral로 배포되었는지 확인합니다. 많은 라이브러리들이 JCenter 종료 후 MavenCentral로 배포처를 옮겼습니다. `build.gradle`에서 버전 번호만 최신으로 업데이트하면 문제가 해결될 수 있습니다.
- 대체 라이브러리 탐색: 만약 해당 라이브러리가 더 이상 관리되지 않아 MavenCentral로 이전하지 않았다면, 유사한 기능을 제공하는 다른 활성화된 라이브러리를 찾는 것이 가장 바람직한 해결책입니다. 이는 기능뿐만 아니라 보안 및 유지보수 측면에서도 중요합니다.
- JitPack 등 다른 저장소 확인: 일부 라이브러리는 공식 저장소 대신 JitPack.io와 같은 곳으로 옮겨갔을 수 있습니다. JitPack은 GitHub 저장소를 직접 빌드하여 라이브러리로 제공해주는 편리한 서비스입니다. 만약 그렇다면 `settings.gradle`이나 `build.gradle`에 JitPack 저장소를 추가해야 합니다.
// build.gradle (또는 settings.gradle의 repositories 블록)에 추가 maven { url 'https://jitpack.io' }
이 과정을 통해 프로젝트의 의존성을 JCenter로부터 완전히 독립시켜야 장기적으로 안정적인 빌드 환경을 확보할 수 있습니다.
`settings.gradle`을 활용한 현대적인 저장소 관리 전략
최신 버전의 Gradle(7.x 이상)에서는 프로젝트 전반의 의존성 관리 방식을 크게 개선했습니다. 기존에는 루트와 각 모듈의 `build.gradle` 파일에 `repositories` 블록이 흩어져 있어 관리가 불편하고 일관성이 떨어질 수 있었습니다. 이를 해결하기 위해 `settings.gradle` 파일 내에 `dependencyResolutionManagement` 블록을 사용하여 저장소를 중앙에서 관리하는 방식이 도입되었습니다.
이 방식의 장점은 다음과 같습니다.
- 중앙 관리(Single Source of Truth): 프로젝트의 모든 모듈이 사용할 저장소 목록을 단 한 곳에서 정의합니다. 이로 인해 설정이 명확해지고 유지보수가 용이해집니다.
- 보안 강화: 기본적으로 각 모듈의 `build.gradle`에 `repositories` 블록을 선언하는 것을 금지(fail on project repositories)하여, 의도치 않은 저장소(예: 보안에 취약한 http 저장소)가 추가되는 것을 방지할 수 있습니다.
- 빌드 성능 향상 가능성: Gradle이 의존성을 탐색할 때, 일관된 저장소 목록을 사용하므로 잠재적으로 탐색 과정을 최적화할 수 있습니다.
`settings.gradle(.kts)` 설정 예시
프로젝트 루트의 `settings.gradle` (Groovy) 또는 `settings.gradle.kts` (Kotlin) 파일을 다음과 같이 수정하는 것이 현재 가장 권장되는 방식입니다.
Groovy DSL (`settings.gradle`)
// settings.gradle (Groovy DSL) - 현대적인 권장 방식 pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() // 필요하다면 다른 저장소 추가 (예: JitPack) // maven { url 'https://jitpack.io' } } } rootProject.name = "MyApplication" include ':app'
Kotlin DSL (`settings.gradle.kts`)
// settings.gradle.kts (Kotlin DSL) - 현대적인 권장 방식 pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() // 필요하다면 다른 저장소 추가 (예: JitPack) // maven { url = uri("https://jitpack.io") } } } rootProject.name = "MyApplication" include(":app")
위 설정의 각 부분을 자세히 살펴보겠습니다.
pluginManagement
: Gradle 플러그인(예: Android Gradle Plugin) 자체를 가져올 저장소를 지정합니다. 일반적으로google()
,mavenCentral()
, 그리고 Gradle 공식 플러그인 포털인gradlePluginPortal()
을 포함합니다.dependencyResolutionManagement
: 프로젝트의 라이브러리 의존성(implementation
,api
등으로 추가하는 것들)을 가져올 저장소를 지정합니다.repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
: 이 설정이 핵심입니다. 만약 하위 모듈의build.gradle
파일 내부에repositories { ... }
블록이 발견되면 빌드를 실패시킵니다. 이를 통해 저장소 설정을settings.gradle
파일로 강제하여 일관성을 유지합니다.repositories { ... }
: 실제 라이브러리를 다운로드할 저장소 목록입니다. JCenter가 사라진 지금,google()
과mavenCentral()
이 표준 조합이 됩니다.
이렇게 `settings.gradle` 파일을 설정해두면, 루트나 모듈의 `build.gradle` 파일에서는 더 이상 `repositories` 블록을 신경 쓸 필요가 없어 코드가 훨씬 간결하고 명확해집니다.
MavenCentral을 넘어: google()
, JitPack
그리고 그 외 저장소들
이제 우리의 주 저장소는 MavenCentral이 되었지만, 실제 개발 환경은 여러 저장소가 목적에 따라 공존하는 형태입니다.
1. Google Maven Repository (`google()`)
구글은 안드로이드 생태계에 필수적인 라이브러리들을 자체 Maven 저장소를 통해 배포합니다. google()
은 이 저장소를 가리키는 편리한 단축 표현입니다 (`maven { url 'https://maven.google.com' }`과 동일). 여기에는 다음과 같은 핵심 라이브러리들이 포함됩니다.
- AndroidX 라이브러리: AppCompat, RecyclerView, ConstraintLayout, ViewModel, LiveData 등 안드로이드 개발의 근간을 이루는 거의 모든 라이브러리
- Material Components for Android: 최신 머티리얼 디자인 UI 컴포넌트
- Google Play Services: 지도, 위치, 인증, 결제 등 구글 서비스를 연동하기 위한 라이브러리
- 기타 구글 라이브러리: Firebase, Dagger, Hilt 등
따라서 안드로이드 개발에 있어 google()
저장소는 mavenCentral()
만큼이나 필수적입니다. 보통 우선순위를 고려하여 google()
을 mavenCentral()
보다 먼저 선언하는 것이 일반적입니다.
2. JitPack
JitPack은 'GitHub/GitLab/Bitbucket 저장소를 라이브러리 저장소로'라는 컨셉의 서비스입니다. 개발자가 라이브러리를 MavenCentral에 배포하는 복잡한 절차 없이, Git 태그(tag)나 커밋(commit) 해시를 기준으로 라이브러리를 빌드하여 제공합니다.
사용법은 간단합니다. `settings.gradle`에 `maven { url 'https://jitpack.io' }`를 추가하고, 의존성 선언 시 `implementation 'com.github.Username:Repository:version'` 형식으로 지정하면 됩니다.
JitPack은 다음과 같은 경우에 매우 유용합니다.
- 공식 배포가 이루어지지 않은 포크(fork)된 프로젝트를 사용해야 할 때
- 아직 정식 릴리즈되지 않은 최신 커밋의 기능을 테스트하고 싶을 때
- 간단한 개인 라이브러리를 빠르게 공유하고 싶을 때
하지만 JitPack은 빌드 요청 시점에 실시간으로 빌드를 수행하므로, 최초 빌드 속도가 다소 느릴 수 있고, 원본 Git 저장소의 상태에 따라 빌드가 실패할 위험도 있습니다. 따라서 핵심적인 프로덕션 라이브러리는 가급적 MavenCentral을 통해 받는 것이 안정적입니다.
3. 사설(Private) Maven 저장소
기업 환경에서는 외부에 공개할 수 없는 사내 라이브러리나, 보안을 위해 외부 저장소 접근을 통제하고 내부 프록시 서버를 통해 라이브러리를 캐싱하는 경우가 많습니다. 이때 JFrog Artifactory, Sonatype Nexus, GitHub Packages와 같은 사설 저장소 솔루션을 사용합니다. 이러한 저장소 역시 `maven { url '...' }` 형식을 통해 Gradle에 등록하여 사용할 수 있습니다.
라이브러리 배포자 관점: 왜 MavenCentral로의 이전이 필수였나?
JCenter 종료는 라이브러리 '소비자'뿐만 아니라 '생산자'에게도 큰 변화를 요구했습니다. JCenter의 간편한 배포에 익숙했던 수많은 오픈소스 개발자들은 자신의 라이브러리가 계속해서 사용될 수 있도록 MavenCentral로 배포 파이프라인을 옮겨야 했습니다.
이 과정은 초기에 다소 어려움이 따랐지만, 커뮤니티와 Sonatype의 노력으로 과거보다 훨씬 수월해졌습니다.
- 자동화된 배포 플러그인:
vanniktech/gradle-maven-publish-plugin
과 같은 Gradle 플러그인들은 POM 파일 생성, GPG 서명, Sonatype 서버로의 업로드 및 릴리즈 과정을 거의 자동으로 처리해줍니다. - 개선된 문서와 커뮤니티 지원: MavenCentral 배포 방법에 대한 수많은 블로그 글과 튜토리얼이 공유되면서 진입 장벽이 크게 낮아졌습니다.
- 신뢰의 상징: 비록 절차는 조금 더 까다롭지만, MavenCentral에 라이브러리를 배포한다는 것 자체가 해당 프로젝트가 일정 수준의 품질과 관리 체계를 갖추고 있다는 '신뢰의 증표'로 여겨지게 되었습니다.
결과적으로 JCenter의 종료는 JVM 라이브러리 생태계가 단기적인 편리함보다는 장기적인 안정성과 신뢰성을 중시하는 방향으로 재편되는 건강한 전환점이 되었다고 평가할 수 있습니다.
결론: 안정적인 의존성 관리를 위한 새로운 표준
JCenter의 시대가 저물면서 우리는 의존성 관리의 패러다임 변화를 겪었습니다. 이 변화의 핵심은 명확합니다.
- `jcenter()`는 이제 유효하지 않습니다. 모든 프로젝트에서 즉시 제거하고 `mavenCentral()`로 대체해야 합니다.
- 현대적인 Gradle 프로젝트의 표준 저장소 구성은
google()
과mavenCentral()
의 조합입니다. 안드로이드 개발에서는 두 저장소 모두 필수적입니다. - 저장소 관리는
settings.gradle(.kts)
의dependencyResolutionManagement
블록을 통해 중앙에서 관리하는 것이 가장 이상적입니다. 이를 통해 프로젝트의 일관성과 보안, 유지보수성을 크게 향상시킬 수 있습니다. - JCenter에만 존재하던 라이브러리는 적극적으로 대체재를 찾거나, 최신 버전이 MavenCentral로 이전했는지 확인하는 노력이 필요합니다. 이는 프로젝트를 건강하게 유지하는 필수적인 과정입니다.
특정 서비스의 영속성을 맹신하기보다, 개방적이고 표준화된 생태계의 가치를 다시 한번 확인하는 계기가 되었습니다. JCenter가 남긴 교훈을 바탕으로, 우리는 이제 `mavenCentral()`이라는 견고한 반석 위에 `google()`, `JitPack` 등 다양한 저장소를 목적에 맞게 활용하며 더욱 안정적이고 효율적인 개발을 이어나가야 할 것입니다. 올바른 저장소 설정은 단순히 빌드를 성공시키는 것을 넘어, 우리 소프트웨어의 안정성과 보안, 미래 확장성을 담보하는 첫걸음입니다.
0 개의 댓글:
Post a Comment