Development/Android

libs.versions.toml 써보셨나요?

Jamie 2026. 3. 22. 14:51
반응형

안녕하세요. Jamie입니다.

혹시 최근에 Android Studio로 새로 프로젝트를 만드신분들중에

“libs.versions.toml” 이라는 무언가가 추가된걸 알아차리셨나요? 혹은 app수준의 build.gradle을 가서 라이브러리를 추가하려고 하는데,

 

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.compose.ui)
    implementation(libs.androidx.compose.ui.graphics)
    implementation(libs.androidx.compose.ui.tooling.preview)
    implementation(libs.androidx.compose.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.compose.ui.test.junit4)
    debugImplementation(libs.androidx.compose.ui.tooling)
    debugImplementation(libs.androidx.compose.ui.test.manifest)
}

 

이런거 보신적 있으시죠? 우리가 이전에 안드로이드에서 라이브러리를 추가하기 위해 사용하던 build.gradle의 모습은 이런겁니다.

 

dependencies {
  implementation 'com.github.bumptech.glide:glide:5.0.5'
}

 

근데 "implementation(libs.androidx.core.ktx)" 이러식으로 바뀌었는데, 관심없으신분들은 위와 같이 그냥 추가하셨을겁니다.

물론 그래도 사용은 가능합니다.

하지만 libs.versions.toml 파일을 한번 살펴볼까요?

 

[versions]
agp = "9.0.1"
coreKtx = "1.18.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
lifecycleRuntimeKtx = "2.10.0"
activityCompose = "1.13.0"
kotlin = "2.0.21"
composeBom = "2024.09.00"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

 

위의 내용은 libs.version.toml의 내용입니다. 눈치 빠르신분들은 아시겠지만 오늘 소개할 버전 카탈로그라고 하는 녀석입니다.

버전 카탈로그는 기존에 관리하던 의존성 방식보다 체계적이고, 성능에도 이점이 있습니다.

 

왜 사용하는가?

버전 카탈로그(libs.versions.toml)는 단순히 유행이라서 쓰는 게 아니라,

멀티 모듈 프로젝트대규모 팀 협업에서 발생하는 고질적인 문제들을 해결하기 위해 등장했습니다.

 

1. 타입 안정성(Type-safety)과 자동 완성

기존 Groovy 기반의 ext.versions 방식이나 build.gradle.kts에 문자열로 직접 넣는 방식은 오타에 매우 취약했습니다.

  • 이점: libs.versions.toml에 정의하면 Gradle이 접근자 코드를 생성해 줍니다.
    IDE에서 libs.androidx.core.ktx처럼 자동 완성이 지원되니 오타로 인한 빌드 에러가 사라지죠.

2. 빌드 성능 최적화 (vs buildSrc)

이전의 자주 쓰던 방식이었던 buildSrc는 코드 한 줄만 바꿔도 프로젝트 전체의 빌드 캐시가 깨져서 전체 Rebuild가 일어나는 치명적인 단점이 있었습니다.

  • 이점: 버전 카탈로그는 텍스트 파일 형식이라, 버전 하나 바꾼다고 해서 전체 모듈을 다시 컴파일하지 않습니다.
    빌드 증분(Incremental Build) 유지에 훨씬 유리해요.

3. 의존성 번들링(Bundles)

Compose나 Retrofit처럼 세트로 따라다니는 라이브러리들이 있죠? bom..이걸 하나로 묶을 수 있습니다.

  • 이점: 여러 모듈에서 공통으로 쓰는 라이브러리 묶음을 libs.bundles.compose 한 줄로 선언할 수 있어,
    개별 모듈의 build.gradle 파일이 훨씬 깔끔해집니다.

4. 중앙 집중식 관리 (Single Source of Truth)

멀티 모듈 구조에서 각 모듈마다 버전이 다르게 파편화되는 걸 막아줍니다.

  • 이점: 특정 라이브러리 버전을 올릴 때 toml 파일 하나만 수정하면 모든 모듈에 즉시 반영됩니다.
    아키텍처 관점에서 의존성 제어권이 한곳으로 모이는 셈이죠.

5. 가독성과 협업 효율

라이브러리 선언부와 실제 버전 숫자가 분리되어 있어, 어떤 라이브러리를 쓰고 있는지 한눈에 들어옵니다.

  • 이점: 코드 리뷰 시 어떤 의존성이 추가되거나 변경되었는지 확인하기가 매우 직관적입니다.

 

예를 들어서 설명해드립니다.

app 하나만 있을 때는 그냥 기존대로 build.gradle 에만 선언해도 문제가 없었겠지만,
app2, app3가 생기고 모듈이 늘어나기 시작하면 지옥문이 열립니다.

멀티 모듈 전환 시 겪게 될 진짜 불편한 점

  • 버전 불일치 (Version Drift): app1은 Retrofit 2.9.0을 쓰는데, 새로 만든 app2 개발자는 실수로 2.8.1을 넣습니다. 빌드는 되는데 런타임에서 특정 API가 없다고 터지거나, 트랜지티브 디펜던시(Transitive Dependency) 충돌로 삽질하게 됩니다.
  • 반복되는 노가다 (Boilerplate): Compose 설정, Hilt, Coroutine 같은 필수 라이브러리들을 모든 모듈의 build.gradle에 똑같이 복사-붙여넣기 해야 합니다. 모듈이 많아 질수록 나중에 수정하려면 고통이 수반됩니다.
  • 업데이트 공포: 라이브러리 하나 버전을 올리려면 모든 모듈의 build.gradle 파일을 하나씩 열어서 수정해야 합니다.
    하나라도 빼먹으면? 또다시 버전 불일치로 고생하게 됩니다.

 

버전 카탈로그(libs.versions.toml) 도입 가이드

1단계: gradle/libs.versions.toml 파일 생성

프로젝트 루트의 gradle 폴더 안에 파일을 만들고 아래처럼 정의하세요.

[versions]
# 버전 번호만 따로 관리
kotlin = "1.9.20"
compose-bom = "2024.02.00"
retrofit = "2.9.0"

[libraries]
# 실제 라이브러리 정의
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
network-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }

[bundles]
# 세트로 묶어서 한 번에 가져오기
compose = ["androidx-compose-ui", "androidx-compose-material3"]

 

2단계: 각 모듈(app, app2, app3)에서 사용

이제 각 모듈의 build.gradle.kts에서는 문자열 버전 없이 깔끔하게 호출만 하면 됩니다.

dependencies {
    // 개별 호출
    implementation(libs.network.retrofit)
    
    // 번들로 한 번에 호출 (Compose 관련 수십 줄이 한 줄로 줄어듦)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.bundles.compose)
}

 

반응형