Skip to main content

코인 Gradle 청소 후기

· 8 min read

Gradle 언어를 Groovy 에서 Kotlin DSL로 변경하고, 이것저것 청소를 해봤습니다.

코드 먼저 확인해보려면 해당 PR을 확인해주세요!

Groovy 에서 Kotlin DSL 적용 시 고려할 점

기본적으로 Groovy 언어로 되어있는 gradle 파일을 Kotlin DSL 로 마이그레이션 할 때 주의할 점이 있습니다.

자세한 내용을 보고싶으면, 공식문서를 참고해주세요.

마이그레이션 들어가기 앞서 주의할 점은 build.gradle 파일 → build.gradle.kts 로 파일명을 변경할 때, kts 파일에서 groovy 언어를 인식하지 못하기 때문에 기존 build.gradle 파일에서 어느정도 마이그레이션을 진행한 뒤, kts 확장자 파일로 변경해주시면 스트레스를 덜 받습니다.

  • groovy 언어는 메서드 호출 시 괄호를 생략하지만, kotlin dsl 에서는 괄호가 필요합니다.
compileSdkVersion 30 // Groovy
compileSdkVersion(30) // Kotlin DSL
  • groovy 언어에서는 속성 할당 시 = 연산자가 필요없지만, kotlin dsl 는 필요하다.
sourceCompatibility JavaVersion.VERSION_17 // Groovy
sourceCompatibility = JavaVersion.VERSION_17 // Kotlin DSL
  • groovy 언어에서는 작은 따옴표를 사용하고, kotlin dsl 에서는 큰 따옴표를 사용한다.
id 'com.android.application' // Groovy
id ("com.android.application") // Kotlin DSL
  • 변수 정의 방법
def building64Bit = false // Groovy
val building64Bit = false // Kotlin DSL
  • 부울 속성의 접두사
minifyEnabled true // Groovy
isMinifyEnabled = true // Kotlin DSL
  • 리스트 및 맵 형식
jvmOptions += ["-Xms4000m", "-Xmx4000m", "-XX:+HeapDumpOnOutOfMemoryError</code>"] // Groovy
jvmOptions += listOf("-Xms4000m", "-Xmx4000m", "-XX:+HeapDumpOnOutOfMemoryError") // Kotlin DSL
def myMap = [key1: 'value1', key2: 'value2'] // Groovy
val myMap = mapOf("key1" to "value1", "key2" to "value2") // Kotlin DSL

마이그레이션하면서 진행된 코드 정리

마이그레이션을 진행하면서 흔하게 사용되는 build.gradle 은 groovy 에서 Kotlin DSL 로 어떻게 변화되었는 지 전체적인 코드를 비교하여 정리하려고 합니다. 추가적으로, 코인 서비스에서는 version catalog 가 사용되었으며, convention plugin 어떤 것을 추가했는 지 정리해보겠습니다. 코드의 순서는 Groovy, Kotlin DSL 순서입니다.

build.gradle(project)

  • Groovy
buildscript {
repositories {
google()
mavenCentral()
}

ext {
/* Common */
compileSdkVersion = 34
minSdkVersion = 21
targetSdkVersion = 34
versionName = "3.4.3"
minVersionCode = 30403
}

dependencies {
classpath libs.android.gradle.tool
classpath libs.kotlin.gradle
classpath libs.android.gradle.crashlytics
classpath libs.firebase.appdistribution.gradle
classpath libs.hilt.gradle.plugin
}
}

plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.google.service) apply false
alias(libs.plugins.androidLibrary) apply false
}

tasks.register('clean', Delete) {
delete rootProject.buildDir
}

  • Kotlin DSL
buildscript {
repositories {
google()
mavenCentral()
}

extra.apply {
set("compileSdkVersion", 34)
set("minSdkVersion", 24)
set("targetSdkVersion", 34)
set("versionName", "3.4.3")
set("versionCode", 30403)
}

dependencies {
classpath(libs.android.gradle.tool)
classpath(libs.kotlin.gradle)
classpath(libs.android.gradle.crashlytics)
classpath(libs.firebase.appdistribution.gradle)
classpath(libs.hilt.gradle.plugin)
}
}

plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.google.service) apply false
alias(libs.plugins.androidLibrary) apply false
}

tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}

build.gradle 플러그인 id 추가하는 방법

// groovy
plugins {
id 'com.android.application'
}

// Kotlin DSL
plugins {
id ("com.android.application")
}
  • Groovy
android {
defaultConfig {
manifestPlaceholders = [naverMapKey: getNaverMapKey()]
}
}

def getNaverMapKey() {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
return properties.getProperty('navermap_key')
}
  • Kotlin DSL
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

android {
defaultConfig {
manifestPlaceholders["naverMapKey"] = getPropertyKey("navermap_key")
}
}

fun getPropertyKey(propertyKey: String): String {
val nullableProperty: String? =
gradleLocalProperties(rootDir).getProperty(propertyKey)
return nullableProperty ?: "null"
}

local.properties 에는 다음과 같이 navermap key 가 선언되어 있을겁니다.

navermap_key=******* // 쌍따음표 없이

다음 Manifest 에도 다음과 같이 메타데이터로 navermap key 를 할당할 수 있습니다.

<meta-data
android:name="com.naver.maps.map.CLIENT_ID"
android:value="${naverMapKey}"/>

서명 설정하는 방법

해당 관련 Kotlin DSL 코드를 자세히 보기 위해서는 문서를 참고해주세요.

  • Groovy
android {
signingConfigs {
release {
storeFile file("./team_kap_android.jks")
storePassword = getPassword()
keyAlias = "koin_release_key"
keyPassword = getPassword()
}
}
}

fun getPassword() {
val properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
return properties.getProperty('koin_store_password')
}
  • Kotlin DSL
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

android {
signingConfigs {
create("release") {
storeFile = file("./team_kap_android.jks")
storePassword = getPropertyKey("koin_store_password")
keyAlias = getPropertyKey("koin_release_key")
keyPassword = getPropertyKey("koin_store_password")
}
}
}

fun getPropertyKey(propertyKey: String): String {
val nullableProperty: String? =
gradleLocalProperties(rootDir).getProperty(propertyKey)
return nullableProperty ?: "null"
}

빌드 타입 구분, 디버그 & 릴리즈

  • Groovy
android {
buildTypes {
debug {
debuggable true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField "boolean", "IS_DEBUG", "true"
firebaseCrashlytics {
mappingFileUploadEnabled false
}
}

release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField "boolean", "IS_DEBUG", "false"
signingConfig = signingConfigs.release
firebaseAppDistribution {
artifactType = "AAB"
releaseNotes = "${project.versionName} release"
groups = "bcsd"
}
}
}
}
  • Kotlin DSL
android {
buildTypes {
getByName("debug") {
isDebuggable = true
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
buildConfigField("Boolean", "IS_DEBUG", "true")
firebaseCrashlytics {
mappingFileUploadEnabled = false
}
}

getByName("release") {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
buildConfigField("Boolean", "IS_DEBUG", "false")
signingConfig = signingConfigs.getByName("release")
firebaseAppDistribution {
artifactType = "AAB"
releaseNotes = "${rootProject.extra["versionName"]} release"
groups = "bcsd"
}
}
}
}

Convention Plugin, Application 과 Library Extension 구분하기

Convention Plugin extension 을 다음과 같이 진행할 때, library 모듈에서는 LibraryExtension 을 사용하고, application 모듈에서는 ApplicationExtension 을 사용하여 혼동해서 사용하지 말아야 합니다. 기존 코인 서비스에서 Hilt ConventionPlugin 을 ApplicationExtension 을 사용하여 의존성을 주입하고 있어 library 모듈인 data, core 모듈에서 사용하지 못한 경험이 있습니다.

apply("com.android.library")

extensions.configure<LibraryExtension> {
configureAndroidLibrary(this)
}
apply("com.android.application")

extensions.configure<ApplicationExtension> {
configureAndroidProject(extension)
}

추가적으로, ConventionPlugin 을 진행하면서 FirebaseConventionPlugin, JavaLibraryConventionPlugin 을 추가해보았습니다.

소감

Groovy 에서 Kotlin DSL 로 마이그리이션 하면서 기존에 프로젝트를 하면서 Groovy 에서의 오류 추적이 어려운 것을 Kotlin DSL 로 한편 쉬워졌을 거라 기대합니다. 코인의 모든 모듈의 gradle 을 살피고 정리해보면서, core와 feature 단위 별 모듈화의 필요성을 느꼈습니다. 혼자 프로젝트를 하는 것이 아닌 다양한 사람들이 같이 협업하다보니 각자가 진행하는 feature 단위 모듈에서 진행하면 gradle 도 훨씬 깔끔해질 것 같습니다. core 는 단일모듈로 있는데, 해당 모듈에서도 다양한 라이브러리가 있어 analytics, notification, designsystem 등 모듈화의 필요성을 느꼈습니다. 간단히 진행될 줄 알았던 해당 작업은 저에게 모듈화의 중요성을 깨닫게 해주었습니다.