매일같이 쓰고 있지만 막상 설명하라고 하면 어...? 하게 되는 것 중 하나가 gradle인 것 같아요.
gradle은 무엇인지, android studio 최신 버전에서 새 프로젝트를 만들었을 때 자동으로 생기는 gradle script는 각각 어떤 역할을 하는지, 마지막으로 Kotlin DSL에 대해 간략히 정리해보았습니다.

Gradle이란?

Gradle은 어떤 타입의 소프트웨어든 유연하게 빌드할 수 있도록 설계된 오픈 소스 빌드 자동화 툴입니다.

  1. Gradle을 통해 Compile, Test, Run과 같은 일련의 과정을 한 번에 편리하게 실행할 수 있습니다.
  2. gradle 스크립트를 통해 다양한 repository와 라이브러리를 편리하게 추가/삭제할 수 있습니다.

특징

  1. 높은 퍼포먼스 - 한 번 빌드된 프로젝트는 다음 빌드에서 훨씬 빠른 속도로 빌드됩니다
    1. Incremental build : 이미 빌드된 파일을 모두 다시 빌드하지 않고, 변경이 발생한 파일만 다시 빌드합니다
    2. Build cache : 캐시를 이용해 이전 빌드의 결과물을 다른 빌드에서 사용합니다
  2. JVM 기반으로 동작합니다. (따라서 사용하려면 JDK 설치가 선행되어야 합니다)
  3. gradle을 확장해 쉽게 본인에게 필요한 task type이나 build model을 구현할 수 있습니다. flavor과 build type 컨셉을 추가한 Android가 그 중 한 예시입니다.
  4. Android Studio, IntelliJ IDEA, Eclipse같은 주요 IDE들이 gradle 빌드를 지원합니다.
  5. Build Scan으로 빌드시 문제가 생겼을때 문제를 찾을 수 있도록 광범위한 정보를 제공합니다.

Android Gradle Plugin (AGP)

Android Gradle Plugin란?

Android Gradle 플러그인은 Android 애플리케이션을 지원하는 빌드 시스템입니다. 다양한 유형의 소스를 컴파일하고 실제 휴대폰에서 실행할 수 있는 애플리케이션에 컴파일된 소스를 연결하는 지원 기능이 있습니다.
Android Studio Chipmunk 버전 기준으로, 안드로이드 프로젝트를 새로 만들었을 때 때 기본으로 생기는 gradle 파일을 하나씩 살펴보겠습니다.

gradlew (gradle-wrapper)

gradlew는 개발자가 새로운 환경에서 개발을 시작하더라도, gradle을 설치할 필요 없이 바로 동일한 환경에서 실행할 수 있도록 해주는 내장 gradle입니다.
프로젝트를 생성하고 나면 사진과 같이 gradle-wrapper.jar 파일과 환경 설정과 관련된 gradle-wrapper.properties 파일이 생겼습니다. 그리고 제 OS 환경에 맞춰 gradle을 손쉽게 실행할 수 있게 해주는 배치 스크립트 gradlew.bat 까지 만들어져 있습니다.

터미널에서 ./gradlew 명령어를 입력해보면, 저는 gradle에 대한 설정이나 설치를 진행한 적이 없음에도 빌드가 잘 돌아갑니다. 바로 이 gradle wrapper 덕분입니다.

gradle.properties

빌드 설정과 관련된 여러가지 옵션을 제공합니다. 프로젝트 최상단에 위치한 파일이므로, 프로젝트 전체에 적용됩니다. #으로 시작하는 라인은 주석입니다. # 없이 초기부터 활성화 되어있는 옵션들은 다음과 같습니다.

org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
kotlin.code.style=official
android.nonTransitiveRClass=true
  1. org.gradle.jvmargs : Gradle Daemon에 사용될 JVM 인자를 지정합니다. Daemon Process는 서비스의 요청에 응답하기 위해 오랫동안 실행중인 백그라운드 프로세스입니다. gradle daemon은 gradle이 실행되는 인스턴스를 유지하고 빌드가 끝난 뒤에도 사라지지 않고 백그라운드에서 대기합니다. Gradle 빌드 시간을 단축시키는 역할을 합니다. 이 설정은 빌드 성능을 위한 JVM 메모리 세팅에 유용합니다.
  2. useAndroidX : support library 대신 적절한 AndroidX 라이브러리를 사용합니다.
  3. kotlin.code.style : 코틀린 코드 스타일을 지정하는 옵션입니다. gradle은 코드를 정렬하는 기능은 하지 않기 때문에, 이 옵션은 Intellij IDE에서 사용하는 옵션입니다.
  4. android.nonTransitiveRClass : R 클래스(R.xx.xxx 형식으로 안드로이드의 리소스를 관리하는 변수들이 모여있는 클래스)에 라이브러리 자체에 선언된 리소스만 포함하고, 라이브러리에 종속되는 항목들은 포함하지 않도록 하는 옵션입니다. 이 옵션을 사용하면 APK 빌드시 용량이 크게 줄어듭니다.

settings.gradle

Android studio bumblebee 이전 버전에서 생성된 프로젝트면 구조가 다를 수 있습니다.

settings.gradle 파일은 프로젝트 최상단에 만들어집니다. 프로젝트 전체에 적용되는 설정을 정의하고, 앱을 빌드할 때 포함해야 하는 모듈을 Gradle에 알려줍니다.

  • 프로젝트에서 사용하는 모듈을 include로 정의할 수 있습니다. 가장 처음에는 app 이라는 이름의 1개의 모듈만 존재하지만, 필요에 따라 여러개의 모듈을 추가할 수 있어요.
  • dependencyResolutionManagement는 프로젝트 전반에 적용할 repositories를 선언하는 블록입니다.
  • pluginManagement custom plugin repositories을 명시하기 위해 사용합니다. 반드시 초기화 문이나 파일에서 첫번째로 선언되는 블록이어야 합니다.
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "EuphonyTest"
include ':app'

build.gradle (root)

프로젝트 최상단에 있는 build.gradle 파일입니다.

plugins {
    id 'com.android.application' version '7.2.1' apply false
    id 'com.android.library' version '7.2.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
  • 앱에서 사용할 plugins를 선언합니다. plugin은 사전에 만들어져있는 gradle task의 집합입니다. 보통은 dependency에만 추가하면 되지만 종종 plugin까지 추가해야하는 라이브러리들이 있는데요, 그때 수정해야 하는 부분입니다.
  • task를 선언할 수 있습니다. task는 gradle의 작업 단위인데요, 특정한 동작을 수행하기 위해 만들어두는 일종의 명령어라고 생각하면 됩니다. 터미널에 ./gradlew task 명령어를 입력해보면, 기본으로 내장되어 있는 task의 목록이 쭉 나옵니다. 현재 clean이라는 task가 선언되어 있습니다.

사용할 수 있는 task 목록은 우측의 gradle 탭을 눌러서도 확인해볼 수 있습니다. 더블클릭하면 실행도 됩니다.

build.gradle (app)

마지막은 app 모듈에 적용되는 build.gradle 파일입니다. 여기서 선언된 내용은 app 모듈에만 적용됩니다.

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.euphonytest"
        minSdk 24
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    implementation 'euphony.lib:euphony:0.7.1.6'
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.2'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
    implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
  • plugins : app 모듈에서 사용할 플러그인을 선언하는 부분입니다.
  • compileSdk : 프로젝트를 컴파일할 API LEVEL을 지정합니다.
  • defaultConfig : 앱 아이디, 버전명, 버전이름, 최소 지원 OS 등등 앱 기본 설정을 정의합니다. 컴파일 될때 apk 파일 이름 포맷 등등 필요에 따라 다양한 옵션을 추가할 수 있어요.
  • compileOptions : 컴파일 옵션입니다. Java8 기능을 이용할 수 있도록 옵션이 추가되어 있네요.
  • kotlinOptions : kotlin에서 바이트코드를 생성할때 대상 java 버전을 설정합니다. 현재 Java8로 설정되어 있습니다.
  • buildFeatures : 현재 viewBinding을 사용하도록 설정되어 있습니다. compose를 시작하려면 이 블럭 안에 viewBinding를 compose로 바꾸면 됩니다.
  • dependencies : 앱에서 사용하고자 하는 라이브러리를 여기서 선언하면 빌드시 import가 진행됩니다. 사용하려는 라이브러리를 "group:name:version"의 형식으로 추가하면 됩니다. 앞에 붙어있는 implementation는 의존성 옵션인데요. implementation, api, compile 등 다양한 옵션이 있어 본인이 필요한 경우 변경이 가능합니다. 가장 흔하게 쓰이는 것은 implementation입니다.
  • buildTypes : 어떤 식으로 빌드할 것인지 빌드 버전, build flavor를 설정할 수 있습니다. 기본으로 생기는 flavor는 debug, release 두가지 인데요. debug로 빌드할 경우 APK 용량이 다소 크지만 개발하면서 디버깅이 가능하고, release로 빌드하면 난독화를 시키고 APK 용량을 축소시키는 옵션이 적용됩니다. 또 flavor 별로 사용할 변수를 지정할 수 있습니다. 가장 대표적인 사용 예시는 flavor 별로 서버 주소를 테스트 서버/라이브 서버로 다르게 적용하는 것입니다. build flavor는 좌측에 있는 build variants 탭을 선택해 고를 수 있습니다.

Kotlin DSL

위에서 소개해드린 gradle 스크립트는 모두 groovy로 작성되어 있습니다. Kotlin DSL을 사용하면 Kotlin으로 gradle 스크립트를 작성할 수 있습니다.
Kotlin 사용자에겐 훨씬 수정이 편리하고, IDE의 지원을 받아 자동완성, 구문 강조 효과를 받을 수 있습니다. 오류가 있는 코드를 미리 알 수 있고, 변수명 등을 리팩토링 하기 편리합니다.
물론 장점만 있는건 아닌데요, groovy와 달리 지금은 라이브러리의 새로운 버전이 생겼을 때 알림을 받는 기능이 지원되지 않습니다. 그리고 빌드 캐시가 없는 최초 실행 시엔 Groovy보다 빌드 속도가 약 1.5~2배 느릴 수 있습니다.
아래는 동일한 코드를 각각 groovy와 kotlin dsl로 작성했을 때의 예시입니다.

  • Groovy
dependencies {
    def nav_version = "2.5.0"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}
  • Kotlin DSL
dependencies {
    val nav_version = "2.5.0"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
}

Kotlin으로 작성했을때 val , functionName(”string”) 처럼 눈에 익숙한 형태의 구문이 됩니다.
Groovy → Kotlin DSL로 교체하는 방법은 매우 간단한데요. 파일 명 뒤에 .kts를 붙이기만 하면 됩니다. 물론 groovy와 DSL은 상호간 문법이 많이 다르기 때문에, 정상적인 실행을 위해선 스크립트를 전반적으로 수정하는 작업이 필요합니다. 관련된 내용은 여기서 확인이 가능합니다.

참고자료