0. Convention Plugin
멀티 모듈을 적용해나가고 있는 우리팀의 프로젝트 특성상, 모듈마다 공통적으로 작성해야하는 설정들이 있다. build types 이나 compile options 설정 등 보통 동일하게 설정들이 있는데 각 모듈을 생성할 때마다 똑같이 작성해야하는 수고로움이 있다.
Convention Plugin은 주로 buildSrc 디렉터리나 별도의 Gradle 플러그인 모듈에서 정의되며, 모듈마다 동일한 설정을 반복하지 않아도 되도록 도와준다. 즉, Gradle에서 반복적인 설정을 캡슐화하여 프로젝트의 공통 규칙을 관리하는 방법이다.
이 블로그글은 적용 방법보다 Convention Plugin을 적용하면서 코드적인 이해를 돕고자 작성했다.
1. Gradle Kotlin DSL 설정 파일
Example
build-logic/convention/build.gradle.kts
build-logic에서 직접 Convention Plugin을 작성하는 대신, convention 모듈을 하위에 두고 그 안에 Convention Plugin을 작성하는 이유는 무엇일까?
build-logic 모듈은 Gradle 빌드 로직과 설정을 관리하는 컨테이너 역할을 한다. 이곳에서 settings.gradle.kts를 통해 프로젝트 설정 및 모듈 포함을 관리하고, convention 모듈을 포함시켜 그 안에 실제 빌드 로직을 처리하게 하는 구조가 효율적이기 때문이다. build-logic 하나에 모든 빌드 설정을 작성하면 모듈이 커지고, 각 빌드 로직(e.g. 플러그인, 의존성, 빌드 최적화 등)이 섞여서 관리하기 어려워질 수 있다. convention 모듈은 특정 빌드 로직이나 플러그인을 관리하기 위한 독립적인 모듈이다. Convention Plugin은 특정 일반적인 빌드 규칙을 정의하는 데 사용된다.
Gradle Convention Plugin을 정의하는 모듈에서 사용되는 Gradle Kotlin DSL 설정 파일
//build.gradle.kts
plugins {
`kotlin-dsl`
}
group = "com.example.buildlogic"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
compileOnly(libs.android.gradle.plugin)
compileOnly(libs.kotlin.gradle.plugin)
}
1. plugins 블록 : Gradle Kotlin DSL 사용선언
plugins {
`kotlin-dsl`
}
- `kotlin-dsl`
- Gradle 빌드 스크립트를 Kotlin으로 작성할 수 있도록 지원하는 플러그인
- build-logic 모듈은 일반적인 프로젝트 모듈이 아니라, Gradle 플러그인 로직을 정의하는 곳이므로, kotlin-dsl이 필요하다.
- Kotlin DSL(build.gradle.kts) 을 사용하려면 kotlin-dsl 플러그인이 필요하다. 적용하지 않는다면 컴파일이 되지 않고 오류가 발생하고, libs.versions.toml 버전 카탈로그 또한 사용할 수 없다.
그렇다면 org.jetbrains.kotlin.android 플러그인을 사용하면 안될까?
Gradle 빌드 스크립트에서도 문제없이 사용할 수 있다. jetbrains의 코틀린 플러그인도 Kotlin 컴파일러를 설정하고, 코틀린 관련 Gradle 빌드 스크립트 (build.gradle.kts)을 작성하는데도 사용할 수 있다. 단, org.jetbrains.kotlin.android 플러그인은 Kotlin 을 Android 프로젝트에서 사용할 수 있도록 설정할 때 사용한다는 목적에서 차이가 있다.
즉, kotlin-dsl은 Gradle 자체를 Kotlin DSL로 작성하는 기능을 제공하고 jetbrains의 kotlin 은 Android 프로젝트에서 Kotlin 코드를 사용할 수 있도록 지원하는 역할을 한다. Convention Plugin인은 Android 프로젝트 전체에 적용는 것이 아니라 Gradle 플러그인 로직을 작성하고 Gradle 빌드 스크립트를 코틀린으로 작성하기 위한 목적이 더 크기때문에 kotlin-dsl 을 적용하는것이 알맞다.
2. group 설정: 모듈의 패키지 네임스페이스
group = "com.example.buildlogic"
- `group`
- Gradle에서 패키지 및 플러그인을 식별하는 네임스페이스 역할
- 보통 패키지 네이밍 규칙을 따른다. 나중에 플러그인을 사용할 때, 이 group 값을 기준으로 참조할 수도 있다.
아래와 같이 gradle plugin을 등록할때 id만 등록하면 되지 않을까?
Gradle 플러그인은 group + id 형식으로 플러그인 아티팩트를 식별하는 데 사용된다. 아래와 같이 등록했을 때, id가 example.android.application 인 플러그인은 com.example.buildlogic 그룹 안에서 정의된다.
- 예를 들어, group이 없으면 다른 Gradle 프로젝트에서 동일한 id를 가진 플러그인이 있을 경우, 플러그인을 식별할 수 없다.
//build.gradle.kts
gradlePlugin {
plugins {
register("androidApplication") {
id = "example.android.application"
implementationClass = "AndroidApplicationConventionPlugin"
}
}
}
만약 플러그인 클래스인 AndroidApplicationConventionPlugin 가 com.example 과 같은 특정 패키지 아래에 위치하면 Gradle 플러그인 로딩과 관련된 빌드 에러가 나타난다.
Unable to load class 'AndroidApplicationConventionPlugin'
AndroidApplicationConventionPlugin
Gradle은 플러그인을 로드하기 위해 id를 사용하여 플러그인을 식별하고, implementationClass 속성에 지정된 경로를 사용하여 해당 클래스를 클래스패스에서 찾는다.
`implementationClass` 로 지정된 경로는 `패키지 경로`를 포함한 `전체 클래스 이름`이어야 한다.
- 예를 들어, implementationClass = "com.example.convention.extensions.AndroidLibraryConventionPlugin"으로 지정한 경우:
- Gradle은 com/example/convention/extensions/AndroidLibraryConventionPlugin.class 경로에서 해당 클래스를 찾으려고 한다.
때문에 implementationClass에 패키지 경로를 포함한 전체 클래스 이름을 포함하거나, 플러그인 클래스 파일을 java 폴더로 위치한다. java 디렉토리를 소스 루트로 설정하고 implementationClass에는 클래스 이름만 포함시킬 수 있는 것이다.
3. java 블록: Java 버전 설정
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
- 위의 Kotlin DSL에서 제공하는 확장함수이며 Java 관련 설정을 쉽게 적용할 수 있도록 도와주는 API이다. Convention Plugin도 결국은 Kotlin과 Java 기반으로 작성된 코드이므로, 사용 가능한 Java 버전을 명시적으로 설정해야 한다.
- `sourceCompatibility`: 코드를 작성할 때 사용할 Java 버전을 지정한다. 즉, 위의 예시에서는 Java 17 문법을 사용할 수 있도록 한다.
- `targetCompatibility`: 컴파일된 바이트코드가 실행될 JVM의 최소 버전을 지정한다. Java 17 이상의 JVM에서 실행 가능하도록 바이트코드를 생성한다. 즉, 컴파일된 클래스 파일(.class)이 Java 17 이상에서 실행 가능하게 된다.
왜 둘을 동일하게 설정할까?
- sourceCompatibility보다 targetCompatibility를 더 낮게 설정하면, 새로운 문법을 사용했는데 구버전 JVM에서 실행하려고 할 때 문제가 발생할 가능성이 있다. 예를 들어 Java 11 문법으로 작성된 코드가 Java 8 용 바이트코드로 컴파일되는데, Java 8에서 실행할 수 없는 Java 11 기능을 사용하면 런타임에서 ClassNotFoundException 등의 오류가 발생할 수 있다.
- targetCompatibility를 높게 설정하면, 굳이 최신 JVM에서만 실행되도록 제한하는 것이라 의미가 없다.
4. dependencies 블록: 필요한 라이브러리 추가
dependencies {
compileOnly(libs.android.gradle.plugin)
compileOnly(libs.kotlin.gradle.plugin)
}
- `compileOnly` : 컴파일할 때만 필요한 의존성을 추가하며 런타임 시에는 포함되지 않는 의존성을 추가한다. 단순히 플러그인 로직을 작성할 때만 참조하도록 설정된다. 즉 위의 라이브러리들은 앱이 실행될 때는 이 라이브러리들을 포함하지 않는다.
그럼 위의 라이브러리들은 어디서 필요한가?
`libs.adnroid.gradle.plugin`인 `Android Gradle Plugin (AGP)`은 Android 프로젝트를 빌드하고 설정하기 위한 핵심 라이브러리이다. Convention Plugin을 만들 때, Android 관련 Gradle API를 활용해야 하는 경우가 많기 때문에 추가된다.
`ApplicationExtensio`n 은 AGP에서 제공하는 확장기능이고, pluginManager.apply 또한 AGP 를 적용해야 사용가능하다. 즉, 이 라이브러리가 없으면 Android 관련 설정을 플러그인에서 정의할 수 없다.
import com.android.build.api.dsl.ApplicationExtension
import com.snacktrend.convention.Const
import com.snacktrend.convention.configureKotlinAndroid
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(project: Project) {
with(project) {
with(pluginManager) {
apply("com.android.application")
apply("org.jetbrains.kotlin.android")
}
extensions.configure<ApplicationExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = Const.TARGET_SDK
}
}
}
}
`libs.kotlin.gradle.plugin`인 `Kotlin Gradle Plugin` 에서 Kotlin을 인식하고 컴파일하는 기능을 제공한다. 플러그인 코드에서 Kotlin을 지원하려면 필요하다.
private fun Project.configureKotlin() {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = Const.JAVA_VERSION.toString()
}
}
}
루트 프로젝트의 settings.gradle.kts 에 includeBuild에 build-logic을 포함시켜준다.
include는 Gradle 프로젝트의 서브모듈을 포함할 때 사용하고, `includeBuild`는 외부 빌드 로직을 포함하는 방식으로 모듈이 아닌 빌드 스크립트를 포함하는데 사용된다. 빌드 로직은 실제로 빌드 프로세스에 영향을 주기 때문에, 이를 독립적인 빌드 스크립트로 취급하고 includeBuild로 참조하여 빌드 프로세스에만 영향을 주게 하는 것이 적합하다.
//root/settings.gradle.kts
pluginManagement {
repositories {
...
includeBuild("build-logic")
}
}
...
include(":app")
'🐸 Android' 카테고리의 다른 글
[Gradle] Android Gradle Plugin과 Gradle 기본 생성 파일 (0) | 2025.02.02 |
---|---|
[Gradle] Java Versions in Android Builds (0) | 2025.01.30 |
FCM Notification 2 - background 에서 푸쉬 알람받기 (0) | 2023.12.12 |
FCM Notification 1 - 안드로이드 13에서 Notification 권한 허가 변경 (0) | 2023.12.12 |
[Android] 렌더링 관점에서 효율적인 ListAdapter (1) | 2023.09.07 |