본문 바로가기
Android개발

Migrating build.gradle from Groovy to Kotlin

by 궝테스트 2020. 9. 25.

guides.gradle.org/migrating-build-logic-from-groovy-to-kotlin/

 

Migrating build logic from Groovy to Kotlin

Declaring dependencies in existing configurations is similar to the way it’s done in Groovy build scripts, as you can see in this example: build.gradle plugins { id 'java' } dependencies { implementation 'com.example:lib:1.1' runtimeOnly 'com.example:run

guides.gradle.org

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

댓글