멀티 모듈을 이용한 안드로이드 프로젝트 개발이 늘어나고 있습니다.
하지만 모듈을 만들 때 마다 build.gradle의 중복 코드가 생기고 있다는 사실을 아시나요?
이러한 문제를 해결하기 위해 빌드 로직을 효율적으로 관리하는 Gradle Kotlin 컨벤션 플러그인을 소개합니다.
*페이지 마지막에 코드 링크 첨부되어 있습니다.
15. 빌드 로직 컨벤션
❏ 이처럼 모듈에는 재사용이 가능한 중복 코드들이 많이 있다.
❏ 어떤 로직을 재사용할지 정하는 것은 하나의 컨벤션이다.
❏ 모듈의 종류와 개수가 많아질수록 이러한 컨벤션은 더욱 중요하다!
❏ 그렇다면 빌드 로직을 어떤 방법으로 재사용 할 수 있을까?
17. ❏ 빌드 로직을 재사용할 수 있는 확장 기능
❏ Gradle Task를 추가하거나 설정을 관리
❏ id(“com.android.application”) 덕분에 앱이 실행할 수 있게 만들어짐
❏ 만약 이러한 플러그인이 없다면, 수많은 중복 코드를 작성해야 함
Gradle Plugin
18. Gradle Plugin
Script Plugin Binary Plugin
스크립트 코드를 별도 파일로 분리
apply(from=”common.gradle”)
apply(from=”project.gradle”)
...
Plugin 인터페이스를 구현하는 클래스 작성
플러그인 아이디를 등록해서 사용
plugins {
id(“my.custom.plugin”)
}
19. Gradle Plugin
Precompiled Script Plugin
Binary Plugin을 클래스로 작성하는 대신 xxx.gradle.kts 파일의
스크립트 형태로 간편하게 작성할 수 있는 확장 플러그인
- buildSrc/src/main/kotlin/common.android.gradle.kts
// build.gradle.kts
plugins {
id(“common.android”)
}
27. // common.android.hilt.gradle
plugins { ... }
// Only Project and Settings build scripts can contain plugins {} blocks
apply plugin: 'com.google.dagger.hilt.android'
apply plugin: 'org.jetbrains.kotlin.kapt'
dependencies {
implementation libs.hilt.android
kapt libs.hilt.android.compiler
}
Script Plugin
plugins { }는 사용할 수 없습니다.
28. // build.gradle.kts :feature:home
plugins {
id(“com.android.library”)
kotlin(“android”)
}
apply(from=”common.android.gradle”)
apply(from=“common.android.compose.gradle”)
apply(from=”common.android.hilt.gradle”)
dependencies {
implementation(project(":core:domain"))
implementation(project(":core:designsystem"))
testImplementation(libs.junit4)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.espresso.core)
}
Script Plugin
지금까지 작성한 파일은
apply를 이용해서 적용할 수 있습니다.
29. // build.gradle.kts :feature:setting
plugins {
id(“com.android.library”)
kotlin(“android”)
}
apply(from=”common.android.gradle”)
apply(from=“common.android.compose.gradle”)
apply(from=”common.android.hilt.gradle”)
dependencies {
implementation(project(":core:domain"))
implementation(project(":core:designsystem"))
testImplementation(libs.junit4)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.espresso.core)
}
Script Plugin
다른 feature 모듈에서도 동일하지만,
문제는 또 다른 중복코드가 발생합니다.
31. // build.gradle.kts :feature:home
plugins {
id(“com.android.library”)
kotlin(“android”)
}
apply(from=”common.android.feature.gradle”)
Script Plugin
이제는 3줄의 코드로 작성할 수 있습니다.
32. Script Plugin
❏ 가장 단순하고 빠르게 사용할 수 있는 빌드 로직 재사용 방법
❏ 그런데 왜 groovy로만 작성하나요?
Gradle Kotlin DSL은 타입에 안전하게 접근하기 때문에 Script Plugin을 사용할 수 없음
https://docs.gradle.org/current/userguide/kotlin_dsl.html#sec:kotlin_using_standard_api
38. internal class AndroidLibraryPlugin : Plugin<Project> {
override fun apply(target: Project) = with(target) {
with(pluginManager) {
apply("com.android.library")
apply("org.jetbrains.kotlin.android")
}
...
}
}
AndroidLibraryPlugin Binary Plugin
다른 플러그인을 적용할 수 있습니다.
39. // build.gradle.kts :feature:home
android {
compileSdk = 33
defaultConfig {
minSdk = 24
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
coreLibraryDesugaring(libs.android.desugarJdkLibs)
}
AndroidLibraryPlugin Binary Plugin
안드로이드 버전 관련 설정 코드
40. internal class AndroidLibraryPlugin : Plugin<Project> {
override fun apply(target: Project) = with(target) {
...
extensions.configure<LibraryExtension> {
compileSdk = 33
defaultConfig.minSdk = 24
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
}
}
android { ... } 블록 접근을 위한 코드
AndroidLibraryPlugin Binary Plugin
57. Binary Plugin
❏ 빌드 로직 캡슐화를 통해 보다 추상적이고 높은 확장성 제공
❏ 하지만, Kotlin DSL에 대한 이해도가 요구되어 🔥난이도가 높음🔥
❏ Gradle Task와 같은 동작을 조합해서 복잡한 기능 사용 가능
❏ feature 모듈의 컨벤션을 하나의 플러그인으로 만들 수 있음
59. Precompiled Script Plugin
❏ 그런데 항상 클래스를 구현하면 귀찮지 않을까요?
❏ Script Plugin과 Binary Plugin의 특징을 모두 사용할 수 없을까?
https://docs.gradle.org/current/userguide/custom_plugins.html#sec:precompiled_plugins
- Binary Plugin을 클래스로 작성하는 대신 xxx.gradle.kts 파일의 스크립트 형태로 간편하게 작성할
수 있는 확장 플러그인
- buildSrc/src/main/kotlin/common.android.gradle.kts
// build.gradle.kts
plugins {
id(“common.android”)
}
64. // buildSrc/src/main/java/convention.android.compose.gradle.kts
android {
// Expression 'android' cannot be invoked as a function.
// The function 'invoke()' is not found
buildFeatures.compose = true
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidxComposeCompiler.get()
}
}
dependencies {
...
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
}
하지만 android { } 에서 오류가 발생합니다
Precompiled Script Plugin
65. android { } 블록은 플러그인에 포함되어
있기 때문에 스크립트 영역에서는 인식 할 수
없는 문제가 있습니다.
plugins {
id(“com.android.library”)
}
android {
buildFeatures.compose = true
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidxComposeCompiler.get()
}
}
dependencies {
...
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
}
Precompiled Script Plugin
현재 구현처럼 플러그인을 추가하거나
80. buildSrc vs build-logic
buildSrc build-logic
- 단일 프로젝트
- 자주 변하지 않는 값 (앱 버전)
- 다중 프로젝트 및 레포지터리
- 빌드 로직을 루트 프로젝트에 종속성이
없도록 만들 때
- 빌드 로직을 뗐다 붙였다 하고 싶을 때
81. 정리
❏ build.gradle을 효율적으로 관리하는 방법
❏ Gradle Plugin을 이용하여 빌드 로직에 대한 유지보수를 개선하기
❏ 우리팀의 빌드 로직 컨벤션을 정의해서 사용하기
❏ build-logic을 활용한 다른 프로젝트를 찾아보세요!
https://github.com/droidknights/DroidKnights2023_App/tree/main/build-logic
https://github.com/android/nowinandroid/tree/main/build-logic
https://github.com/chrisbanes/tivi/tree/main/gradle/build-logic
https://github.com/laco-dev/gradle-convention-plugins
82. 정리
❏ 플러그인 만드는 것마저 귀찮다면?
❏ “Re:Android Studio 설정 살펴보기 및 생산성 올리기” 발표 채널고정