본문 바로가기

안드로이드/멀티 모듈

[안드로이드 멀티 모듈] 5. BuildTypes 컨벤션 플러그인에 적용하기


지난 포스팅에서는 AndroidApplicationConventionPlugin을 만들었습니다. 하지만 buildTypes, buildFeatures, composeOptions 등의 코드를 컨벤션 플러그인에 추가하지 않았는데, 이번 포스팅에서는 buildTypes를 컨벤션 플러그인에서 공통으로 사용할 수 있도록 적용하겠습니다.

해당 부분을 컨벤션 플러그인으로 분리하고, build.gradle.kts에서는 제거될 예정
해당 부분을 컨벤션 플러그인으로 분리하고, build.gradle.kts에서는 제거될 예정

1. Application을 위한 BuildTypes 유틸함수 만들기

buildTypes를 컨벤션 플러그인에서 공통으로 사용하기 위해서 build-logic:convention 모듈에 BuildTypes 유틸함수를 만들겠습니다. 이전과 같은 패턴으로 Project의 확장함수로 구현하겠습니다. 

// BuildTypes.kt
internal fun Project.configureBuildTypes(
    commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
    commonExtension.run {
        buildFeatures {
            buildConfig = true
        }

        val apiKey = gradleLocalProperties(rootDir, providers).getProperty("API_KEY")
        
        extensions.configure<ApplicationExtension> {
            buildTypes {
                debug {
                    // TODO: configure debug build type
                }
                create("staging") {
                    // TODO: configure staging build type
                }
                release {
                    // TODO: configure release build type
                }
            }
        }
    }
}


컨벤션 플러그인의 유틸 함수로 사용하기 위해서 commonExtension을 인자로 받아서 구현했습니다.
API KEY처럼 보안이 중요한 정보localProperties에 입력하기 때문에 gradleLocalProperties 함수를 통해 값을 읽어오는 모습을 확인할 수 있습니다.

이제 debug, staging, release의 build type을 구현하기 위한 함수를 만들겠습니다.

2. Build Variant를 위한 Build Type 구성하기

앱을 개발하다 보면 여러 환경에 따라 앱을 다르게 구성해야 할 때가 있는데 테스트용 앱일 수도 있고, 베타버전의 앱일 수도 있습니다. 안드로이드에서는 이러한 다양한 환경에서의 빌드를 하기 위해 Build Variant를 지원합니다. 본 포스팅에서는 debug, staging, release 3가지를 만들겠습니다.

Build Variant를 공통으로 관리하기 위해 유틸함수를 구현한 모습입니다.

release build type에서는 코드 난독화를 위한 설정도 추가했습니다.

// BuildTypes.kt
private fun BuildType.configureDebugBuildType(apiKey: String) {
    buildConfigField("String", "API_KEY", "\"$apiKey\"")
    buildConfigField("String", "BASE_URL", "\"DEBUG_API_URL\"")
}

private fun BuildType.configureStagingBuildType(apiKey: String) {
    buildConfigField("String", "API_KEY", "\"$apiKey\"")
    buildConfigField("String", "BASE_URL", "\"STAGING_API_URL\"")
}

private fun BuildType.configureReleaseBuildType(
    commonExtension: CommonExtension<*, *, *, *, *, *>,
    apiKey: String
) {
    buildConfigField("String", "API_KEY", "\"$apiKey\"")
    buildConfigField("String", "BASE_URL", "\"RELEASE_API_URL\"")


    isMinifyEnabled = true
    proguardFiles(
        commonExtension.getDefaultProguardFile("proguard-android-optimize.txt"),
        "proguard-rules.pro"
    )
}
// BuildTypes.kt
internal fun Project.configureBuildTypes(
    commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
    commonExtension.run {
        buildFeatures {
            buildConfig = true
        }

        val apiKey = gradleLocalProperties(rootDir, providers).getProperty("API_KEY")

        extensions.configure<ApplicationExtension> {
            buildTypes {
                debug {
                    configureDebugBuildType(apiKey)
                }
                create("staging") {
                    configureStagingBuildType(apiKey)
                }
                release {
                    configureReleaseBuildType(commonExtension, apiKey)
                }
            }
        }
    }
}

3. Android Library 모듈을 위한 BuildTypes 적용하기

1, 2번 과정은 Application 모듈을 위한 BuildType 적용 방법이었습니다. 안드로이드 프로젝트에서는 designsystem, data 등의 Android Library 모듈도 많이 사용하므로 Application뿐만 아니라 Android Library의 BuildType도 함께 관리해야 합니다. 이를 위한 enum class를 만들겠습니다.

// ExtensionType.kt
enum class ExtensionType {
    APPLICATION,
    LIBRARY
}


그 후, 1, 2번 과정에서 만든 configureBuildTypes 유틸함수를 수정하겠습니다.

// BuildTypes.kt
internal fun Project.configureBuildTypes(
    commonExtension: CommonExtension<*, *, *, *, *, *>,
    extensionType: ExtensionType
) {
    commonExtension.run {
        buildFeatures {
            buildConfig = true
        }

        val apiKey = gradleLocalProperties(rootDir, providers).getProperty("API_KEY")
        when (extensionType) {
            ExtensionType.APPLICATION -> {
                extensions.configure<ApplicationExtension> {
                    buildTypes {
                        debug {
                            configureDebugBuildType(apiKey)
                        }
                        create("staging") {
                            configureStagingBuildType(apiKey)
                        }
                        release {
                            configureReleaseBuildType(commonExtension, apiKey)
                        }
                    }
                }
            }
            ExtensionType.LIBRARY -> { 
                extensions.configure<LibraryExtension> {
                    buildTypes {
                        debug {
                            configureDebugBuildType(apiKey)
                        }
                        create("staging") {
                            configureStagingBuildType(apiKey)
                        }
                        release {
                            configureReleaseBuildType(commonExtension, apiKey)
                        }
                    }
                }
            }
        }
    }
}


configureBuildTypes() 함수의 인자로 ExtensionType을 넘겨받도록 구현했습니다. 그리고 when 조건문을 통해 Application과 Android Library 모듈의 분기처리를 해주었고, 각각 다른 Extension을 불러와서 buildTypes를 구성해 주었습니다. 이렇게 Application과 Android Library 모듈의 buildTypes를 한 곳에서 구성할 수 있습니다.

4. AndroidApplicationConventionPlugin에 적용하기

BuildTypes를 관리하는 유틸 함수를 만들었으니 이전 포스팅에서 만든 AndroidApplicationConventionPlugin에 적용하겠습니다.

// AndroidApplicationConventionPlugin.kt
class AndroidApplicationConventionPlugin: Plugin<Project> {
    override fun apply(target: Project) {
        target.run {
            pluginManager.run {
                apply("com.android.application")
                apply("org.jetbrains.kotlin.android")
            }

            extensions.configure<ApplicationExtension> {
                defaultConfig {
                    applicationId = libs.findVersion("projectApplicationId").get().toString()
                    targetSdk = libs.findVersion("projectTargetSdkVersion").get().toString().toInt()
                    versionCode = libs.findVersion("projectVersionCode").get().toString().toInt()
                    versionName = libs.findVersion("projectVersionName").get().toString()
                }

                configureKotlinAndroid(this)

                configureBuildTypes(
                    commonExtension = this,
                    extensionType = ExtensionType.APPLICATION
                )
            }
        }
    }
}


extensions.configure 블록 내부에 configureBuildTypes() 함수를 호출하는 모습을 볼 수 있습니다.
Application 모듈이기 때문에 extensionType의 인자로는 ExtensionType.APPLICATION을 전달했습니다.

5. Build.Gradle.kts (:app)에서 buildTypes 제거하기

컨벤션 플러그인에서 buildTypes를 관리하도록 구현했기 때문에 build.gradle.kts (:app)에서는 buildTypes를 제거하겠습니다. 추후 생성되는 모듈의 build.gradle.kts 파일에서도 buildTypes를 제거할 수 있게 됐습니다.

build.gradle.kts에서 buildTypes가 제거된 모습
build.gradle.kts에서 buildTypes가 제거된 모습

6. 정리

환경에 따라 안드로이드 앱을 다르게 구성해야 할 때 유용하게 사용되는 Build Variant 부분을 컨벤션 모듈의 유틸함수로 만들고, 공통으로 관리할 수 있게 구현했습니다.

Build Variant 모습
Build Variant 모습


다음 포스팅에서는 Compose 옵션과 관련된 컨벤션 플러그인을 만들어서 buildFeatures와 composeOptions를 공통으로 관리할 수 있게 구현할 예정입니다.

이번 포스팅의 결과물은 아래 Github Repository의 5-SettingUpBuildTypes 브랜치를 확인하시면 됩니다.

GitHub - taein8935/multi-module-template-aos

Contribute to taein8935/multi-module-template-aos development by creating an account on GitHub.

github.com