Skip to content

feat: Integrate KMP Product Flavors v1.0.1#133

Open
therajanmaurya wants to merge 1 commit intoopenMF:devfrom
therajanmaurya:feature/kmp-product-flavors-v1.0.1
Open

feat: Integrate KMP Product Flavors v1.0.1#133
therajanmaurya wants to merge 1 commit intoopenMF:devfrom
therajanmaurya:feature/kmp-product-flavors-v1.0.1

Conversation

@therajanmaurya
Copy link
Member

@therajanmaurya therajanmaurya commented Feb 25, 2026

Summary

Integrates kmp-product-flavors v1.0.1 plugin for cross-platform flavor support in Kotlin Multiplatform projects.

Changes

  • Convention Plugin: KMPFlavorsConventionPlugin.kt - Wraps kmp-product-flavors plugin
  • Flavor Config: org/convention/KmpFlavors.kt - Centralized flavor configuration (demo/prod)
  • Plugin Integration: Applied in both KMPLibraryConventionPlugin and KMPCoreBaseLibraryConventionPlugin
  • Version Catalog: Added kmpProductFlavors = "1.0.1" dependency
  • Bug Fix: Fixed BuildConfig package name generation to handle project names with hyphens

Features Enabled

  • Multi-dimensional flavor support (demo/prod)
  • BuildConfig generation per variant
  • Flavor-specific source sets (commonDemo/, commonProd/, etc.)
  • Proper source set dependency wiring (no warnings)

v1.0.1 Fixes

  • Fixed "Invalid Dependency on Default Compilation Source Set" warnings
  • Platform flavor source sets now correctly depend only on commonFlavor
  • Improved convention plugin integration documentation

Test plan

  • Verify build completes without flavor-related warnings
  • Verify ./gradlew listFlavors shows demo/prod variants
  • Verify BuildConfig is generated with correct package names (no hyphens)

Documentation

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Kotlin Multiplatform Product Flavors support enabling cross-platform flavor configuration management
    • Introduced configurable flavor dimensions and variants (DEMO/PROD) with per-flavor build configuration fields
    • Added support for flavor-specific application and bundle ID suffixes, base URL, and analytics settings

- 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>
@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

The 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

Cohort / File(s) Summary
Build Configuration
build-logic/convention/build.gradle.kts, gradle/libs.versions.toml
Added KMP Product Flavors plugin dependency, Gradle plugin registration (id: org.convention.kmp.flavors), and version/library references for kmpProductFlavors.
Convention Plugin Integration
build-logic/convention/src/main/kotlin/KMPCoreBaseLibraryConventionPlugin.kt, build-logic/convention/src/main/kotlin/KMPLibraryConventionPlugin.kt
Applied the new org.convention.kmp.flavors plugin to existing KMP convention plugins to enable flavor configuration across library projects.
KMP Flavors Convention Plugin
build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt
New convention plugin that applies KmpFlavorPlugin, configures KmpFlavorExtension with dimensions/flavors, enables BuildConfig generation, and infers the BuildConfig package from project group/name or path with validation logic.
Centralized Flavor Configuration
build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt
New module defining reusable flavor infrastructure: enums for Dimension (CONTENT_TYPE) and Flavor (DEMO, PROD), data classes for dimension and flavor configs, extension function configureKmpFlavors to apply centralized definitions, and helper functions to query active flavors, base URLs, and analytics settings.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Across platforms, flavors dance with grace,
DEMO and PROD now share a common space,
BuildConfig fields spring forth, dimension by dimension,
Convention plugins unite with joyful intention! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: integrating KMP Product Flavors v1.0.1 into the project, which is reflected throughout the changeset including new plugin registration, convention plugins, flavor configuration, and version catalog updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt (3)

88-89: isFlavorActive is 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.kts consumers, 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_URL values ("https://demo-api.mifos.org" / "https://api.mifos.org") are embedded in the convention plugin. Since this is a project template, downstream adopters must fork KmpFlavors.kt to change these URLs. Consider externalizing them — e.g., reading from gradle.properties or accepting them as parameters to configureKmpFlavors — so the convention plugin remains project-agnostic.

Do you want me to propose an implementation that reads BASE_URL values from gradle.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 name flavors inside this.flavors { } block is visually confusing.

On Line 160, this.flavors { flavors.forEach { ... } } calls KmpFlavorExtension.flavors as the receiver, while flavors inside the lambda still resolves to the outer List<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 (inferBuildConfigPackage always returns String). 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.flavors unconditionally in both KMPLibraryConventionPlugin and KMPCoreBaseLibraryConventionPlugin means 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 behavior

This 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

📥 Commits

Reviewing files that changed from the base of the PR and between c4a85c9 and a1d037f.

📒 Files selected for processing (6)
  • build-logic/convention/build.gradle.kts
  • build-logic/convention/src/main/kotlin/KMPCoreBaseLibraryConventionPlugin.kt
  • build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt
  • build-logic/convention/src/main/kotlin/KMPLibraryConventionPlugin.kt
  • build-logic/convention/src/main/kotlin/org/convention/KmpFlavors.kt
  • gradle/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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +50 to +72
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("-", "_")}"
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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 ->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant