안드로이드 build.gradle.kts에서 Flavor 나눠진 경우 KSP 빌드 폴더를 flavor 별 sourceSets 설정 방법

본문

Android app 배포 시 다양한 형태의 빌드를 위해 Flavor를 제공하고 있다.

이 문서에 빌드 변형 구성을 통한 설명이 잘 나와있다. Gradle뿐만 아니라 kotlin kts도 가이드하고 있다.

이 글에서는 Kotlin Symbol Processing을 활용하여 자동으로 생성된 build 결과물의 경로를 flavor 별로 어떤 식으로 추가하는 게 좋을지 소개합니다.


이 글에서 알아볼 내용

  • Flavor 별 sourceSets 구성 방법
  • debug/release와 flavor 조합
  • 이 글에서는 KSP 사용 방법에 대해서는 설명하지 않습니다.
  • KSP에서 만들어진 결과물과 flavor에 따른 폴더 지정 방법을 설명합니다.


Flavor와 buildTypes

Flavor를 나눌 때는 크게 flavor 이름 + debug 또는 flavor 이름 + release로 나눌 수 있습니다.

모두 명시할 필요는 없고, buildTypesproductFlavors를 이용해 나누게 됩니다.

android {
    ...
    buildTypes {              // 1
        getByName("debug"){...}
        getByName("release"){...}
    }

    // Specifies one flavor dimension.
    flavorDimensions += "version"
    productFlavors {          // 2
        create("demo") {
            dimension = "version"
            applicationIdSuffix = ".demo"
        }
        create("full") {
            dimension = "version"
            applicationIdSuffix = ".full"
        }
    }
}
  1. debug와 release를 정의합니다. 보통 기본 프로젝트 생성 시 함께 나오는데 여기서는
  • debug
  • release
  1. productFlavors는 build 해야 할 종류를 나누는 작업을 할 수 있는데, 여기서는
  • demo
  • full

buildTypes과 productFlavors의 조합은?

위와 같이 조합하면 총 4개가 노출됩니다.

  • Debug 빌드 2개
    • demoDebug
    • fullDebug
  • Release 빌드 2개
    • demoRelease
    • fullRelease

와 같이 만들어집니다.

image_01

demoDebug와 demoRelease로 빌드 하면 기본 package 이름 뒤에 .demo가 합쳐져 나오게 됩니다.

기본 package : tech.thdev.app
demo 빌드 시 : tech.thdev.app.demo

flavor를 구분하는 방법은 매우 간단합니다. 각각의 추가 설정해야 할 부분들은 문서를 참고해야 하겠지만 기본 flavor 설정은 이와 같습니다.


sourceSets 정의

Kotlin Symbol Processing API를 활용하여 빌드 한 결과물을 flavor마다 추가하는 방법을 소개합니다.

안드로이드에서 KSP를 통해 빌드 된 결과물을 debug와 release 폴더를 바라보도록 설정해 줘야 하는데, 아래와 같이 추가할 수 있다고 설명되어 있는 글들이 있습니다.

android {
    buildTypes {
        getByName("debug") {
            sourceSets {
                getByName("main") {
                    kotlin.srcDir("build/generated/ksp/debug/kotlin")
                }
            }
        }
        getByName("release") {
            sourceSets {
                getByName("main") {
                    kotlin.srcDir("build/generated/ksp/release/kotlin")
                }
            }
        }
    }
}

하지만 이 코드는 gradle sync를 다시 해보면 아래와 같은 결과를 볼 수 있습니다.

debug 추가 후
source=[src/main/java, src/main/kotlin, build/generated/ksp/debug/kotlin]

release 추가 후
source=[src/main/java, src/main/kotlin, build/generated/ksp/debug/kotlin, build/generated/ksp/release/kotlin]

결국 debug를 빌드 했다가 flavor를 release로 변경한다면 release에서도 debug와 release 폴더 모두를 바라보게 되고, 거꾸로 바꿔도 결과물이 존재하니 둘다 바라보게 됩니다.

image_02

이와 함께 아래와 같은 오류가 발생합니다.

KSPOutput.kt: (3, 8): Redeclaration: KSPOutput

결국 위와 같은 코드로는 작업 중 Build vaiants 이동이 잦을 경우 매번 clean build와 함께 rebuild 해야 정상 동작함을 알 수 있습니다.


flavor 별 sourceSets 처리하기

flavor를 구분하고 난 다음엔 더 많은 폴더를 바라보아야 하는데 위에서 나눈 demo, full을 기준으로 한다면 KSP도 자동으로 최대 4개의 디렉토리 경로가 생기게 됩니다.

당연히 자동으로 만들어진 코드이고, 4개를 모두 바라볼 수 있으므로 모두 충돌 나서 빌드가 불가능해지게 됩니다.

  • build/generated/ksp/demoDebug/kotlin
  • build/generated/ksp/demoRelease/kotlin
  • build/generated/ksp/fullDebug/kotlin
  • build/generated/ksp/fullRelease/kotlin

flavor 이동 및 재빌드 시에 위와 같이 최대 4개의 경로가 새로 생기고, 동일한 파일도 생성됩니다.

이를 flavor 설정에 따라 하나의 폴더만 바라보도록 source 경로 설정이 필요합니다.

buildTypes과 productFlavors를 설정한 build.gradle.kts에 아래와 같이 추가합니다.

// demo에 대한 flavor 설정
sourceSets.create("demoDebug") {
    kotlin.srcDir("build/generated/ksp/demoDebug/kotlin")
    println("kotlin $kotlin")
}
sourceSets.create("demoRelease") {
    kotlin.srcDir("build/generated/ksp/demoRelease/kotlin")
    println("kotlin $kotlin")
}

// full에 대한 flavor 경로 설정
sourceSets.create("fullDebug") {
    kotlin.srcDir("build/generated/ksp/fullDebug/kotlin")
    println("kotlin $kotlin")
}
sourceSets.create("fullRelease") {
    kotlin.srcDir("build/generated/ksp/fullRelease/kotlin")
    println("kotlin $kotlin")
}

이와 같이 적용하면 현재 선택한 flavor 기준으로 build/generated/ksp 폴더도 main 폴더가 source code 활성화됨을 볼 수 있습니다.

image_03

위에서 buildTypes과 함게 findName()을 통해 sourceSets 한 것과 비교해 println 결과물을 확인해 보면 아래와 같습니다.

// demoDebug 인 경우
source=[src/demoDebug/java, src/demoDebug/kotlin, build/generated/ksp/demoDebug/kotlin]
// demoRelease 인 경우
source=[src/demoRelease/java, src/demoRelease/kotlin, build/generated/ksp/demoRelease/kotlin]
// fullDebug 인 경우
source=[src/fullDebug/java, src/fullDebug/kotlin, build/generated/ksp/fullDebug/kotlin]
// fullRelease 인 경우
source=[src/fullRelease/java, src/fullRelease/kotlin, build/generated/ksp/fullRelease/kotlin]


build.gradle.kts flavor 쉽게 관리하기?

kts로 이동하면서 kotlin 문법을 활용할 수 있습니다. listOf()를 활용해 flavor와 sourceSets을 좀 더 쉽게 관리할 수 있는데, 내부 설정 내용이 크게 다르지 않을 때 유용합니다.

val flavorList = listOf("demo", "full")
// Specifies one flavor dimension.
flavorDimensions += "deploy"
productFlavors {
    flavorList.forEach {
        create(it) {
            applicationIdSuffix = ".$it"
        }
    }
}

// sourceSets
flavorList.forEach {
    val debug = "${it}Debug"
    sourceSets.create(debug) {
        kotlin.srcDir("build/generated/ksp/$debug/kotlin")
        println("kotlin $kotlin")
    }
    val release = "${it}Release"
    sourceSets.create(release) {
        kotlin.srcDir("build/generated/ksp/$release/kotlin")
        println("kotlin $kotlin")
    }
}

이와 같이 줄일 수 있는데, kotlin property를 활용해 경로의 debug/release만 다른 부분을 한 번 더 수정한다면

val String.kspSourceSet: String
    get() = "build/generated/ksp/$this/kotlin"

와 할 수 있습니다.


마무리

이와 같이 buildTypes과 productFlavors 설정에 따라 KSP에서 바라보는 폴더를 지정하는 방법을 살펴보았습니다.

sourceSets에서 create를 활용한 이유는 sourceSets.all {}을 통해 제공하는 flavor 이름을 확인해 보면 조금 늦은 타이밍에 buildTypes + productFlavors를 합쳐 코드를 생성함을 알 수 있습니다.

결국 딜레이 되어 합성 결과를 알 수 있으니 getByName 대신 create를 활용해 처리하게 됩니다.

KSP는 추후 글을 통해 정리해 보려 합니다.



About Taehwan

안드로이드 개발 12년차, 모바일앱 개발자 권태환입니다. 코드와 아키텍처, 그리고 개발 문화에 관심이 많습니다. GDG Korea Android와 DroidKnights에서 개발자 커뮤니티를 함께 만들어가고 있습니다.

Comments

🔍 검색

검색어를 입력하면
관련 글을 찾아드려요