Post

Gradle Project Use Version Category

샘플 Repo : https://github.com/lynheo/gradle-kotlin-spring-version-category-example

대형 Java / Kotlin 프로젝트를 하다 보면 귀찮은 것 중 하나가 각 프로젝트가 의존 하고 있는 라이브러리의 버전 관리이다.

의외로(?) Gradle 에는 여러개의 프로젝트 간에 Library 버전을 공유하는 공식적인 방법을 꽤 오랫동안 제공 하지 않았었다. 하지만 지금은 그게 해결된 Version Category 가 지원되는 정식 버전이 출시 되었기에, 적용과 소개를 해 본다.

일단 최신의 IntelliJ 에서 SpringBoot 프로젝트를 하나 생성했다. JDK 17, Kotlin, Kotlin Gradle 설정이다. 이대로 생성 하고 gradle/wrapper/gradle-wrapper.properties 를 열어보면 다음과 같다

1
2
3
4
5
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

보다 시피 Gradle 7.6.1 로 세팅되어 있다. 해당 기능을 사용하려면 더 상위의 Gradle 이 필요하므로, 2023년 5월 5일 현재 기준 최신의 8.1.1 로 업데이트를 한다

다음 명령어를 실행 하여 업데이트를 한다

1
./gradlew wrapper --gradle-version 8.1.1

아래는 수정 전의 build.gradle.kts 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "3.1.0-SNAPSHOT"
    id("io.spring.dependency-management") version "1.1.0"
    kotlin("jvm") version "1.8.21"
    kotlin("plugin.spring") version "1.8.21"
}

group = "lyn"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
    maven { url = uri("https://repo.spring.io/milestone") }
    maven { url = uri("https://repo.spring.io/snapshot") }
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.jetbrains.kotlin:kotlin-reflect")

    implementation("com.fasterxml.jackson.core:jackson-core:2.15.0")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.0")
    implementation("com.squareup.okhttp3:okhttp:3.2.0")
    
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

이후 gradle 디렉토리 아래에 libs.versions.toml 파일 을만든다. toml 형식은 ini 를 확장 한 형식으로, Rust 의 패키지 관리자인 Cargo 에서도 사용 하는 형식이다. 맨 앞의 “libs” 는 이름이 달라도 상관 없는데, 저 “libs” 라는 이름으로 객체가 빌드 스크립트에 인젝션 된다. 이름은 원하는대로 바꾸자.

*.version.toml은 4개의 섹션을 가지는데 각각 versions 는 공유해서 사용할 버전을 명시하고, libraries는 사용할 라이브러리들, bundles 은 라이브러리의 묶음을, plugins 는 gradle 플러그인을 나열할 수 있다.

libs.versions.toml 의 예제는 아래와 같다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[versions]
kotlin = "1.8.21"
jackson = "2.15.0"

[libraries]
jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jackson" }
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version = "3.2.0" }

[bundles]
jackson-all = ["jackson-core", "jackson-module-kotlin"]

[plugins]
spring-boot = { id = "org.springframework.boot", version = "3.1.0-SNAPSHOT" }
spring-dependency-management = { id = "io.spring.dependency-management", version = "1.1.0" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }

version.ref 를 쓰면, versions에 선언한 버전을 공유하여 사용할 수 있다 그리고 각 섹션의 Key가 되는 값에 - 등의 식별자로 사용할 수 없는 문자가 들어가 있을 경우, . 으로 접근하는 멤버변수로 바뀐다. 즉 spring-dependency-management -> spring.dependency.management 처럼 치환된다

변경된 gradle 파일을 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.spring)
    alias(libs.plugins.spring.boot)
    alias(libs.plugins.spring.dependency.management)
}

group = "lyn"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
    maven { url = uri("https://repo.spring.io/milestone") }
    maven { url = uri("https://repo.spring.io/snapshot") }
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.jetbrains.kotlin:kotlin-reflect")

    implementation(libs.bundles.jackson.all)
    implementation(libs.okhttp3)

    developmentOnly("org.springframework.boot:spring-boot-devtools")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

위에서 말한대로 libs 는 script 에 자동으로 injection 되므로 별도의 선언 없이 사용할 수 있다. plugin 은 alias 함수로 가져올 수 있다.

dependencies 에서는 bundles 로 한번에 library 여러개를 가져 올 수도 있고, 하나씩 가져올 수도 있다. 하나씩 가져올때는 libs 뒤에 바로 library 이름이 들어간다(version, bundles, plugins section 과 달리 libraries 는 별도의 depth 가 없이 바로 참조한다는것에 주의)

난 개인적으로 실제 현업에서의 프로젝트를 이것으로 정리한 후, 굉장히 관리가 편해지고 깔끔해졋다고 여기고 있다. 특히 라이브러리 여러개를 한번의 선언으로 가져올 수 있게 해주는 bundles 는 꽤 편리했다.

기대하던 신 기능이 들어와서 매우 만족스럽다.

PS. Android 에서는 아직 Expreimental 이긴 하지만 프로젝트 생성 시 Version Catalog 를 적용 해 주는 옵션도 생겼다. Picture by Android Developer Blog Image

This post is licensed under CC BY 4.0 by the author.