BuildKonfig
BuildKonfig copied to clipboard
add an easy way to load flavor from arbitrary properties file
def configureBuildKonfigFlavorFromLocalProperties() {
if (project.gradle.startParameter.projectProperties.containsKey("buildkonfig.flavor")) {
// prefer cli parameter
return
}
if (Files.exists(Paths.get("$project.rootDir/local.properties"))) {
def key = "buildkonfig.flavor"
// load buildkonfig.flavor if exists
def localProperties = new Properties()
localProperties.load(new FileInputStream("$project.rootDir/local.properties"))
if (localProperties.containsKey(key)) {
project.setProperty(key, localProperties.getProperty(key))
}
}
}
personally I wrote this script and use it in my project. But it would be nice if this is baked into BuildKonfig
@yshrsmz hi, thank you for the great tool. I'm quite new to Kotlin in general, where do you put this script so it will get the flavor from local.properties instead of gradle.properties?
@yukai18
Write it in a build.gradle which you apply BuildKonfig plugin, and call it.
apply plugin: 'org.jetbrains.kotlin.multiplatform'
apply plugin: 'com.android.library'
apply plugin: 'com.codingfeline.buildkonfig' // <- BuildKonfig plugin
// call it
configureBuildKonfigFlavorFromLocalProperties()
kotlin {
// kotlin configuration
}
buildkonfig {
// BuildKonfig configuration
}
def configureBuildKonfigFlavorFromLocalProperties() {
// THE method
}
Please note this is written in groovy, so if you are using KTS then you need to re-write this in Kotlin
@yshrsmz thank you for your fast response, I tried converting it to Kotlin, the sync was successful, i run ./gradlew :shared:generateBuildKonfig in the terminal, it was successful as well but no file was generated.
Can you kindly help check if I am doing something wrong?
Here is my code in build.gradle.kts located inside shared module:
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
plugins {
id("com.codingfeline.buildkonfig") version "0.11.0" // BuilKonfig plugin
}
configureBuildKonfigFlavorFromLocalProperties()
kotlin {
// kotlin config
}
buildkonfig {
packageName = "com.example.packageName"
defaultConfigs {
buildConfigField(STRING, "flavor", "prod")
}
defaultConfigs("debug") {
buildConfigField(STRING, "flavor", "debug")
}
}
fun configureBuildKonfigFlavorFromLocalProperties() {
val key = "buildkonfig.flavor"
if (project.gradle.startParameter.projectProperties.containsKey(key)) {
// prefer cli parameter
return
}
// load buildkonfig.flavor if exists
val localProperties = gradleLocalProperties(rootDir)
if (localProperties.containsKey(key)) {
project.setProperty(key, localProperties.getProperty(key))
}
}
in my local.properties file
buildkonfig.flavor="debug"
I had to set below code in my gradle.properties that is located at the project's root directory or else it will generate could not set unkown type 'buildkonfig.flavor' error when running project.setProperty(key, localProperties.getProperty(key))
#BuildKonfig
buildkonfig.flavor=
my local.properties and gradle.properties files are at the project root directory, while the build.gradle.kts is inside shared module, not sure if this will matter but I did confirm that localProperties.containsKey(key) was able to get the key from local.properties
@yukai18 Can you provide a minimal repro?
buildkonfig setup looks fine, but I'd like to know how you configure the entire project(or at least shared module)
Hi @yshrsmz , as I was creating the mini repro project, I realized my mistakes. I'm able to fully integrate the plugin now. Thank you so much! Sorry for the trouble.
Here's what I did, in case others are experiencing the same issue as mine
For the generated file not showing, make sure that you did not exclude the build folder in the project navigator, I forgot if this is excluded by default or I somehow turned it off before. π

For the local.properties file make sure there are no double quotes on the string, I wasn't able to make the flavor work because of this
buildkonfig.flavor=debug
hi @yshrsmz, it seems that I cannot get your script working. I have a root project (ProjectA) which include another project called ProjectB and is a Kotlin multiplatform library project and BuildKonfig was included in ProjectB.
However, no matter what I set in ProjectA's local.properties, it just simply won't recognize and BuildKonfig will just use the default flavor. I can only change the flavor in ProjectA's gradle.properties. Do you have any insight on what should I look for? Thanks! :)
@raymondctc
I'm not sure if ProjectB is a module or a project, but I think you can use rootProject instead
if (Files.exists(Paths.get("$rootProject.rootDir/local.properties"))) {
or simply add ..
if (Files.exists(Paths.get("$project.rootDir/../local.properties"))) {
(I didn't test these snippets, but you can get the idea)
@raymondctc
I'm not sure if
ProjectBis a module or a project, but I think you can userootProjectinsteadif (Files.exists(Paths.get("$rootProject.rootDir/local.properties"))) {or simply add
..if (Files.exists(Paths.get("$project.rootDir/../local.properties"))) {(I didn't test these snippets, but you can get the idea)
Thanks! I finally pulled the code and figured out the reason. My project structure is a bit complicated and looks like this
MyProject/
βββ BuildScriptProject
βΒ Β βββ gradle.properties
βΒ Β βββ gradlew
βββ KMMProjects
βΒ Β βββ ProjectB
βββ ProjectA
βββ build.gradle
By printing project.projectDir and project.rootProject.projectDir on ProjectA's build.gradle, the following result is given:
projectDir=/Users/me/MyProject/ProjectA
rootProjectDir=/Users/me/MyProject/BuildScriptProject
And when I try to print project.projectDirand project.rootProject.projectDir in BuildKonfigPlugin's configure call, the following result is given
projectDir=/Users/me/MyProject/ProjectB
rootProjectDir=/Users/me/MyProject/BuildScriptProject
And I learnt that findProperty will by default check for ProjectB's gradle.properties, if it doesn't exist, it will check for the root project (BuildScriptProject). I tried to do the following in ProjectA:
projectDir.rootProjectDir.setProperty(key, flavor)
It doesn't work. findProperty won't search anything to the root project that's set progrmmatically, it only reads value from gradle.properties of the root project.
Solution
- Create a
gradle.propertiesinProjectB, add keybuildkonfig.flavor= - In
ProjectA'sbuild.gradle
project(':ProjectB').setProperty(key, flavor)
Now it works!
I made a modification to your script too with automatic flavor detection. In case some one is interested:
def configureBuildKonfigFlavorFromLocalProperties() {
def key = "buildkonfig.flavor"
if (project.gradle.startParameter.projectProperties.containsKey(key)) {
// prefer cli parameter
return
}
def targetProject = project(":ProjectB")
def currFlavor = getCurrentFlavor()
targetProject.setProperty(key, currFlavor)
}
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern
if (tskReqStr.contains("assemble") || tskReqStr.contains("install")) {
pattern = Pattern.compile("(assemble|install)(\\w+)(Release|Debug|Jokes)")
} else {
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
}
Matcher matcher = pattern.matcher( tskReqStr )
if( matcher.find() ) {
return matcher.group(3).toLowerCase()
} else {
println "NO MATCH FOUND"
return ""
}
}
Hello! First of all I want to say you thank you for such a great library! I've used snippets from previous posts to fix one flaw of BuildKonfig: it doesn't create, nor uses flavors from Android app module. It would be useful if library used flavor from assemble task and somehow passes it to generateBuildKonfig task. I want to try to write such functionality, either for public usage or for gaining personal expertise. Can you tell me which classes of BuildKonfig should be changed to achieve this, or why it wasn't written or shouldn't be written?
@Dzmitry-Lakisau
Hi,
Flavor(or build variant) in Android is a fairly complex feature.
https://developer.android.com/build/build-variants
You can define multiple flavor dimensions, and I'm not sure how we can distinguish which flavor relates to BuildKonfig's flavor.
Here's a groovy script from my previous project, which extracts Android flavor from the task name and maps it to BuildKonfig's flavor.
My project had a single flavor dimension env, so this snippet is relatively simple. But if you have multiple flavor dimensionsβ¦ Β―\(γ)/Β―
def configureBuildKonfigFlavorFromAndroidTasks() {
if (project.gradle.startParameter.projectProperties.containsKey("buildkonfig.flavor")) {
// prefer cli parameter
println("buildkonfig.flavor=${project.gradle.startParameter.projectProperties["buildkonfig.flavor"]}")
return
}
def pattern = "^:androidApp:(assemble|test|bundle|extractApksFor)(\\w+)(Release|Debug)(|UnitTest)\$"
def runningTasks = project.gradle.startParameter.taskNames
def matchingTask = runningTasks.find { it.matches(pattern) }
if (matchingTask == null) {
return
}
Pattern p = Pattern.compile(pattern)
Matcher m = p.matcher(matchingTask)
if (!m.find()) {
return
}
def flavor = m.group(2)
def buildkonfigFlavor = ""
// map matching android flavor to BuildKonfig's flavor
switch (flavor) {
case "Dev":
buildkonfigFlavor = "dev"
break
case "Stg":
buildkonfigFlavor = "stg"
break
case "Prd":
def buildType = m.group(3)
if (buildType == "Debug") {
buildkonfigFlavor = "prdDebug"
} else {
buildkonfigFlavor = "prd"
}
break
}
println("presentation-base:buildkonfig.flavor=${buildkonfigFlavor}")
project.setProperty("buildkonfig.flavor", buildkonfigFlavor)
}
@yshrsmz Above solution only works best for android targets but for iOS target it is not able to find flavour.
Here is code.
//gradle.startParameter.taskRequests.toString() gives
[DefaultTaskExecutionRequest{args=[:shared:embedAndSignAppleFrameworkForXcode],projectPath='null',rootDir='null'}]
Which does not contain any Release or Debug buildTypes, eventually it is not able to find any flavour. Can you please suggest any solutions or workaround?
Do i need to define any flavours in xcode?
@farhazulmullick-pw you can configure BuildKonfig flavor by passing -P parameter to gradlew command. This should work for Xcode project too.
./gradlew build -Pbuildkonfig.flavor=release
https://github.com/yshrsmz/BuildKonfig#product-flavor
@farhazulmullick-pw you can configure BuildKonfig flavor by passing
-Pparameter to gradlew command. This should work for Xcode project too../gradlew build -Pbuildkonfig.flavor=releasehttps://github.com/yshrsmz/BuildKonfig#product-flavor
So @yshrsmz we cannot dynamically get productFlavours for iOS targets from AndoridTarget?
@farhazulmullick-pw Yes because Android targets have nothing to do with iOS targets
@yshrsmz Ok. So, would I have to define explicitly define targetConfigs() for devDebug, devRelease, stageDebug, stageRelease, prodDebug, prodRelease to make work with iOS?
Because earlier i was doing someting like this and it was working flawless on andorid.
kotlin {
applyDefaultHierarchyTemplate()
buildConfigs = getCurrentBuildConfigs()
project.setProperty(FLAVOR_PROPERTY, buildConfigs.flavour)
....
}
fun getCurrentBuildConfigs(): BuildConfigs {
val taskRequestsStr = gradle.startParameter.taskRequests.toString()
.....
// calulate and fill BuildConfig
return buildConfigs
}
data class BuildConfigs(
val buildType: String = "",
val flavour: String = "",
val buildVarient: String = ""
)
buildkonfig {
packageName = "com.penpencil.parent"
defaultConfigs {
buildConfigField(STRING, "BUILD_TYPE", buildConfigs.buildType, nullable = true)
buildConfigField(STRING, "FLAVOUR", buildConfigs.flavour, nullable = true)
buildConfigField(STRING, "BUILD_VARIENT", buildConfigs.buildVarient, nullable = true)
}
targetConfigs("dev") {
create("android") {}
create("ios") {}
}
targetConfigs("prod") {
create("android") {}
create("ios") {}
}
}
@farhazulmullick-pw
So, would I have to define explicitly define targetConfigs() for devDebug, devRelease, stageDebug, stageRelease, prodDebug, prodRelease to make work with iOS?
From the point of view of the BuildKonfig author, if you need to distinguish between prodDebug and prodRelease, then yes, you need to define these flavors.
But if you really want to go that way(which is not the BuildKonfig-standard), you should be able to simply pass some arbitrary property and do whatever you want.
// this code is executed from Xcode
./gradlew :shared:embedAndSignAppleFrameworkForXcode -Pyourcustomflavor=prdDebug
fun getCurrentBuildConfigs(): BuildConfigs {
val taskRequestsStr = gradle.startParameter.taskRequests.toString()
// check if yourcustomflavor exists
val iOSflavor = project.findProperty("yourcustomflavor")
if (iOSflavor != null) {
// calculate config for IOS
}
.....
// calulate and fill BuildConfig
return buildConfigs
}
@farhazulmullick-pw
So, would I have to define explicitly define targetConfigs() for devDebug, devRelease, stageDebug, stageRelease, prodDebug, prodRelease to make work with iOS?
From the point of view of the BuildKonfig author, if you need to distinguish between prodDebug and prodRelease, then yes, you need to define these flavors.
But if you really want to go that way(which is not the BuildKonfig-standard), you should be able to simply pass some arbitrary property and do whatever you want.
// this code is executed from Xcode ./gradlew :shared:embedAndSignAppleFrameworkForXcode -Pyourcustomflavor=prdDebugfun getCurrentBuildConfigs(): BuildConfigs { val taskRequestsStr = gradle.startParameter.taskRequests.toString() // check if yourcustomflavor exists val iOSflavor = project.findProperty("yourcustomflavor") if (iOSflavor != null) { // calculate config for IOS } ..... // calulate and fill BuildConfig return buildConfigs }
Thanks @yshrsmz Its is a great help.
Do i need to define at BuildPhases
Just update your existing Xcode configuration(should be in BuildPhase though)
Also I did not defined iOSFlavour at gradle.properties
That's depending on your preference. If you want default value, then define it.
Thank You @yshrsmz for your help :)
@yshrsmz We have added these scripts to generate a build for iOS unfortunately its not working, can you help us to know what's wrong in these scripts
cd "$SRCROOT"
./gradlew :shared:embedAndSignAppleFrameworkForXcode -Pios.flavor=prodDebug
"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"
the last one we use for Crashlytics
val iOSFlavor = project.findProperty("ios.flavor")
this value gives us null for iOS but in Android works fine
@sureshmaidaragi1919 Can you provide a relevant part of your build.gradle script?
@sureshmaidaragi1919 Can you provide a relevant part of your build.gradle script?
here it is inside build.gradle(shared)
apply(from = "$rootDir/gradle/scripts/buildVarients.gradle")
var buildConfigs = BuildConfigs()
kotlin {
buildConfigs = getCurrentBuildConfigs()
project.setProperty(FLAVOR_PROPERTY, buildConfigs.flavour) //this will set current build config default as staging
....
.,...
fun getCurrentBuildConfigs(): BuildConfigs {
val iOSFlavor = project.findProperty("ios.flavor")
**//iOSFlavor value is always null, is the issue which shouldn't be**
if (iOSFlavor != null) {
return iOSBuildConfigs(iOSFlavor)
}
val taskRequestsStr = gradle.startParameter.taskRequests.toString()
val pattern: Pattern = if (taskRequestsStr.contains("assemble")) {
Pattern.compile("assemble(\\w+)(Release|Debug)")
} else {
Pattern.compile("bundle(\\w+)(Release|Debug)")
}
val matcher = pattern.matcher(taskRequestsStr)
val buildConfigs = if (matcher.find()) {
val flavour = matcher.group(1).lowercase()
val buildType = matcher.group(2)
BuildConfigs(
flavour = flavour,
buildType = buildType.lowercase(),
buildVarient = "${flavour}$buildType"
)
} else {
println("No android product-flavour found!")
BuildConfigs()
}
return buildConfigs
}
data class BuildConfigs(
val buildType: String = "",
val flavour: String = "",
val buildVarient: String = ""
)
Again, that is a pure Gradle problem and I can't exactly help with it(and I don't recommend that configuration).
But can you check what happens if you use project.gradle.startParameter.projectProperties["ios.flavor"] like I do in this comment?
https://github.com/yshrsmz/BuildKonfig/issues/23#issuecomment-1778373687
or rootProject.findProperty(...) instead?
Tried both still didnt help
project.gradle.startParameter.projectProperties["ios.flavor"]
rootProject.findProperty("ios.flavor")
That's weird π€ Could you create a minimal reproducible project?
I updated ./sample-kts/build.gradle.kts with your snippet and ran a following command.
./gradlew -p sample-kts compileKotlinIosArm64 -Pios.flavor=test
and it successfully found ios.flavor property.
So it should be something to do with your gradle configuration or xcode-specific tasks such as embedAndSignAppleFrameworkForXcode