현재 진행하고 있는 프로젝트에서, 기존 모놀리틱 구조를 다중 모듈 프로젝트로 마이그레이션 하였다. 마이그레이션을 하면서, 종속성을 관리하기 위해 VersionCatalog를, 플러그인을 관리하기 위해 Custom Convention Plugin을 도입하였다. 필자는 다른 레퍼런스를 확인하며 구현하였지만, 제대로 알고 쓰지 않는 것 같았다. 따라서 이번 포스팅을 통해 Custom Convention Plugin을 정리하고, 소개하고자 한다.
Task
컴파일된 코드를 실행가능한 파일로 변환하는 작업을 빌드라고 한다. 안드로이드로 가정하면, 컴파일된 코틀린 파일을 aab, apk와 같은 실행가능한 파일로 만드는 작업을 의미한다.
이러한 작업은 우리가 정의한 Gradle 파일에서 Task 단위로 수행되며, Task는 Release, build, test와 같은 종류가 있다.
따라서 Task는 빌드 시스템에서 실행할 수 있는 작업을 의미한다.
Plugin
이전 우리는 Task에 대해 알아보았다. Task는 빌드 시스템에서 실행할 수 있는 하나의 작업이다. 하지만, 확인할 수 있듯 수행되는 Task의 양이 많다. 이러한 작업을 모두 Gradle 파일에 정의하면 길이가 길어지고, 확인하기도 어렵다.
그렇기 때문에, 개발사들은 하나의 개발을 위한 Task의 묶음으로 Plugin을 제공한다. Plugin을 통해, 효율적이고 가독성이 좋게 Task를 구성할 수 있다.
Custom plugin
필자의 상황처럼, 만약 하나의 모듈에서 많은 플러그인이 사용되고, 이러한 플러그인이 다른 모듈에서도 사용된다면 Custom Plugin을 고려할 수 있다. Custom Plugin은 사용자가 직접 Plugin을 그룹화하여 관리할 수 있는 작업이다. Custom Plugin을 사용하면, 빌드 로직을 재 사용하고, 다른 모듈과 공유할 수 있다.
아래는 Custom Convention Plugin을 적용하지 않은, plugin의 모습이다.
plugins{
alias(libs.plugins.android.application)
alias(libs.plugins.google.services)
alias(libs.plugins.google.firebase.crashlytics)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.kapt)
alias(libs.plugins.ksp)
alias(libs.plugins.dagger.hilt)
alias(libs.plugins.androidx.navigation.safeargs)
}
그럼, 직접 Custom Convetention Plugin을 만들어 보자!
1. build-logic 모듈 세팅하기
custom plugin을 정의할 build-logic 디렉터리를 생성한다. 해당 모듈에 setting.gradle 파일을 생성하고, rootProject 이름과, versionCatalog를 정의한다.
// 1. setting.gradle (in build-logic)
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.version.toml"))
}
}
}
rootProject.name = "build-logic"
include(":convention")
작성한 rootProject 이름을 project 단위의 setting.gradle에 정의하여, 함께 빌드가 될 수 있도록 구성한다.
// setting.gradle (in project)
pluginManagement {
includeBuild("build-logic")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
다음 build-logic의 build.gradle 파일에 아래와 같은 종속성을 추가한다. Custom Plugin을 만들기 위해, org.gradle과 com.android의 파일에 접근하는 상황이 발생하기 때문이다.
// build.gradle (in build-logic/convention)
plugins{
`kotlin-dsl`
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
compileOnly(libs.android.build)
compileOnly(libs.kotlin.gradle)
}
2. Custom Plugin 만들기
Plugin 중 가장 많이 사용되는 AndroidHiltPlugin을 살펴보자.
AndroidHiltPlugin: Plugin<Project>{
override fun apply(target: Project) {
with(target){
pluginManager.apply("com.google.dagger.hilt.android")
pluginManager.apply("com.google.devtools.ksp")
}
}
}
위와 같이, Custom Plugin을 만들기 위해서는, Plugin<Project>를 상속받아 apply 메소드를 통해 구현할 수 있다. 매개 변수 Project 객체를 통해, Project에 Task, Plugins, Properties와 같은 사항을 구현할 수 있다.
Plugin의 경우에는, pluginManager를 통해 직접 플러그인을 추가할 수 있다. 따로 Version Catalog에 정의된 Plugin을 사용할 수 없어, 직접 Plugin을 입력해야 한다.
Custom Plugin에서 Version Catalog를 사용하기 위해서는 다음과 같이, VersionCatalogsExtension를 사용하여 구현할 수 있다.
AndroidHiltPlugin: Plugin<Project> {
override fun apply(target: Project) {
with(target){
...
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
}
}
}
또한, BaseExtension를 사용하면, 다른 빌드 속성들을 정의할 수 있다.
class AndroidPlugin: Plugin<Project> {
override fun apply(target: Project) {
with(target){
with(pluginManager){
apply("com.android.library")
}
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
extensions.getByType<BaseExtension>().apply {
setCompileSdkVersion(libs.findVersion("compileSdk").get().requiredVersion.toInt())
defaultConfig {
minSdk = libs.findVersion("minSdk").get().requiredVersion.toInt()
targetSdk = libs.findVersion("targetSdk").get().requiredVersion.toInt()
}
buildFeatures.apply {
buildConfig = true
}
}
}
}
}
마지막으로, Custom Plugin에 종속성도 추가할 수 있다. build.gradle과 같은 방식으로 dependencies에 추가한다.
class AndroidHiltPlugin: Plugin<Project> {
override fun apply(target: Project) {
with(target){
pluginManager.apply("com.google.dagger.hilt.android")
pluginManager.apply("com.google.devtools.ksp")
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
"implementation"(libs.findLibrary("hilt").get())
"ksp"(libs.findLibrary("hilt.ksp").get())
}
}
}
}
3. 플러그인 생성하기
Custom Plugin을 생성하기 위해서, build-logic의 build.gradle에서 생성할 수 있다.
// build.gradle (in build-logic)
gradlePlugin {
plugins {
create("android-hilt") {
id = "com.healthc.hilt"
implementationClass = "com.healthc.plugin.AndroidHiltPlugin"
}
...
}
}
gradlePlugin에 create를 통해, 방금 만든 Plugin을 생성할 수 있다. create는 id와 implementationClass로 구성되는데, id는 다른 gradle에서 호출할 플러그인의 식별자, implementationClass는 생성할 플러그인의 위치를 입력한다.
4. 플러그인 사용하기
다음과 같이 생성한 플러그인을 적용한다.
// build.gradle (in app module)
plugins {
id("com.healthc.application")
id("com.healthc.hilt")
id("com.healthc.kotlin")
id("com.healthc.google.services")
id("com.healthc.navigation")
}
마치며
이번 Custom Convention Plugin을 공부하며, 직접 플러그인을 만드는 방법도 알게 되었지만, Gradle Task, Plugin도 알게 되었다. 기존 프로젝트를 할 때는 Gradle에 대해 잘 알지 못했고, 당연히 plugin과 task도 알지 못하고 구분도 못해, 사용하지 않는 플러그인도 무작정 그냥 모두 추가해버리곤 했다. 그러나 이번 포스팅을 하며, Gradle에 대해 조금씩 알게되었고, 왜 이 플러그인이 필요한지에 대해 생각을 할 수 있었다. 아직 완벽하게 Gradle에 대해 터득하였다고 생각이 들지 않아, 이후에도 조금씩 공부를 할 예정이다!
참고문헌
https://docs.gradle.org/current/userguide/custom_plugins.html
https://velog.io/@vov3616/Gradle-3.-Custom-Plugin-%EB%A7%8C%EB%93%A4%EA%B8%B0
https://medium.com/@magicbluepenguin/how-to-create-your-first-custom-gradle-plugin-efc1333d4419