개요
멀티 모듈 구조의 프로젝트를 진행하다보면, 모듈들이 점점 많아지면서 각 모듈에서 사용되는 gradle 설정들도 중복해서 작성되기 마련입니다. 이렇게 되면 보일러 플레이트가 증가할 뿐만 아니라 gradle 설정을 공통으로 관리하기 어려워져 유지보수 비용이 크게 늘어나게 될 수도 있습니다. 마치 버전 카탈로그를 이용해 라이브러리 의존성 버전을 전역에서 일관되게 관리했던 것처럼, gradle 설정 또한 이렇게 관리할 수 없을까요? 바로 그 솔루션인 Gradle Convention Plugin에 대해 알아봅시다.
Convention Plugin 만들고 적용하기
대부분의 모듈에 적용이 필요해서 중복으로 설정되고 있는 gradle 설정이 뭐가 있을까요? 아마 가장 떠올리기 쉬운 것은 바로 lint 관련 설정일 것입니다. Kotlin 의 가장 대표적인 lint 툴인 spotless 를 적용하는 상황을 예로 들어봅시다.
1 |
|
우선 app 모듈에 spotless 를 적용해보았습니다. 하지만 아직 남아있는 모듈이 많습니다. 모든 모듈의 gradle 파일에 위 코드를 복붙하지 않기 위해, spotless 를 위한 convention plugin 을 만들어봅시다.
우선 이 convention plugin 이라는 것은 프로젝트 어디에 위치해야 할까요? Android 개발의 바이블인 Now in Android 프로젝트에서는 build-logic
이라는 폴더 안에 convention
이라는 이름의 모듈을 만들어 plugin 들을 관리하고 있습니다. 그리고 이 build-logic
폴더를 빌드에 포함시키기 위해 build-logic/settings.gradle.kts
파일을 작성하고, 루트의 settings.gradle.kts
에서 해당 폴더를 빌드에 포함시켜 줍시다.
1 |
|
1 |
|
build-logic
폴더를 만들었다면 해당 폴더 안에 convention
모듈을 만들어줍시다. convention
모듈의 build.gradle.kts
는 아래와 같습니다.
1 |
|
한 가지 확인해야 할 점은 내가 만드려는 플러그인이 의존하는 gradle plugin 의 의존성을 convention gradle 모듈에 추가해주어야 한다는 점입니다. 위 코드에서 libs.spotlessGradlePlugin
은 com.diffplug.spotless:spotless-plugin-gradle
패키지를 의미합니다. 앞으로 convention plugin 이 추가될수록 필요한 의존성도 많아지기 때문에, 주로 버전 카탈로그의 bundles
기능을 사용하여 gradle 플러그인 의존성을 bundle 로 묶어 한 번에 추가하는 편이 일반적입니다.
자 이제 spotless 적용을 위한 convention plugin 을 만들어 봅시다.
1 |
|
그런데 위 파일을 작성하고 나면, 버전 카탈로그의 libs 객체에 접근할 수 없어 ktlint 버전을 가져오지 못하는 것을 발견하실 수 있습니다. build-logic
패키지 내부에 있는 convention
모듈에서는 버전 카탈로그에 바로 접근할 수 없으므로, 편리하게 convention plugin 을 작성하기 위해선 이를 위한 유틸 확장 함수를 따로 선언해야 합니다.
1 |
|
이와 같이 Project
객체와 VersionCatalog
객체에 확장 함수를 정의하면 convention
모듈에서 보다 편리하게 버전 카탈로그를 이용할 수 있습니다. 이제 위에서 작성했던 SpotlessPlugin
에서 ktlint 버전을 설정하는 부분을 아래와 같이 수정하면 정상적으로 동작하는 것을 확인하실 수 있습니다.
1 |
|
이렇게 만들 플러그인을 다른 모듈에서 사용하기 위해서는 이를 gradlePlugin
에 등록해주어야 합니다. convention
모듈의 build.gradle.kts
파일에 아래와 같이 추가해 줍시다.
1 |
|
여기서 id
필드는 다른 모듈에서 plugin 을 추가할 때의 식별자가 되고, implementationClass
는 실제 플러그인의 구현체 클래스를 의미합니다. 이제 다른 모듈에서 id("example.convention.spotless")
와 같은 방식으로 convention plugin 을 추가할 수 있습니다.
1 |
|
모듈 유형별 의존성 분기 걸기
이번엔 spotless 와 같이 대부분의 모듈에 포함되기 쉬운 Hilt 를 convention plugin 을 이용해 적용해봅시다.
1 |
|
Spotless convention plugin 에서는 보지 못했던 패턴이 보입니다. 바로 모듈 유형별로 의존성을 분기 처리 하는 것입니다. Hilt 를 사용하기 위해 필수적인 ksp
플러그인과 hilt-compiler
의존성은 공통으로 추가하고, 모듈에 걸려있는 plugin 종류에 따라 각기 다른 hilt 관련 의존성을 추가해줍니다. 위 코드의 예시로 kotlin("jvm")
의존성이 걸려있다면 안드로이드 의존성이 없는 순수 JVM 모듈이기 때문에 hilt-core
에 대한 의존성만 추가하고, com.android.library
의존성이 걸려있는 안드로이드 모듈에 경우 이에 필요한 hilt-android
의존성을 추가해줄 수 있습니다.
1 |
|
1 |
|
상위 convention 들을 묶어 하위 convention 만들기
더 효율적으로 사용할 수 있는 방법 중 하나는 특정 gradle 설정을 담는 convention plugin 여러개를 모아서서 또 하나의 convention plugin을 만들어내는 것입니다. 예를 들어 feature 기반 멀티 모듈 구조의 안드로이드 프로젝트에서 어느 한 feature 모듈을 가정해봅시다.
1 |
|
위에서 만든 spotless 와 hilt 외에도 android, compose 관련 설정들 또한 convention plugin 으로 만들어 적용했다고 가정한 모습입니다. 이 정도만 되어도 convention plugin 을 사용하지 않았을 때보다 충분히 코드량이 많이 줄었고 재사용성도 높아졌지만, 더 공통화 시킬 수 있는 건덕지가 보이는 것 같습니다. 위 코드와 같이 android 및 compose 관련 플러그인이나 ui
및 data
와 같은 핵심 core 모듈, 그리고 ViewModel 과 Navigation 에 대한 의존성은 그 어떤 feature 모듈이라도 공통적으로 적용되지 않을까요? 이번엔 이들을 묶어서 feature 모듈을 위한 공통 convention plugin을 만들어봅시다.
1 |
|
이제 새로운 feature 모듈을 만들 때 번거롭게 반복적인 gradle 설정을 할 필요 없이, 아래와 같이 feature convention plugin 을 적용하는 것으로 생산성과 일관성을 동시에 높여줄 수 있습니다.
1 |
|
마치며
이렇게 한 번 개발한 convention plugin 은 특정 프로젝트에서 뿐만 아니라, 다른 프로젝트에서도 충분히 재사용이 가능하다는 이점을 갖고 있습니다. 멀티 모듈 구조의 프로젝트를 개발할 때의 가장 일반적인 문제 중 하나가 바로 코드양이 늘어남으로써 오버헤드가 발생할 수 있다는 점인데, gradle convention plugin 은 이에 대한 강력한 솔루션인 것 같습니다. 만약 프로젝트의 모듈화를 고민 중이시라면 꼭 적용해보시는 것을 추천드립니다.