์๋ก ์์ํ ํ๋ก์ ํธ์์๋ ๋ฉํฐ๋ชจ๋์ ์ฅ์ ์ ๋ ๊ทน๋ํ ํ๊ธฐ ์ํด์ feature ๋ณ๋ก ๋ชจ๋์ ๋ชจ๋ ๋๋๊ณ ์๋ค. core๋ ํฉ์ณ์ ์ฐ๋๊ฑธ data, network, ui์ ํํ๋ก ๋ค ๋ถ๋ฆฌํด์ ์ฌ์ฉ ์ค์ด๋ค. ์ด๋ ๋ค๋ณด๋ ๋งค๋ฒ build.gradle.kts ํ์ผ์ ๋๊ฐ์ ์ฝ๋๋ค์ ๋ฃ์ด์ค์ผ ํ๋๋ฐ, ์ด ๋ถ๋ถ์ด ๋๋ฌด ๋ถํธํด์ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๋ ์ค build-logic์ด๋ผ๋ ๊ฒ์ ๋ฐ๊ฒฌํ๋ค.
version-catalog์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ ๋๊ตฌ๋ก ๋น๋์ ํ์ํ ๊ณตํต ๋ก์ง๋ค์ ๋ฐ๋ก ๋นผ๋ด์ด ๊ด๋ฆฌํ ์ ์๋ ๋๊ตฌ๋ก, ์ง๊ธ์ ํด๋ณด์ง ์์์ง๋ง debug, release ๋ชจ๋๋ฅผ ๋๋ ์ ๋น๋ ๋ด์ฉ์ ๋ถ๋ฆฌํ ์ ์๋ ๊ธฐ๋ฅ๋ ๊ฐ๊ณ ์๋ค๊ณ ํ๋ค.
๋๋ Now in Android ๋ฅผ ๊ต๋ณด์ฌ๋ก build-logic์ ๊ตฌํํ๋ค.
ํด๋ ๊ตฌ์กฐ๋ค. build-logic ์์ฒด๋ ๋ฐ๋ก build.gradle.kts๋ฅผ ๊ฐ์ง ์๊ณ convention ๋ชจ๋์์ ํด๋น ํ์ผ์ ๊ด๋ฆฌํ๋ค. build-logic์๋ `settings.gradle.kts`๋ฅผ ์์ฑํด์ ์ด๊ฑธ buildํ ๋ ๊ฐ์ ธ๊ฐ๋๋ก ์ธํ
ํ๋ค.
build-logic์ ๋ง๋ค๊ณ , `settings.gradle.kts` ๋ฅผ ์๋์ฒ๋ผ ์์ฑํ์.
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "build-logic"
include(":convention")
build-logic ์์ฒด๋ ๊ทธ๋ฅ directory๋ก ๋ง๋ค๊ณ , convention ๋ชจ๋๋ง ๋ฐ๋ก ๋ง๋ค์ด์ฃผ๋ฉด ๋๋ค. ํ๋ก์ ํธ์ ์๋ ๋ฒ์ ์นดํ๋ก๊ทธ ํ์ผ์ ๊ฐ์ ธ์์ libs๋ก ์ด๋ฆ ๋ถ์ด๋ ์ฝ๋๊ฐ ๋ค์ด์๋ค. ๋ชจ๋์ ๋ง๋๋ ๋๊ตฌ๋ก ๋ง๋ค๋ฉด, Project ์์ค์ `settings.gradle.kts` ํ์ผ์ include ๋์ด์์ํ
๋ฐ, ์ด๊ฑธ ์ญ์ ํด์ค์ผํ๋ค. build-logic๋ชจ๋์ ํ๋ก์ ํธ๊ฐ ๋น๋ํ ํ์์๋ ๋ชจ๋์ด๊ณ , ๋น๋์ ์ฌ์ฉ๋ ๋ชจ๋์ด๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ project ์์ค gradle์ ๋ณด๋ฉด ์๋์ ๊ฐ๋ค.
pluginManagement {
includeBuild("build-logic")
... ์๋ต
}
rootProject.name = "DRTAA"
include(":app")
// ...
build-logic์ build๋ ์ฌ์ฉํ๊ธฐ ์ํด `includeBuild`๋ก ๊ฐ์ธ์ค์ผํ๋ค.
๋ฉํฐ๋ชจ๋์ ํจ์จ์ ์ฌ๋ ค์ฃผ๊ธฐ ์ํด `gradle.properties`๋ ์ถ๊ฐํด์ค๋ค.
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
์์๋๋ก ๋ณด์.
๋ณ๋ ฌ build๋ฅผ ํ์ฑํ ํ๊ณ , ๋น๋ ์บ์๋ฅผ ์ฌ์ฉํ๊ฒ ๋ง๋ค์ด์ ์ด์ ๋น๋ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํด ๋ณ๊ฒฝ์ฌํญ์ด ์๋ค๋ฉด ๋์ฑ ๋น ๋ฅธ ๋น๋๊ฐ ๊ฐ๋ฅํ๋ค. configureondemand๋ฅผ true๋ก ํ๋ฉด ์ ์ฒด gradle ๋น๋๊ฐ ์๋๋ผ ํ์ฌ ํ์คํฌ์ ํ์ํ ๊ฒ๋ง ๋น๋ํ๋ค๊ณ ํ๋ค.
์ด์ ๋ด๊ฐ ์ฝ์ง์ ๊ฐ์ฅ ๋ง์ดํ convention ๋ชจ๋์ ๊ตฌํํด๋ณด์.
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
`kotlin-dsl`
}
group = "com.drtaa.convention"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
}
}
dependencies {
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
}
tasks {
validatePlugins {
enableStricterValidation = true
failOnWarning = true
}
}
gradlePlugin {
plugins {
register("AndroidHiltPlugin") {
id = "drtaa.plugin.hilt"
implementationClass = "AndroidHiltConventionPlugin"
}
register("AndroidCorePlugin") {
id = "drtaa.plugin.core"
implementationClass = "AndroidCoreConventionPlugin"
}
register("AndroidCommonPlugin") {
id = "drtaa.plugin.common"
implementationClass = "AndroidCommonConventionPlugin"
}
register("AndroidCoreNetworkPlugin") {
id = "drtaa.plugin.network"
implementationClass = "AndroidCoreNetworkConventionPlugin"
}
register("AndroidFeaturePlugin") {
id = "drtaa.plugin.feature"
implementationClass = "AndroidFeatureConventionPlugin"
}
}
}
gradlePlugin ์ค์ฝํ์์ ์๋ ๊ฒ ๋ด๊ฐ ์ปค์คํ
์ผ๋ก ๋ง๋ค ํ๋ฌ๊ทธ์ธ๋ค์ด๋ค. implementationClass๋ ์ค์ ๋ก ๋ด๊ฐ ๋ง๋ class ํ์ผ์ด๊ณ , id๋ก ์ฌ์ฉํ๋ฉด ๋๋ค.
๋๋ AGP 8.5.0์ ์ฌ์ฉํ๊ณ ์์ด์ jvm ํ๊ฒ์ 17 ์ด์์ผ๋ก ํด์ผ๋๋ค. ์ด์ compileOnly์์ ์ฒซ๋ฒ์งธ ์ฝ์ง์ด ๋ฐ์ํ๋ค. ๋ด ๋ฒ์ ์นดํ๋ก๊ทธ์๋ ์ ๊ฒ ์๋๋ฐ, ์ด๋ ์์๋ฅผ ๋ณด๋๋ผ๋ ์ ๊ฑธ ์ฌ์ฉํ๊ณ ์๋ค.
์นจ์ฐฉํ๊ฒ ์๋ ์ฝ๋๋ฅผ toml์ ์ถ๊ฐํด์ฃผ์. gradle ๋๊ตฌ์ ์ฌ์ฉ๋๋ ํ๋ฌ๊ทธ์ธ ๊ฐ์๋ฐ ๊ธฐ๋ณธ toml ๊ตฌ์ฑ์๋ ๋น ์ ธ์๊ธฐ ๋๋ฌธ์ ์ถ๊ฐํด์ค์ผํ๋ค,
[libraries]
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" }
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
์ฌ๊ธฐ๊น์ง ๋ง์ณค์ผ๋ฉด, ์ด์ ์์
ํ ๊ณต๊ฐ์ `build-logic/convention/src/main/kotlin` ์ด๋ค.
๋ค๋ฅธ ์์ ๋ฅผ ๋ณด๋ฉด Application(gradle ํ์ผ์ android ์ค์ฝํ)๊น์ง build-logic์ ๋ฃ๊ณ ์์๋๋ฐ ์ผ๋จ ๋๋ dependency ๋ฐ๋ณต์ ๋ง๋๊ฒ ๋ชฉ์ ์ด๋ผ์ ํด๋น ๋ถ๋ถ์ ๋๊ธฐ๊ฒ ๋ค.
๊ธฐ๋ณธ dependency๋ง ์ผ๋จ ๋ชจ์๋ ํ์ผ ๋จผ์ ๋ณด์.
internal class AndroidCommonConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
dependencies {
add("implementation", libs.findLibrary("androidx.core.ktx").get())
add("implementation", libs.findLibrary("androidx.appcompat").get())
add("implementation", libs.findLibrary("material").get())
add("implementation", libs.findLibrary("junit").get())
add("androidTestImplementation", libs.findLibrary("androidx.junit").get())
add("androidTestImplementation", libs.findLibrary("androidx.espresso.core").get())
add("implementation", libs.findLibrary("timber").get())
}
}
}
}
์ด ๋ชจ๋ ๋ฐ์์ ์ฌ์ฉํ์ง ์์ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ internal๋ก ์ ์ธํ์ฌ ์ธ๋ถ๋ก ๋ถํฐ ๊ณต๊ฐ๋ฅผ ๋ง๋๋ค. ๊ทธ๋ฆฌ๊ณ dependency ์ค์ฝํ์ ๋ค์ด๊ฐ ๊ฐ implementation ๊ตฌ๋ฌธ์ ์ถ๊ฐํด์ค๋ค. ์ฌ๊ธฐ์ ์์ฃผ ์ค์ํ ์ ์ด ์๋ค. ๋ฒ์ ์นดํ๋ก๊ทธ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๋ณด๋ฅผ ๊ธ์ด์ฌ ๋ ์ฌ์ฉํ๋ ์ `libs`๊ฐ build-logic์ settings.gradle.kts์ ์๋ `create("libs")`์ผ๊น? ์๋๋ค.
create("libs")๋ libs๋ผ๋ ์ด๋ฆ์ ๋ถ์ฌ์ ์๋ ๋ฃจํธ ์์ค ๋ฒ์ ์นดํ๋ก๊ทธ ๋ด์ฉ์ ์ฌ์ฉํด ์ ๋ฒ์ ์นดํ๋ก๊ทธ๋ฅผ ์์ฑํ๋ฉฐ settings.gradle.kts ํ์ผ์์ ์ฌ์ฉํ ์ ์๊ฒ ํ๋ ๊ฒ์ด๊ณ `val Project.libs get()` ๋ ์์์ ์ ์ํ libs ๋ฒ์ ์นดํ๋ก๊ทธ์ ์ ๊ทผํ ์ ์๋๋ก ํ ์ฝ๋๋ค. ๋น๋ ์คํฌ๋ฆฝํธ๋ ํ๋ฌ๊ทธ์ธ ์ฝ๋ ๋ด์์ ์ฌ์ฉํ๋ ค๋ฉด ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํด์ผ๋๋ค.
์ด๊ฑฐ ๋๋ฌธ์ 2์๊ฐ์ ๋ ๋ ธ๋ค.
์ libs๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด Project์ ํ์ฅ ํ๋กํผํฐ๋ฅผ ๋ง๋ค์ด ์ฃผ๊ฒ ๋ค.
val Project.libs
get(): VersionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
์ด๊ฑธ ์ฌ์ฉํ๋ฉด libs์ ์ ๊ทผ ๊ฐ๋ฅํ๋ค. ์ด์ hilt๋ ํ๋ฒ ์คํฌ๋ฆฝํธ ํ ํด๋ณด์.
import extenstion.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
internal class AndroidHiltConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager){
apply("com.google.dagger.hilt.android")
apply("org.jetbrains.kotlin.kapt")
}
dependencies {
add("kapt", libs.findLibrary("hilt.compiler").get())
add("kapt", libs.findLibrary("androidx.hilt.compiler").get())
add("implementation", libs.findLibrary("hilt.android").get())
}
}
}
}
pluginManager๋ก ์ง์ ํด์ค์ผ build.gradle.kts์ plugin ์ค์ฝํ์ ๋ค์ด๊ฐ๋ค. ์ฃผ์ํ ์ ์ libs.findPlugin("").get()์ผ๋ก ์ ๊ทผํ๋ฉด ๋น๋๊ฐ ์๋๋ค๋ ์ ์ด๋ค. apply๋ ํ๋ฌ๊ทธ์ธ id๋ฅผ ๋ฐ์์ผ๋๋๋ฐ get์ผ๋ก ์ ๊ทผํ๋ฉด `Provider<PluginDependency>`๋ฅผ ์ ๊ณตํด์ ๋น๋์ ์คํจํ๋ค.
libs๋ฅผ ํ์ฉํ๊ณ ์ถ๋ค๋ฉด ์๋์ฒ๋ผ ๋ฐ๊ฟ์ผํ๋ค. ์ฒซ๋ฒ์งธ get์ผ๋ก Provider๋ฅผ ๋จผ์ ๊ฐ์ ธ์ค๊ณ , ๋๋ฒ์งธ get์ผ๋ก ์ค์ ํ๋ฌ๊ทธ์ธ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์จ ๋ค, ๊ทธ๊ฒ์ ๋ํ id๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ด๋ค.
import extenstion.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
internal class AndroidHiltConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager){
apply(libs.findPlugin("hilt.gradle.plugin").get().get().pluginId)
apply(libs.findPlugin("kotlin.kapt").get().get().pluginId)
}
dependencies {
add("kapt", libs.findLibrary("hilt.compiler").get())
add("kapt", libs.findLibrary("androidx.hilt.compiler").get())
add("implementation", libs.findLibrary("hilt.android").get())
}
}
}
}
dependency๋ implement๋ kapt ๊ฐ์ configure method๋ก ๊ฐ์ฒด ๋์ด๋ค ์ฐ๋ ๋ฐ๋ฉด plugin์ ๋ฐ๋ก ์ฌ์ฉํ๋ ์ ๋๋ฌธ์ ์ด๋ฐ ์ฐจ์ด๊ฐ ๋ฐ์ํ๋ค.
์ด๋ ๊ฒ ๋ง๋ ํ๋ฌ๊ทธ์ธ๋ค์ ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ์ ์ ์ํ ๋๋ ์ฌ์ฌ์ฉ ํ ์ ์๋ค.
internal class AndroidFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("drtaa.plugin.common")
apply("drtaa.plugin.hilt")
apply(libs.findPlugin("navigation.safe.args").get().get().pluginId)
}
dependencies {
add("implementation", project(":core-data"))
add("implementation", project(":core-ui"))
add("implementation", project(":core-model"))
add("implementation", libs.findLibrary("navigation.ui.ktx").get())
add("implementation", libs.findLibrary("navigation.fragment.ktx").get())
add("implementation", libs.findLibrary("androidx.hilt.navigation.fragment").get())
add("implementation", libs.findLibrary("datastore.preferences").get())
add("implementation", libs.findLibrary("glide").get())
add("implementation", libs.findLibrary("lifecycle.runtime.ktx").get())
add("implementation", libs.findLibrary("lifecycle.extensions").get())
add("implementation", libs.findLibrary("coroutines.android").get())
add("implementation", libs.findLibrary("coroutines.core").get())
}
}
}
}
๊ทธ๋ผ ์ผ๋ง๋ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ๊ฐ ์ค์์๊น?
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.hilt.gradle.plugin)
alias(libs.plugins.kotlin.kapt)
alias(libs.plugins.navigation.safe.args)
}
...
dependencies {
// modules
implementation(project(":core-data"))
implementation(project(":core-ui"))
implementation(project(":core-model"))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation(libs.bundles.feature.default)
kapt(libs.hilt.compiler)
kapt(libs.androidx.hilt.compiler)
}
์ด๋ฌ๋ ์ฝ๋๊ฐ ์ด๋ ๊ฒ ๋ฐ๋์๋ค.
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.kotlin.android)
id("drtaa.plugin.feature")
}
...
dependencies {
}
๋์์ด ๋๋ค๋ฉด ๋๊ธ์ด๋ ๊ณต๊ฐ ๋ฒํผ ํ ๋ฒ์ฉ ๋๋ฅด๊ณ ๊ฐ์ฃผ์ธ์!