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