안드로이드 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

My name is Taehwan Kwon. I have developed Android for 6 years and blog has been active for eight years.

Comments