원문 - Migrating build logic from Groovy to Kotlin


목차

  • 마이그레이션을 시작하기 전(Before you start migrating)
  • 그루비 스크립트 준비(Prepare your Groovy scripts)
  • 스크립트 파일명 지정(Script file naming)
  • 플러그인 적용(Applying plugins)
  • 플러그인 구성(Configuring plugins)
  • 구성 회피(Configuration avoidance)
  • 태스크 구성(Configuring tasks)
  • 태스크 생성(Creating tasks)
  • 구성 및 의존성(Configurations and dependencies)
  • 마이그레이션 전략(Migration strategies)
  • 상호 호환성(Interoperability)

그루비에서 코틀린으로 빌드 로직 마이그레이션(Migrating build logic from Groovy to Kotlin)

이 장에서는 그루비 기반 그레이들 빌드 스크립트를 코틀린으로 변환하는 과정을 안내한다.

그루비의 최신 코틀린 DSL은 지원되는 IDE(콘텐츠 지원, 리팩터링, 문서 등)에서 쾌적한 편집 환경을 제공한다.


또한 그레이들 코틀린 DSL 입문서를 읽어 그레이들 코틀린 DSL의 특수성, 제한사항, 사용법을 알아보자.

사용자 메뉴얼의 나머지 부분에는 그루비 DSL과 코틀린 DSL을 모두 보여주는 빌드 스크립트 발췌문이 포함되어 있다. 이곳은 이를 수행하는 방법과 각 DSL에 대해 무엇을 찾을 수 있는 가장 좋은 장소다. 플러그인 사용부터 의존성 해결 동작 커스텀까지 모든 그레이들 기능을 다룬다.


마이그레이션을 시작하기 전(Before you start migrating)

읽어 보기: 마이그레이션하기 전에 다음 내용은 중요한 정보를 이해하는 것이 도움이 된다.

  • 가장 먼저 최신 버전의 그레이들, 적용된 플러그인, IDE를 사용하는 것이 좋다.
  • 코틀린 DSL은 인텔리J IDEA 및 안드로이드 스튜디오에서 완벽하게 지원된다. 이클립스 또는 넷빈즈와 같은 다른 IDE는 아직 그레이들 코틀린 DSL 파일을 편집하는 데 유용한 도구를 제공하지 않지만 코틀린 DSL 기반 빌드 임포트 및 태스크는 평소와 같이 작동한다.
  • 인텔리J IDEA에서는 코틀린 DSL 스크립트용 콘텐츠 지원 및 리팩토링 도구를 얻으려면 그레이들 모델에서 프로젝트를 임포트 한다.
  • 코틀린 DSL이 더 느린 상황이 있다. 예를 들어 클린 체크아웃이나 임시 CI 에이전트에서 처음 사용하는 경우 속도가 더 느린 것으로 알려져 있다. buildSrc 디렉토리의 내용이 변경되어 빌드 스크립트 캐싱이 무효화되는 시나리오에도 동일하게 적용된다. 구성 시간이 느린 빌드는 IDE 응답성에 영향을 미칠 수 있다. 그레이들 성능에 대한 문서를 확인하자.
  • 자바 8 이상으로 그레이들을 실행해야 한다. 자바 7은 지원되지 않는다.
  • 임베디드 코틀린 컴파일러는 x86-64 아키텍처의 리눅스, 맥OS, 윈도우, Cygwin, FreeBSD 및 솔라리스에서 작동하는 것으로 알려져 있다.
  • 코틀린 문법과 기본 언어 기능에 대한 지식은 도움이 된다. 코틀린 레퍼런스 문서코틀린 Koans가 도움이 될 것이다.
  • plugins {} 블록을 사용하여 그레이들 플러그인을 선언하면 편집 환경이 크게 향상되므로 적극 권장된다. 코틀린으로 변환하기 전에 그루비 빌드 스크립트에 이를 채택하는 것을 고려해 보자.
  • 코틀린 DSL은 model {} 요소를 지원하지 않는다. 이는 중단된 그레이들 소프트웨어 모델의 일부다.
  • 인큐베이팅 구성 온디맨드 기능(incubating configuration on demand feature)을 활성화하는 것은 문제발생 시 진단하기 어려워질 수 있으므로 권장되지 않는다.

그레이들 코틀린 DSL 입문서(Gradle Kotlin DSL Primer)에서 자세히 알아보자.

이슈가 발생하거나 버그가 의심되는 경우, gradle/gradle 이슈 트래커를 활용하자.

한꺼번에 마이그레이션할 필요는 없다! 그루비 및 코틀린 기반 빌드 스크립트는 둘 다 두 언어의 다른 스크립트를 적용할 수 있다. 코틀린 DSL 샘플에서 다루지 않은 그레이들 기능에 대한 영감을 얻을 수 있다.

그루비 스크립트 준비(Prepare your Groovy scripts)

코틀린과 그루비는 스크립트 문법 변환이 간단하므로 지루할 수 있다.

  • 그루비 문자열은 작은따옴표 ‘string’ 또는 큰따옴표 “string”으로 묶을 수 있지만 코틀린에서는 큰따옴표 “string”만 사용된다.
  • 그루비에서는 함수를 호출할 때 괄호를 생략할 수 있지만 코틀린에서는 항상 괄호가 필요하다.
  • 그레이들 그루비 DSL에서는 프로퍼티스를 할당할 때 = 할당 연산자(assignment operator)를 생략할 수 있지만 코틀린에서는 항상 할당 연산자가 필요하다.

첫 번째 마이그레이션 단계로, 다음과 같이 그루비 빌드 스크립트를 준비하는 것이 좋다.

  • 큰따옴표(double quotes)를 사용하여 따옴표를 통합하고,
  • 함수 호출(invocation) 및 프로퍼티 할당(assignment)을 명확하게 한다(각각 괄호와 할당 연산자 사용).

전자는 ‘를 검색하고 “로 바꾸면 쉽게 수행할 수 있다. 예를 들어,

build.gradle

group 'com.acme'

dependencies {
    implementation 'com.acme:example:1.0'
}

이 된다.

build.gradle

group "com.acme"

dependencies {
    implementation "com.acme:example:1.0"
}

그루비 스크립트에서 함수 호출과 프로퍼티 할당을 구별하는 것이 쉽지 않을 수 있으므로 후자는 좀 더 복잡하다. 좋은 전략은 먼저 모든 모호한 명령문 프로퍼티 할당을 수행한 다음 실패한 명령문을 함수 호출로 전환하여 빌드를 수정하는 것이다.

예를들어,

build.gradle

group "com.acme"

dependencies {
    implementation "com.acme:example:1.0"
}

becomes:

build.gradle

group = "com.acme" //............................... 1.

dependencies {
    implementation("com.acme:example:1.0") //....... 2.
}
  1. 프로퍼티 할당
  2. 함수 호출

그루비를 유지하면서 코틀린 문법에 가까워 졌기 때문에 스크립트 이름을 바꿔 그레이들 코틀린 DSL 스크립트로 전환하기가 더 쉬워졌다.

스크립트 파일명 지정(Script file naming)


그루비 DSL 스크립트 파일은 .gradle 파일명 확장자를 사용한다. 코틀린 DSL 스크립트 파일은 .gradle.kts 파일 이름 확장자를 사용한다.


코틀린 DSL을 사용하려면 파일명을 build.gradle 대신 build.gradle.kts로 지정하면 된다.

세팅 파일인 settings.gradle의 이름을 settings.gradle.kts로 바꿀 수도 있다.

다중 프로젝트 빌드에서는 그루비 DSL(build.gradle 포함)을 사용하는 일부 모듈과 코틀린 DSL(build.gradle.kts 포함)을 사용하는 다른 모듈을 가질 수 있다.

또한, 더 나은 IDE 지원을 위해 다음 컨벤션을 적용하자.

  • *.settings.gradle.kts 패턴에 따라 설정에 적용되는 스크립트명을 지정,
  • *.init.gradle.kts 패턴에 따라 init 스크립트명을 지정한다.

플러그인 적용(Applying plugins)

그루비 DSL과 마찬가지로 그레이들 플러그인을 적용하는 방법에는 두 가지가 있다.

다음은 선언적 plugins {} 블록을 사용하는 예제다.

build.gradle

plugins {
    id 'java'
    id 'jacoco'
    id 'maven-publish'
    id 'org.springframework.boot' version '2.4.1'
}

build.gradle.kts

plugins {
    java
    jacoco
    `maven-publish`
    id("org.springframework.boot") version "2.4.1"
}

코틀린 DSL은 위에 표시된 java, jacoco 또는 maven-publish 선언과 같이 모든 그레이들 코어 플러그인에 대한 프로퍼티 익스텐션을 제공한다.

그루비 DSL과 동일한 방식으로 서드파티 플러그인을 적용할 수 있다. 큰따옴표와 괄호는 제외한다. 해당 스타일로 코어 플러그인을 적용할 수도 있다. 그러나 타입이 지정된 접근자는 타입이 안전하고 IDE에서 자동 완성되므로 권장된다.

명령형(imperative) apply 구문을 사용할 수도 있지만, 코어가 아닌(non-core) 플러그인은 빌드 스크립트의 클래스패스에 포함되어야 한다.

build.gradle

buildscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath('org.springframework.boot:spring-boot-gradle-plugin:2.4.1')
    }
}

apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'org.springframework.boot'

build.gradle.kts

buildscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.4.1")
    }
}

apply(plugin = "java")
apply(plugin = "jacoco")
apply(plugin = "org.springframework.boot")

apply() 함수보다 plugins {} 블록을 사용하는 것이 좋다.

plugins {} 블록의 선언적 특성을 통해 코틀린 DSL은 적용된 플러그인이 제공하는 익스텐션, 구성 및 기타 기능에 대해 타입 안전 접근자를 제공할 수 있다. 이를 통해 IDE가 플러그인 모델의 세부정보를 쉽게 발견하고 구성하기 쉽다.

자세한 내용은 그레이들 사용자 매뉴얼의 plugins {} 블록 문서를 참고하자.


플러그인 구성(Configuring plugins)

많은 플러그인들에는 이를 구성할 수 있는 익스텐션(extension) 기능이 함께 제공된다. 선언적 plugins {} 블록을 사용하여 해당 플러그인을 적용하면 코틀린 익스텐션 기능을 사용하여 그루비와 동일한 방식으로 익스텐션을 구성할 수 있다. 다음 샘플은 이것이 jacoco 플러그인에서 어떻게 작동하는지 보여준다.

build.gradle

plugins {
    id 'jacoco'
}

jacoco {
    toolVersion = '0.8.1'
}

build.gradle.kts

plugins {
    jacoco
}

jacoco {
    toolVersion = "0.8.1"
}

대조적으로, 명령형 apply() 함수를 사용하여 플러그인을 적용하는 경우 해당 플러그인을 구성하려면 configure<T>() 함수를 사용해야 한다. 다음 샘플은 configure<T>() 함수에서 플러그인의 확장 클래스인 CheckstyleExtension을 명시적으로 선언하여 Checkstyle 플러그인에서 이것이 어떻게 작동하는지 보여준다.

build.gradle

apply plugin: "checkstyle"

checkstyle {
    maxErrors = 10
}

build.gradle.kts

apply(plugin = "checkstyle")

configure<CheckstyleExtension> {
    maxErrors = 10
}

다시 한 번, plugins {} 블록을 통해 플러그인을 선언적으로 적용하는 것이 좋다.


어떤 플러그인이 제공하는 익스텐션 기능을 사용할 수 있는지 알아보기

IDE는 플러그인이 제공하는 컴포넌트를 알고 있으므로 IDE에 제안할 때 해당 요소를 포함하도록 한다. 이는 빌드 스크립트의 최상위 레벨(대부분의 플러그인 익스텐션이 프로젝트(Project) 객체에 추가됨)과 확장의 구성(configuration) 블록 내에서 모두 발생한다.

또한 :kotlinDslAccessorsReport 태스크를 실행하여 적용된 모든 플러그인이 제공하는 익스텐션에 대해 알아볼 수도 있다. 해당 익스텐션 접근에 사용할 수 있는 코틀린 코드를 나타나게 접근자 메서드명과 타입을 제공한다.


구성하려는 플러그인이 메서드 시그니처에서 groovy.lang.Closure를 사용하거나 다른 동적 그루비 의미 체계를 사용하는 경우 코틀린 DSL 빌드 스크립트에서 해당 플러그인을 구성하려면 더 많은 작업이 필요하다. 코틀린 코드에서 크루비 코드를 호출하는 방법이나 그루비 스크립트에서 플러그인 구성을 유지하는 방법에 대한 자세한 내용은 그레이들 코틀린 DSL 문서의 상호 호환성 장을 참고하자.

플러그인은 직접 구성할 수 있는 태스크에도 기여한다. 이 주제는 아래 태스크 구성에서 다룬다.


빌드 스크립트를 선언적으로 유지하기

그레이들 코틀린 DSL의 이점을 최대한 활용하려면 빌드 스크립트를 선언적으로 유지하려고 노력해야 한다. 여기서 기억해야 할 가장 중요한 점은 타입 안전 접근자를 얻으려면 빌드 스크립트 본문보다 먼저 플러그인을 적용해야 한다는 것이다.

그레이들 사용자 매뉴얼에서 그레이들 코틀린 DSL을 사용하여 플러그인을 구성하는 방법을 읽어보는 것이 좋다.

예를 들어, 대부분의 안드로이드 빌드와 같이 빌드가 멀티 프로젝트 빌드인 경우 멀티 프로젝트 빌드에 대한 후속 장도 읽어보자.

마지막으로, 안드로이드 그레이들 플러그인과 같이 올바른 메타데이터로 게시되지 않은 플러그인과 함께 plugins {} 블록을 사용하는 전략이 있다.


구성 회피(Configuration avoidance)

그레이들 4.9에는 빌드 스크립트 및 플러그인에서 태스크를 생성하고 구성하기 위한 새로운 API가 도입됐다. 이 새로운 API가 결국 기존 API를 대체하려는 의도다.

기존 그레이들 태스크 API와 새로운 그레이들 태스크 API의 주요 차이점 중 하나는 그레이들이 Task 인스턴스를 생성하고 구성 코드를 실행하는 데 시간을 소비하는지 여부다. 새로운 API를 사용하면 그레이들은 빌드에서 실행되지 않는 태스크 구성을 지연하거나 완전히 피할 수 있다. 예를 들어 코드를 컴파일할 때 그레이들은 테스트를 실행하는 태스크를 구성할 필요가 없다.

자세한 내용은 구성 시간을 줄이기 위해 그레이들 API 발전(Evolving the Gradle API to reduce configuration time) 블로그 게시물과 사용자 매뉴얼의 태스크 구성 회피 장을 참고하자.

그레이들 코틀린 DSL은 타입 안전 모델 접근자가 새로운 API를 활용하도록 하고 DSL 구성을 제공하여 이를 더 쉽게 사용할 수 있도록 함으로써 구성 회피를 적용한다. 전체 그레이들 API는 계속 사용할 수 있다.

태스크 구성(Configuring tasks)

그루비와 코틀린 DSL은 태스크 구성 문법에서 크게 달라지기 시작한다.

코틀린에서 태스크는 tasks 컨테이너에 네임스페이스가 지정된다.

build.gradle

tasks.jar {
    archiveFileName = 'foo.jar'
}

build.gradle.kts

tasks.jar {
    archiveFileName.set("foo.jar")
}

코틀린에서 task.jar {} 표기법은 구성 회피 API를 활용하고 jar 태스크의 구성을 연기한다.

타입 안전 태스크 접근자(type-safe task accessor) jobs.jar을 사용할 수 없는 경우 위의 플러그인 구성 절을 참고하자. tasks 컨테이너 API를 사용하도록 대체할 수 있다. 다음 코틀린 샘플은 위의 타입 안전 접근자를 사용하는 것과 동일하다.

태스크 컨테이너 API 사용

build.gradle

tasks.named('jar') {
    archiveFileName = 'foo.jar'
}

build.gradle.kts

tasks.named<Jar>("jar") {
    archiveFileName.set("foo.jar")
}

코틀린은 정적으로 타입이 지정된 언어이므로 태스크 타입을 명시적으로 지정해야 한다. 그렇지 않으면 유추된 타입이 Jar가 아닌 Task이고 archiveName 프로퍼티가 Jar 태스크 타입에 특정하기 때문에 스크립트가 컴파일되지 않는다.

구성 회피가 마이그레이션을 방해하고 그루비와 같은 태스크를 즉시 구성하려는 경우 tasks 컨테이너에서 즉시 구성 API를 사용할 수 있다.

즉시 구성을 위해 tasks 컨테이너 API 사용

build.gradle

tasks.getByName('jar') {
    archiveFileName = 'foo.jar'
}

build.gradle.kts

tasks.getByName<Jar>("jar") {
    archiveFileName.set("foo.jar")
}

그레이들 코틀린 DSL의 컨테이너 태스크는 여기에 자세히 설명되어 있다.


태스크 타입 알아보기

태스크에 어떤 타입이 있는지 모르는 경우, 내장된 help 태스크를 통해 해당 정보를 찾을 수 있다. 다음과 같이 --task 옵션을 사용하여 관심 있는 태스크명을 전달하기만 하면 된다.

❯ ./gradlew help --task jar
...
Type
     Jar (org.gradle.api.tasks.bundling.Jar)

스프링 부트 프로젝트의 bootJarbootRun 태스크를 구성하는 예제를 실행하여 이 모든 것을 하나로 묶어 보자.

타입 안전 접근자를 사용하여 스프링 부트 구성

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.4.5'
}

tasks.bootJar {
    archiveFileName = 'app.jar'
    mainClassName = 'com.example.demo.Demo'
}

tasks.bootRun {
    mainClass = 'com.example.demo.Demo'
    args '--spring.profiles.active=demo'
}

build.gradle.kts

plugins {
    java
    id("org.springframework.boot") version "2.4.5"
}

tasks.bootJar {
    archiveFileName.set("app.jar")
    mainClassName = "com.example.demo.Demo"
}

tasks.bootRun {
    mainClass.set("com.example.demo.Demo")
    args("--spring.profiles.active=demo")
}

주요 차이점은 코틀린 DSL 접근자를 사용할 때 태스크 구성이 자동으로 지연(lazy)된다는 것이다.

이제 예제를 위해 빌드 로직 구조에 따라 사용할 수 없는 타입 안전 접근자 대신 API를 사용하여 적용된 동일한 구성을 살펴보자. 자세한 내용은 그레이들 사용자 매뉴얼의 해당 문서를 참고하자. 먼저 help 태스크을 통해 bootJarbootRun 태스크의 타입을 결정한다.

❯ ./gradlew help --task bootJar
...
Type
     BootJar (org.springframework.boot.gradle.tasks.bundling.BootJar)
❯ ./gradlew help --task bootRun
...
Type
     BootRun (org.springframework.boot.gradle.tasks.run.BootRun)

이제 두 태스크의 타입을 알았으므로 관련 타입(BootJarBootRun)을 가져오고 필요에 따라 태스크를 구성할 수 있다. IDE는 필요한 임포트(import) 작업을 도와줄 수 있으므로 전체 패키지 없이 간단한 이름만 필요하다. 임포트가 포함된 결과 빌드 스크립트는 다음과 같다.

API를 사용하여 스프링 부트 구성

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.4.5'
}

tasks.named('bootJar') {
    archiveFileName = 'app.jar'
    mainClassName = 'com.example.demo.Demo'
}

tasks.named('bootRun') {
    mainClass = 'com.example.demo.Demo'
    args '--spring.profiles.active=demo'
}

build.gradle.kts

import org.springframework.boot.gradle.tasks.bundling.BootJar
import org.springframework.boot.gradle.tasks.run.BootRun

plugins {
    java
    id("org.springframework.boot") version "2.4.5"
}

tasks.named<BootJar>("bootJar") {
    archiveFileName.set("app.jar")
    mainClassName = "com.example.demo.Demo"
}

tasks.named<BootRun>("bootRun") {
    mainClass.set("com.example.demo.Demo")
    args("--spring.profiles.active=demo")
}

태스크 생성(Creating tasks)

태스크 생성은 task(…​)라는 스크립트 탑 레벨(top-level) 함수를 사용하여 수행할 수 있다.

탑 레벨 tasks(…​) 기능 사용하기

build.gradle

task greeting {
    doLast { println 'Hello, World!' }
}

build.gradle.kts

task("greeting") {
    doLast { println("Hello, World!") }
}

위의 내용은 그루비 및 코틀린 DSL을 모두 사용하여 생성된 태스크를 즉시 구성한다.

태스크 등록 또는 생성은 다음과 같이 각각 register(...​)create(...​) 함수를 사용하여 tasks 컨테이너에서 수행할 수도 있다.

구성 회피 API 및 DSL 사용

build.gradle

tasks.create('greeting') {
    doLast { println('Hello, World!') }
}

build.gradle.kts

tasks.create("greeting") {
    doLast { println("Hello, World!") }
}

위의 샘플은 타입이 지정되지 않은 임시(ad-hoc) 태스크을 생성하지만 특정 타입의 태스크를 생성하려는 경우가 더 일반적이다. 동일한 register()create() 메서드를 사용하여 이 작업을 수행할 수도 있습니다. 다음은 Zip 타입의 새 태스크를 생성하는 예제다.

구성 회피 API 및 DSL 사용

build.gradle

tasks.register('docZip', Zip) {
    archiveFileName = 'doc.zip'
    from 'doc'
}

build.gradle.kts

tasks.register<Zip>("docZip") {
    archiveFileName.set("doc.zip")
    from("doc")
}

즉시적용 API 및 DSL 사용

build.gradle

tasks.create(name: 'docZip', type: Zip) {
    archiveFileName = 'doc.zip'
    from 'doc'
}

build.gradle.kts

tasks.create<Zip>("docZip") {
    archiveFileName.set("doc.zip")
    from("doc")
}

구성 및 의존성(Configurations and dependencies)

기존 구성에서 의존성을 선언하는 것은 다음 예제에서 볼 수 있듯이 그루비 빌드 스크립트에서 수행되는 방식과 유사하다.

build.gradle

plugins {
    id 'java-library'
}
dependencies {
    implementation 'com.example:lib:1.1'
    runtimeOnly 'com.example:runtime:1.0'
    testImplementation('com.example:test-support:1.3') {
        exclude(module: 'junit')
    }
    testRuntimeOnly 'com.example:test-junit-jupiter-runtime:1.3'
}

build.gradle.kts

plugins {
    `java-library`
}
dependencies {
    implementation("com.example:lib:1.1")
    runtimeOnly("com.example:runtime:1.0")
    testImplementation("com.example:test-support:1.3") {
        exclude(module = "junit")
    }
    testRuntimeOnly("com.example:test-junit-jupiter-runtime:1.3")
}

적용된 플러그인이 제공하는 각 구성은 configurations 컨테이너의 멤버로도 사용할 수 있으므로 다른 구성과 마찬가지로 참조할 수 있다.


어떤 구성을 사용할 수 있는지 알아보기

어떤 구성을 사용할 수 있는지 알아내는 가장 쉬운 방법은 configurations 컨테이너 내에서 IDE에 제안을 요청하는 것이다.

적용된 플러그인이 제공한 구성에 접근하기 위한 코틀린 코드를 나타내고 해당 접근자 모두의 이름을 제공하는 :kotlinDslAccessorsReport 태스크를 사용할 수도 있다.


플러그인을 적용하기 위해 plugins {} 블록을 사용하지 않으면 해당 플러그인이 제공하는 의존성 구성을 일반적인 방식으로 구성할 수 없다. 대신 구성 이름에 문자열 리터럴을 사용해야 한다. 즉, IDE 지원을 받을 수 없다.

build.gradle

apply plugin: 'java-library'
dependencies {
    implementation 'com.example:lib:1.1'
    runtimeOnly 'com.example:runtime:1.0'
    testImplementation('com.example:test-support:1.3') {
        exclude(module: 'junit')
    }
    testRuntimeOnly 'com.example:test-junit-jupiter-runtime:1.3'
}

build.gradle.kts

apply(plugin = "java-library")
dependencies {
    "implementation"("com.example:lib:1.1")
    "runtimeOnly"("com.example:runtime:1.0")
    "testImplementation"("com.example:test-support:1.3") {
        exclude(module = "junit")
    }
    "testRuntimeOnly"("com.example:test-junit-jupiter-runtime:1.3")
}

This is just one more reason to use the plugins {} block whenever you can!

커스텀 구성 및 의존성(Custom configurations and dependencies)

때로는 구성을 만들고 의존성을 연결해야 하는 경우도 있다. 다음 예제에서는 두 가지 새로운 구성을 선언한다.

  • PostgreSQL 의존성을 추가하는 db
  • testImplementation 구성을 확장하도록 구성하고, 다른 의존성을 추가하는 integTestImplementation

build.gradle

configurations {
    db
    integTestImplementation {
        extendsFrom testImplementation
    }
}

dependencies {
    db 'org.postgresql:postgresql'
    integTestImplementation 'com.example:integ-test-support:1.3'
}

build.gradle.kts

val db by configurations.creating
val integTestImplementation by configurations.creating {
    extendsFrom(configurations["testImplementation"])
}

dependencies {
    db("org.postgresql:postgresql")
    integTestImplementation("com.example:integ-test-support:1.3")
}

위 예시의 dependencies {} 블록 내에서는 db(…​)integTestImplementation(…​) 표기법만 사용할 수 있다. 왜냐하면 두 구성 모두 creating() 메서드를 통해 사전에 위임된 프로퍼티스로 선언되었기 때문이다. 구성이 다른 곳에서 정의된 경우 먼저 configurations을 통해 위임 프로퍼티스를 생성하거나(configuration.creating()이 아닌) dependencies {} 블록 내에서 문자열 리터럴을 사용하여 구성을 참조할 수 있다. 다음 예제에서는 두 가지 접근 방식을 모두 보여준다.

build.gradle.kts

// 기존 'testRuntimeOnly' 구성을 가져온다.
val testRuntimeOnly by configurations

dependencies {
    testRuntimeOnly("com.example:test-junit-jupiter-runtime:1.3")
    "db"("org.postgresql:postgresql")
    "integTestImplementation"("com.example:integ-test-support:1.3")
}

마이그레이션 전략(Migration strategies)

위에서 본 것처럼 코틀린 DSL을 사용하는 스크립트와 그루비 DSL을 사용하는 스크립트는 모두 동일한 빌드에 참여할 수 있다. 또한 buildSrc 디렉토리의 Gradle 플러그인, 포함된 빌드 또는 외부 위치의 태스크들은 모든 JVM 언어를 사용하여 구현할 수 있다. 이를 통해 팀을 방해하지 않고 빌드를 점진적으로 하나씩 마이그레이션할 수 있다.

마이그레이션에 대한 두 가지 접근 방식.:

  • 구조를 유지하면서 빌드의 기존 문법을 코틀린으로 조금씩 마이그레이션하는 것을 기계적 마이그레이션이라고 한다.
  • 그레이들 모범 사례에 맞춰 빌드 로직을 재구성하고 그러한 노력의 일환으로 코틀린 DSL로 전환하기.

두 가지 접근 방식 모두 실행 가능하다. 간단한 빌드에는 기계적 마이그레이션으로 충분하다. 복잡하고 매우 동적인 빌드에는 어쨌든 약간의 재구성이 필요할 수 있으므로 이러한 경우 그레이들 모범 사례를 따르도록 빌드 로직을 다시 구현하는 것이 합리적이다.

그레이들 모범 사례를 적용하면 빌드를 더 쉽게 사용할 수 있고, 더 빠르게 만들 수 있으므로 결국에는 모든 프로젝트를 그런 방식으로 개선하면서 마이그레이션하는 것이 좋다.

또한 빌드 로직의 더 많은 부분이 그루비의 동적 측면에 의존할수록 코틀린 DSL에서 사용하기가 더 어려워진다는 점을 고려하자. 동적 그루비 빌드 로직이 어디에 있는지에 관계없이 그레이들 코틀린 DSL 문서의 상호 호환성 장에서 정적 코틀린의 동적 경계를 넘나드는 방법에 대한 방법을 찾을 수 있다.

코틀린 DSL의 정적 컨텍스트 내에서 더 쉽게 작업할 수 있게 해주는 두 가지 주요 모범 사례가 있다.

  • plugins {} 블록 사용
  • 빌드의 buildSrc 디렉토리에 로컬 빌드 로직 넣기

plugins {} 블록은 코틀린 DSL을 최대한 활용하기 위해 빌드 스크립트를 선언적으로 유지하는 것이다.

buildSrc 프로젝트를 활용하는 것은 빌드 로직을 쉽게 테스트할 수 있고 우수한 IDE 지원을 제공하는 공유 로컬 플러그인 및 컨벤션으로 구성하는 것이다.

코틀린 DSL 빌드 구조 샘플(Kotlin DSL build structure samples)

빌드 구조에 따라 다음 사용자 매뉴얼 장에 관심이 있을 수 있다.

상호 호환성(Interoperability)

빌드 논리에서 언어를 혼합하는 경우 언어 경계를 넘어야 할 수도 있다. 극단적인 예는 자바, 그루비, 코틀린으로 구현된 태스크와 플러그인을 사용하면서 동시에 코틀린 DSL과 그루비 DSL 빌드 스크립트를 또한 사용할 수 있다.

코틀린 참조 문서 인용:

코틀린은 자바 상호 호환성을 염두에 두고 설계됐따. 기존 자바 코드를 코틀린에서 자연스럽게 호출할 수 있고, 자바에서도 코틀린 코드를 원활하게 사용할 수 있다.

코틀린에서 자바를 호출하는 것자바에서 코틀린을 호출하는 것은 모두 코틀린 레퍼런스 문서에 잘 설명되어 있다.

그루비 코드와의 상호 호환성에도 대부분 동일하게 적용된다. 또한 코틀린 DSL은 그루비 의미 체계를 선택할 수 있는 여러 가지 방법을 제공한다.


그레이들 코틀린 DSL 및 상호 호환성

그레이들 코틀린 DSL 입문서의 상호 호환성 절에서 자세한 문서를 찾아보자.