feat: Integrate KMP Product Flavors v1.0.1#133
feat: Integrate KMP Product Flavors v1.0.1#133therajanmaurya wants to merge 1 commit intoopenMF:devfrom
Conversation
- Add KMPFlavorsConventionPlugin for cross-platform flavor support - Add centralized KmpFlavors.kt configuration (demo/prod) - Apply kmp.flavors plugin in KMPLibraryConventionPlugin and KMPCoreBaseLibraryConventionPlugin - Update libs.versions.toml with kmpProductFlavors = "1.0.1" - Fix BuildConfig package name generation to handle hyphens properly Features enabled: - Multi-dimensional flavor support (demo/prod) - BuildConfig generation per variant - Flavor-specific source sets Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe pull request introduces Kotlin Multiplatform (KMP) Product Flavors support by adding a new Gradle convention plugin that applies and configures cross-platform flavor definitions. This includes a centralized flavor configuration system with DEMO and PROD flavors, automated BuildConfig field generation, and integration into existing KMP convention plugins. Changes
Sequence DiagramsequenceDiagram
participant Gradle as Gradle Plugin System
participant KMPFlavorsPlugin as KMPFlavorsConventionPlugin
participant KmpFlavorExt as KmpFlavorExtension
participant BuildConfig as BuildConfig Generator
Gradle->>KMPFlavorsPlugin: apply(target: Project)
KMPFlavorsPlugin->>KmpFlavorExt: apply KmpFlavorPlugin
KMPFlavorsPlugin->>KmpFlavorExt: configureKmpFlavors()
KmpFlavorExt->>KmpFlavorExt: registerDimensions(CONTENT_TYPE)
KmpFlavorExt->>KmpFlavorExt: registerFlavors(DEMO, PROD)
KmpFlavorExt->>BuildConfig: generateBuildConfig = true
BuildConfig->>BuildConfig: add BASE_URL field
BuildConfig->>BuildConfig: add ANALYTICS_ENABLED field
BuildConfig-->>KMPFlavorsPlugin: complete
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt (3)
88-89:isFlavorActiveis dead code within the build logic.This helper has no call sites in the build configuration code — it is never invoked during build configuration. If it is intended as a public utility for module-level
build.gradle.ktsconsumers, add a KDoc note. Otherwise, remove it to avoid confusion.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt` around lines 88 - 89, The function isFlavorActive is dead/unreferenced; either remove it or document it as public API: if you intend it as an internal helper, delete isFlavorActive from KmpFlavors to remove dead code; if you intend it for consumers, add KDoc to isFlavorActive explaining its contract and visibility (including that it checks Flavor.flavorName against an activeVariant string, case-insensitively) and ensure it is declared public in the API you expect consumers to use.
94-97: Hard-coded API URLs in build logic reduce template reusability.
BASE_URLvalues ("https://demo-api.mifos.org"/"https://api.mifos.org") are embedded in the convention plugin. Since this is a project template, downstream adopters must forkKmpFlavors.ktto change these URLs. Consider externalizing them — e.g., reading fromgradle.propertiesor accepting them as parameters toconfigureKmpFlavors— so the convention plugin remains project-agnostic.Do you want me to propose an implementation that reads
BASE_URLvalues fromgradle.properties(e.g.,kmp.flavors.demo.baseUrl,kmp.flavors.prod.baseUrl) with the current values as defaults?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt` around lines 94 - 97, Replace hard-coded URLs in getBaseUrl by reading configurable properties and/or passing values into configureKmpFlavors: update configureKmpFlavors to accept optional baseUrl parameters (e.g., demoBaseUrl, prodBaseUrl) and have getBaseUrl(flavor: Flavor) return those values; if parameters are not supplied, read from project properties (keys like "kmp.flavors.demo.baseUrl" and "kmp.flavors.prod.baseUrl") with the existing "https://demo-api.mifos.org" and "https://api.mifos.org" as defaults. Ensure references to getBaseUrl and configureKmpFlavors are updated so callers can override via parameters or via gradle.properties without needing to edit KmpFlavors.kt.
139-174: Shadowed nameflavorsinsidethis.flavors { }block is visually confusing.On Line 160,
this.flavors { flavors.forEach { ... } }callsKmpFlavorExtension.flavorsas the receiver, whileflavorsinside the lambda still resolves to the outerList<FlavorConfigData>parameter. Kotlin resolves this correctly, but the identical identifier shadowing a different type in an adjacent scope is a readability hazard. Renaming the parameter (e.g.,flavorDataList) would make intent unambiguous.Additionally,
buildConfigPackage: String?(Line 144) is always supplied as a non-null value from the only call site (inferBuildConfigPackagealways returnsString). The nullable type and the null guard on Line 148 are unnecessary.♻️ Proposed fix
fun configureKmpFlavors( extension: KmpFlavorExtension, dimensions: List<DimensionConfig>, - flavors: List<FlavorConfigData>, + flavorDataList: List<FlavorConfigData>, generateBuildConfig: Boolean = true, - buildConfigPackage: String? = null, + buildConfigPackage: String, ) { extension.apply { this.generateBuildConfig.set(generateBuildConfig) - buildConfigPackage?.let { this.buildConfigPackage.set(it) } + this.buildConfigPackage.set(buildConfigPackage) ... this.flavors { - flavors.forEach { flavorData -> + flavorDataList.forEach { flavorData ->🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt` around lines 139 - 174, Rename the parameter flavors to something unambiguous (e.g., flavorDataList) and update its usages inside configureKmpFlavors to avoid shadowing the extension property this.flavors; specifically change flavors.forEach { flavorData -> ... } to flavorDataList.forEach { flavorData -> ... } inside the this.flavors { ... } block so the receiver and the outer collection aren't confused. Also change the buildConfigPackage parameter on configureKmpFlavors from String? to non-null String and remove the safe-call/let guard; set the extension property directly via this.buildConfigPackage.set(buildConfigPackage) (update the function signature and the single assignment site accordingly).build-logic/convention/src/main/kotlin/KMPLibraryConventionPlugin.kt (1)
25-25: Consider whether all KMP library modules need flavor support.Applying
org.convention.kmp.flavorsunconditionally in bothKMPLibraryConventionPluginandKMPCoreBaseLibraryConventionPluginmeans every KMP library module will receive:
- Flavor-specific source sets (
commonDemo/,commonProd/) whether or not they are used- Per-module BuildConfig generation (with
BASE_URL,ANALYTICS_ENABLED) even for infrastructure modules (e.g., database, analytics, network) that have no flavor-specific behaviorThis increases build configuration overhead uniformly. If only a subset of modules genuinely need flavors (e.g., feature modules), consider making
id("org.convention.kmp.flavors")opt-in per module rather than embedded in the convention plugin.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@build-logic/convention/src/main/kotlin/KMPLibraryConventionPlugin.kt` at line 25, Remove the unconditional apply("org.convention.kmp.flavors") from KMPLibraryConventionPlugin and KMPCoreBaseLibraryConventionPlugin and make flavor support opt-in: either (a) expose a boolean parameter or project property that those plugins check (e.g., project.findProperty("kmpFlavorsEnabled") or an extension flag) and only apply("org.convention.kmp.flavors") when true, or (b) delete the apply call from both plugins and document that modules that need flavors should explicitly id("org.convention.kmp.flavors") in their build script; update any BuildConfig generation logic tied to flavors (BASE_URL, ANALYTICS_ENABLED) to guard on the same flag/extension so infrastructure modules don’t get flavor-specific sources or BuildConfig generated unnecessarily.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@build-logic/convention/build.gradle.kts`:
- Line 36: Add the new implementation dependency to the Dependency Guard
baseline: run the Dependency Guard task to update the baseline after adding
implementation(libs.kmpProductFlavors.gradlePlugin) so the new artifact is
accepted for consumers of the convention plugin (KmpFlavorPlugin applied by
class reference); commit the updated baseline file produced by Dependency Guard
to the repo.
In `@build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt`:
- Around line 50-72: The inferBuildConfigPackage function uses inconsistent
hyphen sanitization: sanitizedGroup currently replaces "-" with "." while the
fallback path replaces "-" with "_" which can produce different package names;
change the group-based branch so both sanitizedGroup and sanitizedName replace
hyphens with underscores (use group.replace("-", "_") and
project.name.replace("-", "_")) so the produced package
("$sanitizedGroup.$sanitizedName") matches the fallback's hyphen-to-underscore
strategy and yields valid Kotlin identifiers.
In `@build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt`:
- Line 64: The code uses deprecated EnumClass.values() calls; replace all
occurrences of Dimension.values(), Flavor.values(), and
KmpFlavors.Flavor.values() with the Kotlin 1.9+ enum entries property (e.g.,
Dimension.entries, Flavor.entries, KmpFlavors.Flavor.entries) so the getters and
any map/iteration logic (such as the get() = Dimension.values().map { ... }
expression and similar occurrences around the Flavor usages) iterate over
entries instead of values().
---
Nitpick comments:
In `@build-logic/convention/src/main/kotlin/KMPLibraryConventionPlugin.kt`:
- Line 25: Remove the unconditional apply("org.convention.kmp.flavors") from
KMPLibraryConventionPlugin and KMPCoreBaseLibraryConventionPlugin and make
flavor support opt-in: either (a) expose a boolean parameter or project property
that those plugins check (e.g., project.findProperty("kmpFlavorsEnabled") or an
extension flag) and only apply("org.convention.kmp.flavors") when true, or (b)
delete the apply call from both plugins and document that modules that need
flavors should explicitly id("org.convention.kmp.flavors") in their build
script; update any BuildConfig generation logic tied to flavors (BASE_URL,
ANALYTICS_ENABLED) to guard on the same flag/extension so infrastructure modules
don’t get flavor-specific sources or BuildConfig generated unnecessarily.
In `@build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt`:
- Around line 88-89: The function isFlavorActive is dead/unreferenced; either
remove it or document it as public API: if you intend it as an internal helper,
delete isFlavorActive from KmpFlavors to remove dead code; if you intend it for
consumers, add KDoc to isFlavorActive explaining its contract and visibility
(including that it checks Flavor.flavorName against an activeVariant string,
case-insensitively) and ensure it is declared public in the API you expect
consumers to use.
- Around line 94-97: Replace hard-coded URLs in getBaseUrl by reading
configurable properties and/or passing values into configureKmpFlavors: update
configureKmpFlavors to accept optional baseUrl parameters (e.g., demoBaseUrl,
prodBaseUrl) and have getBaseUrl(flavor: Flavor) return those values; if
parameters are not supplied, read from project properties (keys like
"kmp.flavors.demo.baseUrl" and "kmp.flavors.prod.baseUrl") with the existing
"https://demo-api.mifos.org" and "https://api.mifos.org" as defaults. Ensure
references to getBaseUrl and configureKmpFlavors are updated so callers can
override via parameters or via gradle.properties without needing to edit
KmpFlavors.kt.
- Around line 139-174: Rename the parameter flavors to something unambiguous
(e.g., flavorDataList) and update its usages inside configureKmpFlavors to avoid
shadowing the extension property this.flavors; specifically change
flavors.forEach { flavorData -> ... } to flavorDataList.forEach { flavorData ->
... } inside the this.flavors { ... } block so the receiver and the outer
collection aren't confused. Also change the buildConfigPackage parameter on
configureKmpFlavors from String? to non-null String and remove the safe-call/let
guard; set the extension property directly via
this.buildConfigPackage.set(buildConfigPackage) (update the function signature
and the single assignment site accordingly).
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
build-logic/convention/build.gradle.ktsbuild-logic/convention/src/main/kotlin/KMPCoreBaseLibraryConventionPlugin.ktbuild-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.ktbuild-logic/convention/src/main/kotlin/KMPLibraryConventionPlugin.ktbuild-logic/convention/src/main/kotlin/org/convention/KmpFlavors.ktgradle/libs.versions.toml
| compileOnly(libs.firebase.crashlytics.gradlePlugin) | ||
| compileOnly(libs.firebase.performance.gradlePlugin) | ||
| // KMP Product Flavors - for cross-platform flavor support | ||
| implementation(libs.kmpProductFlavors.gradlePlugin) |
There was a problem hiding this comment.
Run Dependency Guard to baseline the new implementation dependency.
kmpProductFlavors.gradlePlugin is added as implementation (correctly, since KmpFlavorPlugin is applied by class reference), but this adds the artifact to the runtime dependency set of all convention plugin consumers. The Dependency Guard baseline must be updated to reflect this change.
As per coding guidelines: Use Dependency Guard to validate dependencies in Kotlin Multiplatform projects.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@build-logic/convention/build.gradle.kts` at line 36, Add the new
implementation dependency to the Dependency Guard baseline: run the Dependency
Guard task to update the baseline after adding
implementation(libs.kmpProductFlavors.gradlePlugin) so the new artifact is
accepted for consumers of the convention plugin (KmpFlavorPlugin applied by
class reference); commit the updated baseline file produced by Dependency Guard
to the repo.
| private fun inferBuildConfigPackage(project: Project): String { | ||
| // Try to use the project's group if available | ||
| val group = project.group.toString() | ||
| if (group.isNotEmpty() && group != "unspecified") { | ||
| // Replace hyphens with dots to ensure valid Kotlin package name | ||
| val sanitizedGroup = group.replace("-", ".") | ||
| val sanitizedName = project.name.replace("-", ".") | ||
| return "$sanitizedGroup.$sanitizedName" | ||
| } | ||
|
|
||
| // Fall back to path-based package name | ||
| val pathParts = project.path | ||
| .removePrefix(":") | ||
| .split(":") | ||
| .filter { it.isNotEmpty() } | ||
|
|
||
| return if (pathParts.isNotEmpty()) { | ||
| // Replace hyphens with underscores to ensure valid Kotlin identifiers | ||
| "org.mifos.${pathParts.joinToString(".") { it.replace("-", "_") }}" | ||
| } else { | ||
| "org.mifos.${project.name.replace("-", "_")}" | ||
| } | ||
| } |
There was a problem hiding this comment.
Inconsistent hyphen sanitization strategy between the two package-inference code paths.
The group-based path (Line 55-56) replaces - with ., splitting a component like feature-auth into two sub-packages (feature.auth). The path-based fallback (Line 68) replaces - with _, producing a single identifier (feature_auth). The two paths can therefore generate different package names for the same module, depending solely on whether group is set.
Using underscores uniformly (valid Kotlin package identifiers) in both paths avoids this split-vs-collapse mismatch:
♻️ Proposed fix
if (group.isNotEmpty() && group != "unspecified") {
- val sanitizedGroup = group.replace("-", ".")
- val sanitizedName = project.name.replace("-", ".")
+ val sanitizedGroup = group.replace("-", "_")
+ val sanitizedName = project.name.replace("-", "_")
return "$sanitizedGroup.$sanitizedName"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt` around
lines 50 - 72, The inferBuildConfigPackage function uses inconsistent hyphen
sanitization: sanitizedGroup currently replaces "-" with "." while the fallback
path replaces "-" with "_" which can produce different package names; change the
group-based branch so both sanitizedGroup and sanitizedName replace hyphens with
underscores (use group.replace("-", "_") and project.name.replace("-", "_")) so
the produced package ("$sanitizedGroup.$sanitizedName") matches the fallback's
hyphen-to-underscore strategy and yields valid Kotlin identifiers.
| * Default dimension configurations for kmp-product-flavors. | ||
| */ | ||
| val defaultDimensions: List<DimensionConfig> | ||
| get() = Dimension.values().map { dimension -> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Replace deprecated values() with entries (Kotlin 1.9+).
Dimension.values(), Flavor.values(), and KmpFlavors.Flavor.values() are all deprecated as of Kotlin 1.9. The project targets Kotlin 2.2.21, so entries is available and preferred.
♻️ Proposed fix
- get() = Dimension.values().map { dimension ->
+ get() = Dimension.entries.map { dimension ->
- get() = Flavor.values().map { flavor ->
+ get() = Flavor.entries.map { flavor ->
- val flavor = KmpFlavors.Flavor.values().find { it.flavorName == flavorData.name }
+ val flavor = KmpFlavors.Flavor.entries.find { it.flavorName == flavorData.name }
Also applies to: 75-75, 183-183
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt` at line
64, The code uses deprecated EnumClass.values() calls; replace all occurrences
of Dimension.values(), Flavor.values(), and KmpFlavors.Flavor.values() with the
Kotlin 1.9+ enum entries property (e.g., Dimension.entries, Flavor.entries,
KmpFlavors.Flavor.entries) so the getters and any map/iteration logic (such as
the get() = Dimension.values().map { ... } expression and similar occurrences
around the Flavor usages) iterate over entries instead of values().
Summary
Integrates kmp-product-flavors v1.0.1 plugin for cross-platform flavor support in Kotlin Multiplatform projects.
Changes
KMPFlavorsConventionPlugin.kt- Wraps kmp-product-flavors pluginorg/convention/KmpFlavors.kt- Centralized flavor configuration (demo/prod)KMPLibraryConventionPluginandKMPCoreBaseLibraryConventionPluginkmpProductFlavors = "1.0.1"dependencyFeatures Enabled
commonDemo/,commonProd/, etc.)v1.0.1 Fixes
commonFlavorTest plan
./gradlew listFlavorsshows demo/prod variantsDocumentation
🤖 Generated with Claude Code
Summary by CodeRabbit