guides.gradle.org/migrating-build-logic-from-groovy-to-kotlin/
Groovy DSL gradle build 스크립트를 Kotlin DSL 로 변환하는 작업을 시작하려고 한다.
이번 포스팅에서는 하나의 모듈일 경우에 대하여 기본적인 변환 작업의 예를 보여준다.
Kotlin DSL 이란 무엇?
- DSL(Domain Specific Language) 은 특정 분야에 최적화된 프로그래밍 언어를 말한다 (ex. SQL)
- DSL 은 Java, C 같은 범용적인 언어보다는 덜 복잡하며 DSL 의 대상 분야에서 (개발자가 아니어도) 사용할 수 있게 만들어진다 (때문에 가독성이 높다)
- 여기서 Kotlin 이 붙으면 코틀린의 특징으로 읽기 좋고 간략한 코드를 만든 것이 Kotlin DSL 이다
Kotlin DSL scripts
- Groovy 베이스와 마찬가지로 Kotlin DSL 은 Gradle Java API 위에 구현된다
- Kotlin DSL 스크립트에서 읽을 수 있는 모든 것은 Gradle 이 컴파일하고 실행하는 코틀린 코드이다
- 빌드 스크립트에서 사용하는 객체, 함수, 프로퍼티들은 Gradle API 와 적용된 플러그인 API 에서 가져온다
Script file names
- Kotlin DSL 을 활성화 하려면 빌드 스크립트 파일의 확장자를 아래와 같이 바꾸면 된다
- Groovy 의 .gradle -> Kotlin 의 .gradle.kts
- 주의할 점은 Groovy 와 Kotlin 을 섞어서 사용할 수 있다
아니~ Groovy 잘 쓰고 있는데 왜 Kotlin 으로 바꿔야 하나?
- 사실 구글이 가이드하는 방법으로도 충분하다
- 하지만 Kotlin + buildSrc 를 사용하면 안드로이드 스튜이오에서 자동완성을 사용할 수 있어 편-안
- 여기에 Custom Plugin 까지 더해지면 더 편-안
Before you start migrating
- 현재 Intellij IDEA 와 Android Studio 에서는 지원이 잘 되고 있지만,
그 외 Eclipse 같은 다른 IDE 는 편집 시 도움되는 툴을 아직 제공하지 않는다 - 빌드 스크립트 캐싱을 clean 할 경우 조금 느릴 수 있다 (이 경우 성능 가이드 참고해야함)
- Java 7 은 지원하지 않기 때문에 Java 8 이상으로 실행해야 한다
Groovy vs Kotlin script
- 문자열 작성 시, 그루비는 '작은 따옴표' 와 "큰 따옴표" 를 사용할 수 있지만 코틀린은 "큰 따옴표" 만 사용 가능하다
// build.gradle
group 'com.acme'
dependencies {
implementation 'com.acme:example:1.0'
}
// build.gradle.kts
group "com.acme"
dependencies {
implementation "com.acme:example:1.0"
}
- 함수 호출 시, 그루비는 괄호() 생략 가능하지만 코틀린은 (괄호) 가 필요하다
- 속성 할당 시, 그루비는 = 없이 할당 가능하지만 코틀린은 = 이 필요하다
// build.gradle
group "com.acme"
dependencies {
implementation "com.acme:example:1.0"
}
// build.gradle.kts
group = "com.acme"
dependencies {
implementation("com.acme:example:1.0")
}
본격 마이그레이션 작업을 시작해본다
1. .gradle 에서 .gradle.kts 로 변경하기 전 선행 작업
- 큰 따옴표를 사용하여 인용문을 통일하자!
- 함수 호출 및 속성 할당을 각각 괄호와 = 연산자를 사용해서 바꾸자
- 이것을 먼저 하는 이유는 이따 kts 로 변경 시 어차피 수정해야하니,, 지금 해보자는!
dependencies {
// module 을 debug/release 로 나눠서 추가 할 경우
- debugImplementation project(":test-android")
- releaseImplementation project(":test-android")
// 일반적인 라이브러리를 추가 할 때
- implementation "com.google.firebase:firebase-messaging:20.2.4"
+ implementation("com.google.firebase:firebase-messaging:20.2.4")
- kapt "com.google.dagger:hilt-android-compiler:$rootProject.hilt_version"
+ kapt("com.google.dagger:hilt-android-compiler:$rootProject.hilt_version")
}
2. 의존성 관리를 위해 buildSrc 구성하기
- Gradle 은 buildSrc 디렉터리가 발견되면 자동으로 컴파일하고 테스트해서 빌드 스크립트의 클래스 경로에 넣는다
- 멀티 프로젝트의 경우 buildSrc 는 프로젝트 root 에 하나만 있어야 한다
2-1. buildSrc 구성하기
- 프로젝트 root 에 buildSrc 디렉터리를 생성한다
- Dependencies.kt 파일은 build.gradle dependencies 블럭에 넣었던 각종 라이브러리들 정보를 구현할 것이다
- Versions.kt 파일은 Dependencies.kt 에 정리된 라이브러리들의 버전만 따로 정리해서 구현할 것이다
(파일명은 자유롭게 작성해도 된다!) - 여기서는 간단 버전을 구현하고 Plugin 구현은 다음 포스팅에서 진행!
2-2. build.gradle.kts 구현해보자
- /buildSrc 바로 밑에 build.gradle.kts 파일을 생성 후 아래와 같이 작성해준다
plugins {
`kotlin-dsl`
}
repositories {
jcenter()
}
2-3. Versions.kt 를 구현해보자
- 가져다 쓰기 편하게 자유롭게 구현하면 된다
object Versions {
// gradle plugin
const val buildGradle = "4.0.1"
const val googleServicesGradle = "4.3.3"
const val firebaseCrashlyticsGradle = "2.2.0"
// kotlin
const val kotlin = "1.4.0"
// coroutines
const val coroutines = "1.3.9"
...
}
2-4. Dependencies.kt 를 구현해보자
- 가져다 쓰기 편하게 자유롭게 구현하면 된다
object Dependency {
object GradlePlugin {
const val build = "com.android.tools.build:gradle:${Versions.buildGradle}"
const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
const val googleService = "com.google.gms:google-services:${Versions.googleServicesGradle}"
const val firebaseCrashlytics = "com.google.firebase:firebase-crashlytics-gradle:${Versions.firebaseCrashlyticsGradle}"
}
object Kotlin {
const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}"
}
const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}"
...
}
2-5. .gitIgnore 도 추가해주자
- 위 파일들을 구현 후 gradle sync 를 하면 build 관련 파일들이 마구마구 생기므로 gitIgnore 를 추가해준다
/build
3. settings.gradle -> settings.gradle.kts 변경
- include() 코드를 살펴보면 String 을 vararg 로 받고있어서 멀티 모듈일 경우 쉼표로 구분하여 추가하면 된다
include(":app")
4. 프로젝트 레벨 build.gradle -> build.gradle.kts 변경
- 위에서 작성했던 Dependencies.kt 를 참고하여 기존의 classpath 를 아래와 같이 변경해준다
- 기존과 또 달라진 점은 maven { url "http://~~~" } 이었던 코드가 괄호와 큰따옴표로 바뀌었다
- clean task 부분도 문법이 달라졌다
- 만약 ext {} 로 버전명을 선언하고 있었다면 이 코드는 이제 과감히 제거하자
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath(Dependency.GradlePlugin.build)
classpath(Dependency.GradlePlugin.kotlin)
classpath(Dependency.GradlePlugin.googleService)
classpath(Dependency.GradlePlugin.firebaseCrashlytics)
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
// 만약 maven url 을 추가가 필요하면 아래와 같이 변경한다
maven(url = "http://~~~")
}
}
task("clean", Delete::class) {
delete = setOf(rootProject.buildDir)
}
5. 모듈 레벨 build.gradle -> build.gradle.kts 변경
- plugins 부분에 코틀린 안드로이드 처럼 version 을 따로 입력하여
프로젝트 레벨 build.gradle.kts 에서 classpath 부분을 생략할 수 있다 - 그러나 여러 모듈을 사용 시, 추후 개선건도 있고 com.android.library 의 경우 classpath 설정이 필요하여 패스
plugins {
id("com.android.application")
kotlin("android") version Dependency.GradlePlugin.kotlin
kotlin("android.extensions")
kotlin("kapt")
id("com.google.firebase.crashlytics")
}
android {
compileSdkVersion(Versions.compileSdk)
buildToolsVersion(Versions.buildTools)
defaultConfig {
applicationId = "com.example.test.android"
minSdkVersion(Versions.minSdk)
targetSdkVersion(Versions.targetSdk)
versionCode = 100
versionName = "1.0.0"
multiDexEnabled = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
dataBinding = true
}
signingConfigs {
getByName("debug") {
storeFile = file("../keystore/debug.keystore")
}
// create 로 생성해서 지정하고 있는 점 주의!
create("release") {
storeFile = file("../keystore/test.keystore")
storePassword = file("../keystore/test.keystore.password").readText().trim()
keyAlias = "...."
keyPassword = file("../keystore/test.keystore.password").readText().trim()
}
}
buildTypes {
getByName("debug") {
isMinifyEnabled = false
isDebuggable = true
signingConfig = signingConfigs.getByName("debug")
manifestPlaceholders["enableCrashlytics"] = "false"
extra.set("alwaysUpdateBuildId", false)
}
// 이제 getByName 으로 가져올 수 있음
getByName("release") {
// shrinkResources = true
isMinifyEnabled = true
isDebuggable = false
signingConfig = signingConfigs.getByName("release")
manifestPlaceholders["enableCrashlytics"] = "true"
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
flavorDimensions("server")
productFlavors {
create("dev") {
dimension = "server"
applicationIdSuffix = ".dev"
// 아래 설정을 사용한다면!
buildConfigField("String", "field_name", "field_value")
}
create("cbt") {
dimension = "server"
applicationIdSuffix = ".cbt"
}
create("prod") {
dimension = "server"
}
}
sourceSets {
getByName("dev") {
getByName("main").java.srcDirs("src/main/kotlin")
getByName("main").resources.srcDirs("/src/main/res", "/src/dev/res")
getByName("test").java.srcDirs("src/test/kotlin")
}
getByName("cbt") {
getByName("main").java.srcDirs("src/main/kotlin")
getByName("main").resources.srcDirs("/src/main/res", "/src/cbt/res")
getByName("test").java.srcDirs("src/test/kotlin")
}
getByName("prod") {
getByName("main").java.srcDirs("src/main/kotlin")
getByName("main").resources.srcDirs("/src/main/res")
getByName("test").java.srcDirs("src/test/kotlin")
}
}
dexOptions {
preDexLibraries = true
javaMaxHeapSize = "4096m"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
packagingOptions {
exclude("META-INF/LICENSE.txt")
exclude("META-INF/NOTICE.txt")
}
lintOptions {
isAbortOnError = false
}
/*testOptions {
unitTests.returnDefaultValues = true
}*/
}
dependencies {
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
// module 추가 시
debugImplementation(project(":모듈명"))
releaseImplementation(project(":모듈명"))
// kotlin
implementation(Dependency.Kotlin.stdlib)
// firebase
implementation(Dependency.Firebase.messaging)
implementation(Dependency.Firebase.analytics)
implementation(Dependency.Firebase.crashlytics)
// coroutines
implementation(Dependency.coroutines)
// exclude 사용 시 아래와 같이 변경한다
// glide
implementation(Dependency.Glide.okHttp3Integration) {
exclude(group = "com.android.support")
}
kapt(Dependency.Glide.compiler)
// test
testImplementation(Dependency.Test.junit)
androidTestImplementation(Dependency.Test.runner)
androidTestImplementation(Dependency.Test.espresso)
}
apply(mapOf("plugin" to "com.google.gms.google-services"))
+추가
최근에 com.android.tools.build:gradle:4.0.1 을 4.1.0 으로 버전 업데이트하면서
기존에 없던 빌드 에러가 생기기 시작!
바로 mapOf 를 사용해서 build.gradle.kts 에 적용하던 애들이다.
room 스키마 관련 path, manifestPlaceholders 관련으로 아래와 같이 변경하면 된다.
// AndroidManifest 파일에서 사용 할 변수
manifestPlaceholders["enableCrashlytics"] = "false"
// room 스키마
argument("room.schemaLocation", "$projectDir/schemas")
'Android개발' 카테고리의 다른 글
Android Lint #2 - Custom Lint (0) | 2020.07.03 |
---|---|
Android Lint #1 - 기본 (0) | 2020.07.02 |
App Startup time (1) | 2020.06.25 |
App Startup (0) | 2020.06.25 |
Elevation (0) | 2020.03.18 |
댓글