December 7, 2023

Gradle λ©€ν‹° ν”„λ‘œμ νŠΈμ—μ„œ μ»¨λ²€μ…˜ ν”ŒλŸ¬κ·ΈμΈμœΌλ‘œ μ˜μ‘΄μ„± 관리


Multi-project Structure

λΉ λ₯Έ 이해λ₯Ό μœ„ν•΄ μ•„λž˜μ²˜λŸΌ ꡬ성해 λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

예제 μ†ŒμŠ€λŠ” Github에 μ˜¬λ €λ‘μ—ˆμŠ΅λ‹ˆλ‹€.

β”œβ”€β”€ gradle
β”‚   β”œβ”€β”€ wrapper
β”‚   β”‚   β”œβ”€β”€ gradle-wrapper.jar
β”‚   β”‚   └── gradle-wrapper.properties
β”‚   └── libs.versions.toml   // 1. 버전 μΉ΄νƒˆλ‘œκ·Έ
β”‚
β”œβ”€β”€ build-logic              // 2. λΉŒλ“œ ν”ŒλŸ¬κ·ΈμΈμ„ μœ„ν•œ ν”„λ‘œμ νŠΈ
β”‚   β”œβ”€β”€ src
β”‚   β”‚   └── main
β”‚   β”‚       └── kotlin
β”‚   β”‚           β”œβ”€β”€ io.github.ijmo.kotlin-application-conventions.gradle.kts
β”‚   β”‚           β”œβ”€β”€ io.github.ijmo.kotlin-common-conventions.gradle.kts
β”‚   β”‚           └── io.github.ijmo.kotlin-library-conventions.gradle.kts
β”‚   β”œβ”€β”€ build.gradle.kts
β”‚   └── settings.gradle.kts
β”‚
β”œβ”€β”€ app                      // 3. μ„œλΈŒ ν”„λ‘œμ νŠΈ
β”‚   └── build.gradle.kts
β”œβ”€β”€ library                  // 3. μ„œλΈŒ ν”„λ‘œμ νŠΈ
β”‚   └── build.gradle.kts
β”‚
β”œβ”€β”€ gradlew
β”œβ”€β”€ gradlew.bat
└── settings.gradle.kts      // 4. λΉŒλ“œ ν”ŒλŸ¬κ·ΈμΈμ„ include


1. 버전 μΉ΄νƒˆλ‘œκ·Έ

β”œβ”€β”€ gradle
β”‚   β”œβ”€β”€ wrapper
β”‚   β”‚   └── ...
β”‚   └── libs.versions.toml
...
  • 버전을 κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ libs.versions.toml 을 μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.

  • 의쑴 라이브러리 λͺ©λ‘λ„ μ—¬κΈ°μ„œ 관리λ₯Ό ν•  수 μžˆμœΌλ‚˜, μ €λŠ” λ²„μ „λ§Œ λ„£μ–΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

gradle/libs.versions.toml
[versions]
kotlin = "1.9.20"
springBoot = "3.2.0"
springCloud = "2023.0.0"


2. λΉŒλ“œ ν”ŒλŸ¬κ·ΈμΈμ„ μœ„ν•œ ν”„λ‘œμ νŠΈ

β”œβ”€β”€ build-logic
β”‚   β”œβ”€β”€ src
β”‚   β”‚   └── main
β”‚   β”‚       └── kotlin
β”‚   β”‚           β”œβ”€β”€ io.github.ijmo.kotlin-application-conventions.gradle.kts
β”‚   β”‚           β”œβ”€β”€ io.github.ijmo.kotlin-common-conventions.gradle.kts
β”‚   β”‚           └── io.github.ijmo.kotlin-library-conventions.gradle.kts
β”‚   β”œβ”€β”€ build.gradle.kts
β”‚   └── settings.gradle.kts
...


  • libs.versions.toml 을 μ½μ–΄μ˜€λ„λ‘ ν•©λ‹ˆλ‹€. settings 파일 κΈ°μ€€μœΌλ‘œ μƒλŒ€κ²½λ‘œλ₯Ό μž…λ ₯ν–ˆμŠ΅λ‹ˆλ‹€.

build-logic/settings.gradle.kts
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            from(files("../gradle/libs.versions.toml"))
        }
    }
}

rootProject.name = "my-example-app-build-logic"


  • λ§Œλ“€λ €λŠ” ν”ŒλŸ¬κ·ΈμΈμ—μ„œ μ™ΈλΆ€ ν”ŒλŸ¬κ·ΈμΈμ„ μ‚¬μš©ν•˜κ³  μ‹Άλ‹€λ©΄ λΉŒλ“œ μŠ€ν¬λ¦½νŠΈμ—μ„œ μ˜μ‘΄μ„±μ„ μΆ”κ°€ν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€. Spring Boot을 μ‚¬μš©ν•˜κΈ° μœ„ν•΄ gradlePluginPortal 에 μžˆλŠ” spring-boot-gradle-plugin 을 μΆ”κ°€ν•΄ μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

  • libs.versions.toml μ—μ„œ μ§€μ •ν•œ 버전을 κ°€μ Έμ™”μŠ΅λ‹ˆλ‹€.

build-logic/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    gradlePluginPortal()
}

val versionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${versionCatalog.findVersion("kotlin").get()}")
    implementation("org.springframework.boot:spring-boot-gradle-plugin:${versionCatalog.findVersion("springBoot").get()}")
}


  • gradle init 을 톡해 ν”„λ‘œμ νŠΈλ₯Ό λ§Œλ“€λ©΄ 3κ°€μ§€ λͺ¨λΈμ˜ ν”ŒλŸ¬κ·ΈμΈμ„ μŠ€μΊν΄λ”© ν•΄μ€λ‹ˆλ‹€. 파일 μ΄λ¦„μ—μ„œλ„ μ•Œ 수 μžˆλ“―μ΄ ν”„λ‘œμ νŠΈ κ³΅ν†΅μœΌλ‘œ μ“°μ΄λŠ” 'common', μ•±μ—μ„œ μ“°κΈ° μœ„ν•œ 'application', λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ μ“°κΈ° μœ„ν•œ 'library' 둜 λ‚˜λˆ„μ–΄μ§„ κ΅¬μ‘°μž…λ‹ˆλ‹€.

  • μ„œλΈŒ ν”„λ‘œμ νŠΈμ˜ μœ ν˜•μ΄ ν•œ μ’…λ₯˜(μ•± λ˜λŠ” 라이브러리)μ΄κ±°λ‚˜ λͺ¨λ“  ν”„λ‘œμ νŠΈκ°€ 같은 μ˜μ‘΄μ„±μ„ κ°€μ§„λ‹€λ©΄ ꡳ이 μ—¬λŸ¬ 파일둜 λ‚˜λˆŒ ν•„μš”μ—†μ΄ ν•œ νŒŒμΌμ— λ‹€ μ§‘μ–΄λ„£μœΌλ©΄ λ˜κ² μŠ΅λ‹ˆλ‹€.

  • νŒŒμΌμ΄λ¦„μ€ 제 κΈ°μ€€μœΌλ‘œ μ§€μ—ˆμœΌλ‹ˆ μ μ ˆν•˜κ²Œ λ°”κΏ”μ„œ μ“°μ‹œλ©΄ λ˜κ² μŠ΅λ‹ˆλ‹€.

build-logic/src/main/kotlin/io.github.ijmo.kotlin-common-conventions.gradle.kts
plugins {
    id("org.jetbrains.kotlin.jvm")
}

repositories {
    mavenCentral()
}

dependencies {
    constraints {
        implementation("org.apache.commons:commons-text:1.10.0")
    }
}

testing {
    suites {
        val test by getting(JvmTestSuite::class) {
            useJUnitJupiter("5.9.3")
        }
    }
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}


  • λΉŒλ“œ μŠ€ν¬λ¦½νŠΈμ—μ„œ μ˜μ‘΄μ„±μ„ μΆ”κ°€ν–ˆμ—ˆλ˜ Spring Boot ν”ŒλŸ¬κ·ΈμΈμ„ plugins {} μ•ˆμ—μ„œ apply ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

  • Spring BOM(Bill of Material)을 μ μš©ν•˜κΈ° μœ„ν•΄ implementation(platform()) 을 μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€.

  • μ—¬κΈ°μ„œ dependencies {} 에 μΆ”κ°€ν•œ λΌμ΄λΈŒλŸ¬λ¦¬λŠ” resolve λ˜μ–΄ classpath에 μΆ”κ°€λ©λ‹ˆλ‹€. μ„œλΈŒ ν”„λ‘œμ νŠΈμ—μ„œ μ‚¬μš©ν•˜λ €λ©΄ ν•΄λ‹Ή ν”„λ‘œμ νŠΈμ˜ λΉŒλ“œ μŠ€ν¬λ¦½νŠΈμ—μ„œ dependencies {} 에 μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

build-logic/src/main/kotlin/io.github.ijmo.kotlin-application-conventions.gradle.kts
plugins {
    id("io.github.ijmo.kotlin-common-conventions")
    id("org.springframework.boot")
    application
}

val versionCatalog: VersionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
val springBootVersion = versionCatalog.findVersion("springBoot").get()
val springCloudVersion = versionCatalog.findVersion("springCloud").get()

dependencies {
    implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion"))
    implementation(platform("org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"))

    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions:1.2.2")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.7.3")

    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-webflux")

    testImplementation("io.projectreactor:reactor-test:3.6.0")
}


  • 라이브러리 ν”„λ‘œμ νŠΈλ₯Ό μœ„ν•œ ν”ŒλŸ¬κ·ΈμΈ μž…λ‹ˆλ‹€.

  • ν•„μš”ν•œ 라이브러리λ₯Ό dependencies {} 에 μΆ”κ°€ν•΄μ£Όμ„Έμš”.

build-logic/src/main/kotlin/io.github.ijmo.kotlin-library-conventions.gradle.kts
plugins {
    id("io.github.ijmo.kotlin-common-conventions")
    `java-library`
}


3. λΉŒλ“œ ν”ŒλŸ¬κ·ΈμΈμ„ include

β”œβ”€β”€ settings.gradle.kts
...
  • λ§Œλ“  ν”ŒλŸ¬κ·ΈμΈμ„ pluginManagement {} 을 톡해 ν¬ν•¨μ‹œμΌœ μ€λ‹ˆλ‹€.

  • μ„œλΈŒ ν”„λ‘œμ νŠΈλ„ include() ν•΄μ€λ‹ˆλ‹€.

settings.gradle.kts
pluginManagement {
    includeBuild("build-logic")
}

rootProject.name = "my-example-app"

include("app", "library")


4. μ„œλΈŒ ν”„λ‘œμ νŠΈ

β”œβ”€β”€ app
β”‚   └── build.gradle.kts
β”œβ”€β”€ library
β”‚   └── build.gradle.kts
...
  • classpath에 μΆ”κ°€λœ νŒ¨ν‚€μ§€(라이브러리) 쀑에 각 ν”„λ‘œμ νŠΈμ— μ‚¬μš©ν•  νŒ¨ν‚€μ§€λ₯Ό λΉŒλ“œ μŠ€ν¬λ¦½νŠΈμ— μΆ”κ°€ν•©λ‹ˆλ‹€.

app/build.gradle.kts
plugins {
    id("io.github.ijmo.kotlin-application-conventions")
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    testImplementation("io.projectreactor:reactor-test")

    implementation(project(":library"))
}


Tags: gradle plugin  multi-project  convention plugin  dependency