Gradle Version Catalog를 통한 멀티 모듈 프로젝트 의존성 관리

이상민·2023년 12월 9일
0

Gradle에서 의존성 버전 관리하기

개발을 하면서 모든 코드를 직접 작성하는 경우는 매우 적다. 보통 라이브러리나 프레임워크를 사용하여 바닥에서 부터 코딩하지 않고 많은 기능을 사용할 수 있다. 이러한 코드들은 버전에 따라 기능이 deprecate 되거나 regression이 발생하거나 다른 의존성의 버전끼리 충돌하는 등 다양한 문제가 있을 수 있기 때문에 안정화된 버전을 관리 하는 것이 중요하다.

가장 기초적인 버전 관리 방법은 버전을 명시하는 것이다

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-html:0.9.1")
}

문제 1. 멀티 모듈에서 의존성 버전 관리

각 의존성마다 버전을 명시하기 때문에 단일 모듈로 구성된 프로젝트의 경우에는 앞에서 언급한 방법이 가장 쉽고 직관적이다. 하지만 멀티 모듈 프로젝트를 구성한다면 build.gradle 파일이 여러개 생기기 때문에 버전을 여러번 작성해줘야 한다.

// moduleA/build.gradle.kts
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-html:0.9.1")
}

// moduleB/build.gradle.kts
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-html:0.9.1")
}

만약 버전 업그레이드를 하고 싶다면? 두 파일을 모두 수정해야하는 문제가 있다. 따라서 아래와 같이 버전을 별도 파일로 분리하여 관리할 수 있다.

// gradle.properties
kotlinXVersion=0.9.1

// moduleA/build.gradle.kts
val kotlinXVersion: String by project
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-html:${kotlinXVersion}")
}

// moduleB/build.gradle.kts
val kotlinXVersion: String by project
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-html:${kotlinXVersion}")
}

위와 같이 수정하면 이제 gradle.properties 파일을 한 번만 수정하면 모든 프로젝트의 버전을 변경할 수 있다. 다만 kotlinXVersion 변수를 계속 선언해줘야 하는 보일러 플레이트 코드가 있다.

문제 2. 플러그인 버전 관리

플러그인의 버전 관리는 약간 다른 방식으로 해야한다. 단일 모듈 프로젝트에서 플러그인은 아래와 같이 사용할 수 있다.

// build.gradle.kts
plugins {
	kotlin("jvm") version 1.8.22
}

만약 플러그인 버전도 gradle.properties에서 다른 의존성 버전과 같이 관리하고 싶다면? 아쉽게도 plugins 블록에서는 명시적으로 버전을 작성할 수만 있어서 아래와 같이 settings.gradle에서 별도로 관리해야했다. 여러 파일을 왔다 갔다 해야 하고 불편하다.

// gradle.properties
kotlinVersion=1.8.22

// settings.gradle.kts
pluginManagement {
    val kotlinVersion: String by settings
    
    plugins {
        kotlin("jvm") version kotlinVersion
    }
}

// build.gradle.kts
plugins {
	kotlin("jvm")
}

이외에도 기존 gradle에는 ext, buildSrc, composite build를 통해 의존성의 버전을 관리할 수 있었지만 모두 여러 파일을 왔다 갔다 해야하는 불편함이 있다.

Gradle Version Catalog

버전 카탈로그 생성하기

gradle은 여러 의존성을 한 곳에서 관리할 수 있도록 Version Catalog라는 기능을 gradle 7.4에서 추가했다. type-safe한 accessor를 생성해 주기 때문에 더 이상 문자열을 매번 작성할 필요도 없다.

// gradle.properties
kotlinVersion=1.8.22
springBootVersion=3.2.0

// settings.gradle.kts
dependencyResolutionManagement {
    val kotlinVersion: String by settings
    val springBootVersion: String by settings

    versionCatalogs {
        create("kt") {
            plugin("jvm", "org.jetbrains.kotlin.jvm").version(kotlinVersion)
        }
        
        create("spring") {
            plugin("kotlin", "org.jetbrains.kotlin.plugin.spring").version(kotlinVersion)
            plugin("boot", "org.springframework.boot").version(springBootVersion)
            plugin("dependency-management", "io.spring.dependency-management").version(springDependencyManagementVersion)
            
            library("boot-starter-jpa", "org.springframework.boot", "spring-boot-starter-data-jpa").version(springBootVersion)
            library("boot-starter-thymeleaf", "org.springframework.boot", "spring-boot-starter-thymeleaf").version(springBootVersion)
            library("boot-starter-web", "org.springframework.boot", "spring-boot-starter-web").version(springBootVersion)
        }
}
  • create({catalogName}) : 버전 카탈로그를 생성한다
  • plugin({alias}, {id}).version({version}) : 플러그인의 alias를 설정하고 버전을 명시한다
  • library({alias}, {group}, {artifact}).version({version}) : 의존성의 alias를 설정하고 버전을 명시한다. 버전 명시가 필요하지 않은 경우 .withoutVersion()을 사용할 수 있다

버전 카탈로그 사용하기

버전 카탈로그를 생성 했다면 이제 사용할 차례이다. 먼저 플러그인은 alias()를 통해 자동 생성된 type-safe accessor를 사용할 수 있다.

plugins {
    alias(kt.plugins.jvm)
    alias(spring.plugins.kotlin)
    alias(spring.plugins.boot)
    alias(spring.plugins.dependencyManagement)
}

주의!
Gradle 버전에 따라 컴파일은 정상적으로 뜨는데 IDE에서 can't be called by implicit receiver 라고 에러가 뜰 수 있다. 리그레션 문제 이므로 Gradle 버전을 업그레이드 하자.
https://github.com/gradle/gradle/issues/22797

의존성에서는 type-safe accessor를 바로 사용하면 된다. 버전 카탈로그에서 작성한 alias의 하이픈(-)은 점(.)으로 변환된다.

dependencies {
    implementation(spring.boot.starter.jpa)
    implementation(spring.boot.starter.thymeleaf)
    implementation(spring.boot.starter.web)
}

주의!
코틀린 DSL에서는 allprojects, subprojects와 같은 블록 내에서 accessor 직접 사용할 수 없고 rootProject를 통해 호출해야한다

allprojects {
    dependencies {
        implementation(rootProject.spring.boot.starter.jpa)
        implementation(rootProject.spring.boot.starter.thymeleaf)
        implementation(rootProject.spring.boot.starter.web)
    }
}

https://github.com/gradle/gradle/issues/16634

이 글에서 소개하지는 않지만, 여러 의존성을 묶어서 하나로 사용할 수 있는 bundle 기능도 제공한다. 더 자세한 설명은 gradle의 공식 문서를 참고하자. https://docs.gradle.org/current/userguide/platforms.html

profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

0개의 댓글