예제파일 : https://github.com/seyoungcho2/GradleKotlinDSL
Gradle with Kotlin DSL
Groovy로 빌드 파일을 작성하는 것은 불편하다.
- 다른 곳에서 선언된 변수에 대해 자동완성이 지원되지 않고 문서 찾기가 어렵다.
- 실행시점 전까지 오류가 검출되지 않는다.
- IDE에서 제공하는 리펙터링 기능을 사용할 수 없다. (Intellij 기준 Shift+F6 을 눌러서 리펙토링 불가)
- 코드 작성이 제약이 약해 빌드 스크립트가 자유 분방해진다. Groovy는 같은 코드를 여러 방식으로 쓰는 것을 허용한다. 대표적 예로 문자열을 쓸 때 ' 를 쓰는 것과 "를 쓰는 것이 모두 허용되는 점이다.
왜 Kotlin DSL로 이전해야 하는가?
- 코드 자동완성과 참조
- 오류코드 강조
- 리펙터링 : 변수의 리펙터링이 가능해진다
- 제약 : 코드 작성에 제약이 강하다. 개인적으로 여러 명이 공유하는 스크립트에서 이정도 수준의 제약이 있어야 한다고 본다.
- 코드의 직관성 : 코틀린이 익숙한 사용자는 러닝커브 없이 자신만의 메서드를 자유자제로 만들 수 있다. 심지어 알아보기도 쉽다!
Gradle Build Script를 만들 때 일반적인 Gradle 파일처럼 만들어도 되지만(참조 : Gradle을 Groovy에서 Kotlin DSL로 Migration 하기)
위 방법대로 만들면 Gradle파일을 Kotlin DSL로 만드는 것의 장점을 제대로 살리지 못하는 것이라 생각한다. <그림6>과 같이 Kotlin DSL의 장점을 살려 Gradle Script를 만드는 방법을 아래에서 살펴보자.
Kotlin DSL로 Gradle 빌드 파일 만들기
buildSrc 모듈 : Gradle 빌드를 도와주는 Kotlin 모듈
Kotlin 자동완성 기능을 사용하고 의존성 관리를 하기 위해 gradle 파일에서 참조할 수 있는 모듈인 buildSrc 모듈을 만들어야 한다. buildSrc 모듈에는 Kotlin 파일을 작성할 수 있고, 작성된 Kotlin 파일은 .gradle파일에서 참조될 수 있다.
Gradle 공식 문서에서는 buildSrc에 대해 다음과 같이 나와있다.
요약
- Gradle이 수행되면 buildSrc Path가 있는지 확인하고, buildSrc가 있다면 자동으로 컴파일되어 build script의 classpath에 포함된다.
- 복잡한 빌드 로직을 캡슐화하는데 사용된다.
- Kotlin Build Script 뿐만 아니라 Groovy Build Script에서도 활용 가능하다.
buildSrc Directory 추가 및 build.gradle.kt 파일 생성
먼저 Project 최상위 폴더에 buildSrc Directroy를 추가한다.
이후 해당 디렉터리에 mavenCentral에서 kotlin-dsl 플러그인을 받아올 수 있도록 다음과 같은 build.gradle.kts파일을 만든후 아래와 같이 작성한다.
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
sync를 누르면 그림3의 .gradle과 build 디렉터리가 gen된다.
*jcenter()로 가이드 하는 문서가 많은데 jcenter()은 deprecated되었다. mavenCentral()로 변경하자.
buildSrc 사용하기
buildSrc에는 라이브러리, 버전, 플러그인에 대해서는 물론이고 Gradle 파일 내에서 쓰는 메서드까지 작성할 수 있다.
아래 <그림7>에서 볼 수 있듯이 일반 프로젝트와 같이 패키지 구조를 사용할 수 있고, 코틀린의 장점을 활용할 수 있다(Object를 활용한 싱글톤 패턴 구현 등).
이제 <그림6>과 같이 어떻게 만드는지 한 번 살펴보자. 아래와 같이 데이터 클래스와 메서드를 buildSrc 모듈에 작성해보자.
data class DependencyInfo(val name: String, val version: String, val method: Method) {
private fun getFullName() = "$name:$version"
private fun getMethodName() = method.methodString
fun implement(dependencyHandler: DependencyHandler) {
dependencyHandler.add(this.getMethodName(), this.getFullName())
}
}
fun Array<DependencyInfo>.implementOn(dependencyHandler: DependencyHandler) =
this.forEach { dependency -> dependency.implement(dependencyHandler) }
private val moshiDependencies = arrayOf(
DependencyInfo("com.squareup.moshi:moshi-kotlin", Versions.moshiVersion, Method.IMPLEMENTATION),
DependencyInfo("com.squareup.moshi:moshi-kotlin-codegen", Versions.moshiVersion, Method.KAPT)
)
위 코드를 작성한 후 build.gradle.kts 파일 내부에서 <그림8>과 같이 작성하면 moshiDependencies Array에서 선언된 모든 라이브러리가 implement된다.
kts : Kotlin Script의 약자로 코틀린을 스크립트 언어처럼 쓸 때 쓰는 확장자이다. 빌드파일은 Kotlin Script로 작성된다.
재사용성
무엇보다 좋은 점은 buildSrc 모듈의 재사용이 가능하다는 점이다. buildSrc 모듈은 외부와 의존성을 가지지 않는 모듈이기 때문에 다른 프로젝트에서 한 프로젝트에서 작성된 buildSrc 모듈을 재사용하려면 복사 붙여넣기만 하면 별도 설정 없이 재사용이 가능하다. 이를 통해 라이브러리들을 묶어서 손쉽게 관리할 수 있게 된다.
한계점
위의 많은 좋은 점들에도 불구하고 Gradle 빌드 스크립트에 Kotlin DSL을 도입하는 것은 아래의 크리티컬한 단점에 의해 상쇄된다.
빌드 속도가 Groovy를 사용할 때 보다 느리다.
참조: 안드로이드 공식-Groovy에서 Kotlin으로 빌드 구성 이전
빌드 속도가 Groovy를 사용할 때 보다 느리다는 점은 도입을 고민해보아야 할 문제이다. 많이 개선되었다고는 하지만, 간단한 테스트 결과 1.5배에서 2배까지 차이가 나는 것으로 보아 빌드가 오래 걸리는 프로젝트에는 도입이 어려우며, 이 속도가 개선될 때까지는 도입을 재고해보는 것이 좋을 것으로 보인다.
다만 buildSrc 기능은 Groovy에서도 사용 가능하고 멀티 모듈 프로젝트에서는 build.gradle 스크립트 중 일부만 build.gradle.kts로 이전도 가능하니, 빌드 작업량이 크지 않은 모듈들부터 build.gradle.kts로 이전하는 전략도 좋은 방법으로 보인다.
결론
개발 언어와 빌드 스크립트를 작성하는 언어를 같은 Kotlin으로 쓰는 것은 빌드 스크립트에 대한 러닝커브를 줄여 생산성을 향상시킬 수 있다. 또한 Kotlin에서 제공하는 수많은 편리한 언어기능은 물론, 정적언어가 제공하는 수많은 장점들을 빌드스크립트 작성에 활용할 수 있다면 유지보수성이 올라갈 것은 명백해 보인다.
Kotlin DSL로 작성된 Gradle Build Script는 기존 Groovy로 만든 Build Script에 비해 많은 장점이 있지만, 현재 빌드 속도에 대한 문제가 해결되지 않아 큰 프로젝트에 적용시키기 어려워 보인다. 하지만 반대로 생각해보면 빌드 속도 문제만 해결된다면 안드로이드 공식 빌드 시스템에서의 빌드 스크립트가 Groovy에서 Kotlin DSL로 바뀔지도 모른다.