Skip to content

chore(deps): full v2.7.0 adoption — Tier 2 record + drift gate + version pin + Gradle 9.5.1#189

Open
therajanmaurya wants to merge 5 commits into
openMF:devfrom
therajanmaurya:chore/adopt-kmp-product-flavors-v2.7.0
Open

chore(deps): full v2.7.0 adoption — Tier 2 record + drift gate + version pin + Gradle 9.5.1#189
therajanmaurya wants to merge 5 commits into
openMF:devfrom
therajanmaurya:chore/adopt-kmp-product-flavors-v2.7.0

Conversation

@therajanmaurya

@therajanmaurya therajanmaurya commented Jun 3, 2026

Copy link
Copy Markdown
Member

Summary

End-to-end consumer-side adoption of kmp-product-flavors v2.7.0 (Maven Central + Gradle Plugin Portal, published 2026-06-02 — AGP 9.2.1 + Kotlin 2.3.21 + 100% coverage on the flavor-plugin module).

Bundles two coordinated changes into one PR — the Tier 2 source-of-truth record + the version bumps it documents.

1. Tier 2 adoption record (canonical reference)

  • docs/ADOPTION_KMP_PRODUCT_FLAVORS.md — first-party reference adoption realizing the abstract Tier 1 spec (library docs/adoption/v2.7/consumer.md) against this template's actual paths + outputs. Includes files inventory (KMPFlavorsConventionPlugin.kt + AppFlavor.kt + LocalFlavorsLoader.kt), §1–§14 verify gates, per-version section (v2.7.0 seeded), "downstream forks own only LocalFlavors.kt" contract clarification, and "how future bumps work" 8-step recipe.
  • scripts/adoption-doc-verify.py — local drift-gate runner. Parses every ### ✅ Verify block from the record and reports PASS/FAIL. Baseline: 14/14 PASS. Script-only (no CI surface) — contributor-side correctness check, not a merge blocker.

2. Version bumps (the adoption itself)

  • gradle/libs.versions.tomlkmpProductFlavors 2.4.2 → 2.7.0
  • gradle/wrapper/gradle-wrapper.properties — Gradle 9.5.0 → 9.5.1 (SHA256 bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f)

Audit

Verified using the library's /lib-sync audit tool against the Tier 1 spec:

Phase PASS AUTO_FIX MANUAL SKIP
Pre-bump 13 0 0 2
Post-bump 13 0 0 2

SKIPs are correct:

  • §4a (direct-apply pattern, 3a) — n/a; this template uses 3b convention plugin
  • §12 (AGP 9.x landmine checks) — conditional; AGP 9.2.1 already verified by §13 end-to-end smoke test

Why this approach

Single source of truth per tier — this Tier 2 record IS the audit trail for the template's adoption. The library's Tier 1 spec gives the abstract contract; this record realizes it concretely; downstream forks (mifos-mobile, mifos-pay, mifos-x-field-officer-app) inherit this whole file via sync-dirs.sh and tweak only their LocalFlavors.kt.

Three-tier chain: [library consumer.md] → [this record] → [downstream forks' LocalFlavors.kt].

v2.7.0 preserves the v2.6 DSL surface byte-identically. Zero source changes required in KMPFlavorsConventionPlugin or AppFlavor.kt. The library's migration doc confirms: "You do not need to migrate."

Related

Test plan

  • CI green (Android / Desktop / Web / iOS builds + Static Analysis)
  • python3 scripts/adoption-doc-verify.py docs/ADOPTION_KMP_PRODUCT_FLAVORS.md → 14/14 PASS
  • ./gradlew :validateFlavors :listFlavors :generateFlavorBuildConfig → BUILD SUCCESSFUL (§13 smoke test)
  • Confirm no DSL changes leaked into KMPFlavorsConventionPlugin.kt or AppFlavor.kt

Summary by CodeRabbit

  • Chores

    • Bumped kmp-product-flavors to 2.8.1
    • Upgraded Gradle wrapper to 9.5.1
    • Added an adoption verification CLI to detect doc drift
  • New Features

    • iOS: per-flavor build configurations, generated xcconfig files, and Xcode schemes for demo/prod variants
    • Android: added a staging build type and flavor-aware build behavior
    • Desktop/Web: flavor-aware app titles and bundle IDs
  • Refactor

    • Simplified flavor/build-type handling and introduced a local flavors extension for overrides
  • Documentation

    • Expanded adoption record with integration steps and verify gates

Rajan Maurya added 2 commits June 4, 2026 00:10
Full v2.7.0 adoption per the library's Tier 1 spec
(MobileByteLabs/kmp-product-flavors docs/adoption/v2.7/consumer.md).

Audited against this branch with the library's /lib-sync audit tool:

  Pre-audit  (before bumps): 13 PASS · 0 AUTO_FIX · 0 MANUAL · 2 SKIP
  Post-audit (after bumps):  13 PASS · 0 AUTO_FIX · 0 MANUAL · 2 SKIP

SKIPs are correct:
  · §4a (direct-apply pattern, 3a) — n/a; this consumer uses 3b convention
    plugin
  · §12 (AGP 9.x compatibility check) — conditional; AGP 9.2.1 builds
    already verified by §13 end-to-end smoke test

Changes:
  - gradle/libs.versions.toml: kmpProductFlavors 2.4.2 → 2.7.0
  - gradle/wrapper/gradle-wrapper.properties: Gradle 9.5.0 → 9.5.1
    (new SHA256: bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f)

Library v2.7.0 ships AGP 9.2.1 + Kotlin 2.3.21 + 100% coverage on the
flavor-plugin module + 8-phase epic per PLAN-v27-agp9-support (704 tests
across 92 classes, +423 tests since v2.6).

Per the Tier 2 adoption record (docs/ADOPTION_KMP_PRODUCT_FLAVORS.md, in
this same PR), this template is the first-party canonical consumer —
downstream forks (mifos-mobile, mifos-pay, mifos-x-field-officer-app)
will pull this bump via sync-dirs.sh on their next sync; only their
LocalFlavors.kt requires hand-editing.
…duct-flavors v2.7.0

Adds the canonical Tier 2 adoption record for the kmp-product-flavors plugin
per the three-tier source-of-truth chain (Library → Template → Fork).

## What this PR ships

### `docs/ADOPTION_KMP_PRODUCT_FLAVORS.md`

The first-party reference adoption of MobileByteLabs/kmp-product-flavors —
realizes the abstract Tier 1 spec (`docs/adoption/v2.7/consumer.md` in the
library) concretely against this template's actual paths + actual expected
outputs.

Structure:

- **Files inventory** — the 3 files that constitute the adoption pattern:
  - `build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt`
  - `build-logic/convention/src/main/kotlin/org/convention/AppFlavor.kt`
    (AGP-only-module helper)
  - `build-logic/convention/src/main/kotlin/LocalFlavorsLoader.kt`
    (reflective extension hook for downstream Tier 3 forks)
- **§1-§14 verify gates** — concrete realization of every abstract gate in
  the library's `consumer.md`. Each block is a bash one-liner the local
  drift gate runs.
- **Per-version section** — `## v2.7.0 — adopted 2026-06-03` seeded; future
  bumps append above it via `/lib-sync` in the library repo.
- **"Downstream forks — LocalFlavors.kt is the ONLY file you own"** —
  clarifies the Tier 3 contract: forks (mifos-mobile, mifos-pay,
  mifos-x-field-officer-app) sync everything from this template via
  `sync-dirs.sh`; their sole hand-edit surface is
  `build-logic/convention/src/main/kotlin/local/LocalFlavors.kt`.
- **"How future bumps work"** — 8-step recipe consumers can follow when
  the library publishes a new minor.

### `scripts/adoption-doc-verify.py`

Local drift-gate runner. Parses every `### ✅ Verify` /
`### Release-time check` / `#### [§N` header's bash block in the Tier 2
record, runs each, and reports PASS/FAIL with the offending line.

Run locally:

    python3 scripts/adoption-doc-verify.py docs/ADOPTION_KMP_PRODUCT_FLAVORS.md

Baseline: **14/14 PASS** at the v2.7.0 seed. The gate is intentionally
script-only (no CI surface) — it's a contributor-side correctness check
not a merge blocker. The library's per-release `/lib-sync` automation
runs the same blocks against the consumer working tree during the
adoption flow.

## Why this approach

Single source of truth per tier — this Tier 2 record IS the audit trail
for the template's adoption. No tier copies content from another; they
cross-reference. The library's Tier 1 spec gives the abstract contract;
this record realizes it concretely; downstream forks inherit this whole
file via `sync-dirs.sh` and tweak only their `LocalFlavors.kt`.

Three-tier chain: [Library `docs/adoption/v2.7/consumer.md`] → [this
record] → [downstream forks' `LocalFlavors.kt`].

## Related

- Library Tier 1 spec: MobileByteLabs/kmp-product-flavors PR openMF#118
- Library v2.7.0 release: Maven Central + Gradle Plugin Portal (published
  2026-06-02)
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Tier‑2 adoption record for kmp-product-flavors (v2.8.1), a verification CLI to run document verify blocks, bumps the version catalog and Gradle wrapper, extends the convention plugin with an iOS xcconfig generator and LocalFlavors hook, updates desktop/web/Android builds to be flavor-aware, and wires per-flavor iOS xcconfig files, pbxproj build configurations, and schemes.

Changes

kmp-product-flavors Adoption Record and Verification

Layer / File(s) Summary
Adoption record and version entry
docs/ADOPTION_KMP_PRODUCT_FLAVORS.md
Adds the Tier‑2 adoption record, documents v2.8.1 (current) with delta-focused verify gates, records template cleanups (removed legacy files/refs), and retains the comprehensive v2.7.0 §1–§14 verify gates and procedures.
Drift-gate verification script
scripts/adoption-doc-verify.py
Implements a CLI that extracts fenced bash verify blocks from adoption docs, optionally skips marked blocks, executes them with bash -c in a configurable --cwd, captures output, aggregates PASS/FAIL with truncation, prints summaries, and exits with codes 0/1/2.
Build dependency pins
gradle/libs.versions.toml, gradle/wrapper/gradle-wrapper.properties
Bumps kmpProductFlavors reference to 2.8.1 and upgrades the Gradle wrapper to 9.5.1 with updated checksum.
KMP convention plugin & iOS generator
build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt, build-logic/convention/src/main/kotlin/GenerateIosFlavorXcconfigsTask.kt
Reworks AGP bridge comments/imports to rely on AgpProductFlavorRegistrar, extends demo flavor suffixes, and registers a new GenerateIosFlavorXcconfigsTask whose outputs are wired as dependencies of iOS framework/link tasks.
SyncFork and LocalFlavors hook
build-logic/convention/src/main/kotlin/SyncForkConfigPlugin.kt, build-logic/convention/src/main/kotlin/local/LocalFlavors.kt
Clarifies sync-fork iOS identity ownership and adds local.LocalFlavors consumer entry point (documented example, empty apply hook) for downstream forks to register local flavors.
Android buildTypes cleanup
cmp-android/build.gradle.kts
Removes reliance on removed AppBuildType, adds a staging build type, and simplifies release wiring so the flavor plugin is the single API for flavor/build-type lifecycle settings.
Desktop & Web flavor integration
cmp-desktop/build.gradle.kts, cmp-web/build.gradle.kts
Apply kmp.flavors.convention, resolve active flavor via -PkmpFlavor/DSL default, derive flavor-specific title and bundle id, and propagate into JVM args, packaging, and resource tokens (APP_DISPLAY_NAME).
iOS per-flavor Xcode wiring
cmp-ios/Configs/iOSApp.xcconfig, cmp-ios/Configs/*.xcconfig, cmp-ios/iosApp.xcodeproj/*
Adds umbrella iOSApp.xcconfig include, generates per-variant demo*/prod* .xcconfig files with KMPF_VARIANT/bundle/team mappings, registers iOSApp.xcconfig and Configs group in project.pbxproj, expands project and target build configurations to six demo*/prod* entries (adjusting FRAMEWORK_SEARCH_PATHS), and adds shared schemes for each variant.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I bumped the pin and wrote a guide so clear,

a script to check the gates and flags we cheer,
Gradle, desktop, web and Xcode now align,
flavors bloom per target, every build-time fine—
a tidy hop, the template's spring is here!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 changes: adopting kmp-product-flavors v2.7.0, adding adoption documentation (Tier 2 record), implementing a drift gate, and updating Gradle to 9.5.1.
Linked Issues check ✅ Passed The PR fully addresses #186 objectives: adds docs/ADOPTION_KMP_PRODUCT_FLAVORS.md as Tier 2 record, implements scripts/adoption-doc-verify.py as drift gate, updates kmp-product-flavors to v2.7.0, and includes all §1–§14 verify gates with platform support.
Out of Scope Changes check ✅ Passed The PR includes additional changes beyond #186's scope: Gradle 9.5.1 update, removal of AppFlavor.kt/AppBuildType.kt, iOS xcconfig generation, and desktop/web flavor support. However, these align with the v2.7.0 adoption stated in PR objectives and support the adoption record's completeness.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Zero DSL changes — version-only bump. v2.8.1 ships per-flavor signingConfigs DSL
+ versionCode/versionName properties (opt-in; not declared in this template).
BuildKonfig generation unchanged. Assembly verified via assembleDemoDebug.
mavenLocal() already first in settings.gradle.kts for pre-GA resolution.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/ADOPTION_KMP_PRODUCT_FLAVORS.md`:
- Line 95: The markdown link text "MIGRATION_v2.6_TO_v2.7.md" points to the
wrong target (CHANGELOG.md); update the link target in
docs/ADOPTION_KMP_PRODUCT_FLAVORS.md so the href points to the actual migration
file (MIGRATION_v2.6_TO_v2.7.md or its correct repo URL) instead of
CHANGELOG.md, ensuring the visible label and the linked destination match.
- Around line 167-178: The markdown has unlabeled fenced code blocks causing
MD040; update each fence around the Gradle snippet that contains
flavorDimensions / register("demo") / buildTypes to use a language tag (e.g.,
```kotlin), and label the small file path block that shows
build-logic/convention/src/main/kotlin/local/LocalFlavors.kt with a neutral tag
like ```text so all three unlabeled fences are tagged and the lint warnings are
resolved.
- Line 127: The documentation's expected Gradle version is stale: update the
string "Gradle 8.x.x (≥ 8.0)" in ADOPTION_KMP_PRODUCT_FLAVORS.md to reflect the
actual wrapper version pinned in gradle/wrapper/gradle-wrapper.properties
(9.5.1) — e.g., change to "Gradle 9.5.1 (≥ 9.5.1)" or a matching 9.x.x range;
ensure the text "Gradle 8.x.x" is replaced consistently with the new version
label.
- Around line 342-343: The document currently contradicts itself about
downstream forks' responsibilities: decide whether downstream forks SHOULD
append their own adoption section to ADOPTION_KMP_PRODUCT_FLAVORS.md or they
SHOULD NOT, then make the file consistent. Update the paragraph that currently
says downstream forks "should also append a new version section" (the sentence
referencing sync-dirs.sh and LocalFlavors.kt) to match the chosen contract, and
adjust the other references that claim forks do not maintain their own adoption
doc so all occurrences convey the same rule; ensure the text mentions
sync-dirs.sh behavior and the relationship with LocalFlavors.kt so readers
understand which file is auto-synced vs locally edited.

In `@scripts/adoption-doc-verify.py`:
- Line 147: The script initializes overall_skip but never updates it, causing
misleading totals; remove the unused SKIP tracking and any SKIP column from the
summary until proper skip-handling is implemented: delete the overall_skip
variable and any increments/uses of overall_skip and remove "SKIP" from the
final totals/print logic (look for overall_pass/overall_fail/overall_skip and
the summary print block that references SKIP) so the script only reports PASS
and FAIL consistently.
- Around line 26-27: The docs mention a --strict flag but the CLI parser never
defines it, so add a boolean flag to the argument parsing logic (the
ArgumentParser instance that calls parse_args()) such as
parser.add_argument('--strict', action='store_true', help='Fail on any block
error, not just exit code') and then wire that flag into the existing
verification flow where the code checks behavior on block errors (ensure any
conditional branches that decide failure use args.strict); update any variable
names referencing the flag (args.strict) so the docs and CLI match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60a5f9b4-9ce9-436e-b91a-cd7f6a8fc32a

📥 Commits

Reviewing files that changed from the base of the PR and between 93350c0 and 7d3f1e2.

📒 Files selected for processing (4)
  • docs/ADOPTION_KMP_PRODUCT_FLAVORS.md
  • gradle/libs.versions.toml
  • gradle/wrapper/gradle-wrapper.properties
  • scripts/adoption-doc-verify.py


### Why the bump is safe

Per the library's [`MIGRATION_v2.6_TO_v2.7.md`](https://github.com/MobileByteLabs/kmp-product-flavors/blob/development/CHANGELOG.md) (opens with "You do not need to migrate."):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the migration link target mismatch.

Line [95] labels MIGRATION_v2.6_TO_v2.7.md but links to CHANGELOG.md, which sends readers to the wrong artifact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ADOPTION_KMP_PRODUCT_FLAVORS.md` at line 95, The markdown link text
"MIGRATION_v2.6_TO_v2.7.md" points to the wrong target (CHANGELOG.md); update
the link target in docs/ADOPTION_KMP_PRODUCT_FLAVORS.md so the href points to
the actual migration file (MIGRATION_v2.6_TO_v2.7.md or its correct repo URL)
instead of CHANGELOG.md, ensuring the visible label and the linked destination
match.


```bash
./gradlew --version | grep '^Gradle'
# Expected: Gradle 8.x.x (≥ 8.0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale expected Gradle output.

Line [127] says Gradle 8.x.x, but wrapper is pinned to 9.5.1 in gradle/wrapper/gradle-wrapper.properties (Line [3]).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ADOPTION_KMP_PRODUCT_FLAVORS.md` at line 127, The documentation's
expected Gradle version is stale: update the string "Gradle 8.x.x (≥ 8.0)" in
ADOPTION_KMP_PRODUCT_FLAVORS.md to reflect the actual wrapper version pinned in
gradle/wrapper/gradle-wrapper.properties (9.5.1) — e.g., change to "Gradle 9.5.1
(≥ 9.5.1)" or a matching 9.x.x range; ensure the text "Gradle 8.x.x" is replaced
consistently with the new version label.

Comment thread docs/ADOPTION_KMP_PRODUCT_FLAVORS.md
Comment thread docs/ADOPTION_KMP_PRODUCT_FLAVORS.md
Comment on lines +26 to +27
scripts/adoption-doc-verify.py --strict docs/... (fail on any block error, not just exit code)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align --strict docs with actual CLI behavior.

Lines [26]-[27] advertise --strict, but argparse never defines that flag, so documented usage fails.

Suggested edit (docs-only fix)
-  scripts/adoption-doc-verify.py --strict docs/...   (fail on any block error, not just exit code)
+  scripts/adoption-doc-verify.py docs/...            (fails when any verify block exits non-zero)

Also applies to: 113-139

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/adoption-doc-verify.py` around lines 26 - 27, The docs mention a
--strict flag but the CLI parser never defines it, so add a boolean flag to the
argument parsing logic (the ArgumentParser instance that calls parse_args())
such as parser.add_argument('--strict', action='store_true', help='Fail on any
block error, not just exit code') and then wire that flag into the existing
verification flow where the code checks behavior on block errors (ensure any
conditional branches that decide failure use args.strict); update any variable
names referencing the flag (args.strict) so the docs and CLI match.


overall_pass = 0
overall_fail = 0
overall_skip = 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

SKIP is reported but never tracked.

overall_skip is always 0, so totals are misleading once # adoption-verify: skip is used.

Suggested minimal fix (remove misleading SKIP until implemented)
-    overall_skip = 0
@@
-    total = overall_pass + overall_fail + overall_skip
+    total = overall_pass + overall_fail
@@
-    print(f"Total: {total}   PASS: {overall_pass}   FAIL: {overall_fail}")
+    print(f"Total: {total}   PASS: {overall_pass}   FAIL: {overall_fail}")

Also applies to: 173-176

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/adoption-doc-verify.py` at line 147, The script initializes
overall_skip but never updates it, causing misleading totals; remove the unused
SKIP tracking and any SKIP column from the summary until proper skip-handling is
implemented: delete the overall_skip variable and any increments/uses of
overall_skip and remove "SKIP" from the final totals/print logic (look for
overall_pass/overall_fail/overall_skip and the summary print block that
references SKIP) so the script only reports PASS and FAIL consistently.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmp-ios/iosApp.xcodeproj/project.pbxproj (1)

311-311: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align iOS target SDK / deployment target to the “minimum iOS 15.0, target iOS 17.0” guideline

In cmp-ios/iosApp.xcodeproj/project.pbxproj, all configurations set IPHONEOS_DEPLOYMENT_TARGET = 15.3 and SDKROOT = iphoneos (unversioned), with no explicit iOS 17.0 target SDK setting. Update the build settings to target iOS 17.0 (e.g., via an iOS 17.0-pinned SDKROOT/equivalent) and set the minimum to 15.0 per the guideline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-ios/iosApp.xcodeproj/project.pbxproj` at line 311, Update the Xcode
project build settings so all configurations use a minimum deployment target of
iOS 15.0 and a pinned iOS 17.0 SDK: replace instances of
IPHONEOS_DEPLOYMENT_TARGET = 15.3 with IPHONEOS_DEPLOYMENT_TARGET = 15.0 and set
SDKROOT to iphoneos17.0 (or add SDKROOT = iphoneos17.0 where SDKROOT is
currently unversioned) for the same build configurations referenced in the
project.pbxproj so every build configuration consistently targets iOS 17.0 while
supporting a minimum of iOS 15.0.
🧹 Nitpick comments (3)
cmp-ios/iosApp.xcodeproj/project.pbxproj (2)

749-749: ⚡ Quick win

Framework search path inconsistency between configurations.

The legacy Debug/Release configurations (lines 749, 781) use $(CONFIGURATION) in FRAMEWORK_SEARCH_PATHS, while the new flavor-specific configurations (lines 813, 847, 881, 916, 950, 984) use $(KMPF_VARIANT). This inconsistency may cause issues if both configuration sets are used.

Consider either:

  • Removing the legacy Debug/Release target configurations if they're no longer needed, or
  • Updating the legacy configs to also use $(KMPF_VARIANT) for consistency

Also applies to: 781-781, 813-813, 847-847, 881-881, 916-916, 950-950, 984-984

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-ios/iosApp.xcodeproj/project.pbxproj` at line 749, FRAMEWORK_SEARCH_PATHS
is inconsistent: legacy Debug/Release entries use $(CONFIGURATION) while new
flavor-specific entries use $(KMPF_VARIANT); either remove the legacy
Debug/Release target configs if they are obsolete, or update the legacy
FRAMEWORK_SEARCH_PATHS entries to replace $(CONFIGURATION) with $(KMPF_VARIANT)
so all configurations reference the same variable—look for the
FRAMEWORK_SEARCH_PATHS keys that contain
"$(SRCROOT)/../cmp-shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"
and change them to use "$(KMPF_VARIANT)" (or delete the legacy target blocks if
you intend to drop them).

737-800: ⚡ Quick win

Consider removing legacy Debug/Release target configurations.

The project retains the original Debug/Release target configurations (lines 737-800) alongside the new per-flavor configurations (demoDebug, demoRelease, prodDebug, etc.). Retaining both sets may cause confusion about which configurations should be used for builds.

If the new flavor-specific configurations fully replace Debug/Release, consider removing the legacy configurations from the target build configuration list.

Also applies to: 1028-1029

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-ios/iosApp.xcodeproj/project.pbxproj` around lines 737 - 800, The project
still contains legacy XCBuildConfiguration entries named "Debug" and "Release"
(IDs like 7555FFA6242A565B00829871 and 7555FFA7242A565B00829871) alongside your
new flavor-specific configs (demoDebug/demoRelease/prodDebug/etc.); remove these
legacy Debug/Release XCBuildConfiguration blocks and any references to them in
the target buildConfigurationList(s) so only the flavor-specific configurations
remain, and ensure you also remove or update their baseConfigurationReference
values if they point to old Pods-iosApp.debug/release.xcconfig to avoid dangling
references.
cmp-ios/Configs/prodStaging.xcconfig (1)

18-18: ⚡ Quick win

Clarify CocoaPods debug-vs-release intent for staging (currently looks correct)

prodStaging.xcconfig points to Pods-iosApp.release.xcconfig; the template’s prodStaging Xcode build settings are release-like (e.g., SWIFT_OPTIMIZATION_LEVEL = "-O"), while prodDebug uses Pods-iosApp.debug.xcconfig. Add a brief comment explaining that CocoaPods xcconfigs are chosen by Debug/Release behavior, so staging intentionally uses the release variant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-ios/Configs/prodStaging.xcconfig` at line 18, Add a brief clarifying
comment in prodStaging.xcconfig near the existing include line to state that
this file intentionally points to Pods-iosApp.release.xcconfig because the
prodStaging template uses release-like build settings (e.g.,
SWIFT_OPTIMIZATION_LEVEL = "-O"), whereas prodDebug uses
Pods-iosApp.debug.xcconfig; reference the Debug/Release selection behavior so
future readers know the CocoaPods xcconfig variant is chosen to match the
staging release-like configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmp-ios/Configs/demoDebug.xcconfig`:
- Around line 10-11: Replace the hardcoded demo credentials by reading them from
environment/config at build time: remove literal values for KMPF_DEMO_USERNAME
and KMPF_DEMO_PASSWORD and change them to references/placeholders that are
injected from environment variables or a secure config provider (e.g. use Xcode
build settings or xcconfig includes that map to env vars), and update
documentation to require setting those env vars for demo builds; ensure any CI
or deployment pipelines set KMPF_DEMO_USERNAME and KMPF_DEMO_PASSWORD securely
and add a note in the repo README indicating these must be changed per
deployment.

In `@cmp-ios/Configs/demoRelease.xcconfig`:
- Around line 8-9: KMPF_DEMO_USERNAME and KMPF_DEMO_PASSWORD are hardcoded in
demoRelease.xcconfig; remove these credentials and read them from a secure
external source instead (e.g., environment variables injected by CI, an included
non-committed xcconfig like DemoCredentials.xcconfig.local, or the iOS
Keychain/Secrets manager). Update build settings to reference the externalized
values (keep the KMPF_DEMO_USERNAME and KMPF_DEMO_PASSWORD keys but have them
populated at build time) and ensure the new local credentials file is added to
.gitignore and documented for developers and CI secret config.

In `@cmp-ios/Configs/prodDebug.xcconfig`:
- Line 18: Uncomment the CocoaPods include in the prodDebug.xcconfig so the
flavor config imports the generated pods xcconfig (restore the line referencing
Pods-iosApp.debug.xcconfig and keep the conditional "`#include`?"), ensuring
prodDebug.xcconfig (and other flavor configs) actually receive CocoaPods
settings after running pod install; update the prodDebug.xcconfig by enabling
the include line that currently reads referencing Pods-iosApp.debug.xcconfig and
re-run pod install to verify the flavor mappings are applied.

In `@docs/ADOPTION_KMP_PRODUCT_FLAVORS.md`:
- Around line 130-145: The fenced code block beginning with "cmp-ios/Configs/"
in docs/ADOPTION_KMP_PRODUCT_FLAVORS.md is unlabeled and triggers markdownlint
MD040; update the opening fence to include a language tag (e.g., add "text"
after the triple backticks) so the block reads ```text and leave the closing
fence unchanged, locating the block by the line that starts with
"cmp-ios/Configs/" and the surrounding xcconfig/xcscheme listing.

---

Outside diff comments:
In `@cmp-ios/iosApp.xcodeproj/project.pbxproj`:
- Line 311: Update the Xcode project build settings so all configurations use a
minimum deployment target of iOS 15.0 and a pinned iOS 17.0 SDK: replace
instances of IPHONEOS_DEPLOYMENT_TARGET = 15.3 with IPHONEOS_DEPLOYMENT_TARGET =
15.0 and set SDKROOT to iphoneos17.0 (or add SDKROOT = iphoneos17.0 where
SDKROOT is currently unversioned) for the same build configurations referenced
in the project.pbxproj so every build configuration consistently targets iOS
17.0 while supporting a minimum of iOS 15.0.

---

Nitpick comments:
In `@cmp-ios/Configs/prodStaging.xcconfig`:
- Line 18: Add a brief clarifying comment in prodStaging.xcconfig near the
existing include line to state that this file intentionally points to
Pods-iosApp.release.xcconfig because the prodStaging template uses release-like
build settings (e.g., SWIFT_OPTIMIZATION_LEVEL = "-O"), whereas prodDebug uses
Pods-iosApp.debug.xcconfig; reference the Debug/Release selection behavior so
future readers know the CocoaPods xcconfig variant is chosen to match the
staging release-like configuration.

In `@cmp-ios/iosApp.xcodeproj/project.pbxproj`:
- Line 749: FRAMEWORK_SEARCH_PATHS is inconsistent: legacy Debug/Release entries
use $(CONFIGURATION) while new flavor-specific entries use $(KMPF_VARIANT);
either remove the legacy Debug/Release target configs if they are obsolete, or
update the legacy FRAMEWORK_SEARCH_PATHS entries to replace $(CONFIGURATION)
with $(KMPF_VARIANT) so all configurations reference the same variable—look for
the FRAMEWORK_SEARCH_PATHS keys that contain
"$(SRCROOT)/../cmp-shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"
and change them to use "$(KMPF_VARIANT)" (or delete the legacy target blocks if
you intend to drop them).
- Around line 737-800: The project still contains legacy XCBuildConfiguration
entries named "Debug" and "Release" (IDs like 7555FFA6242A565B00829871 and
7555FFA7242A565B00829871) alongside your new flavor-specific configs
(demoDebug/demoRelease/prodDebug/etc.); remove these legacy Debug/Release
XCBuildConfiguration blocks and any references to them in the target
buildConfigurationList(s) so only the flavor-specific configurations remain, and
ensure you also remove or update their baseConfigurationReference values if they
point to old Pods-iosApp.debug/release.xcconfig to avoid dangling references.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2d7b163-2faa-400a-ae40-59f0e8501048

📥 Commits

Reviewing files that changed from the base of the PR and between 7d3f1e2 and 6b4e992.

📒 Files selected for processing (17)
  • build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt
  • build-logic/convention/src/main/kotlin/org/convention/AppBuildType.kt
  • build-logic/convention/src/main/kotlin/org/convention/AppFlavor.kt
  • cmp-android/build.gradle.kts
  • cmp-ios/Configs/demoDebug.xcconfig
  • cmp-ios/Configs/demoRelease.xcconfig
  • cmp-ios/Configs/demoStaging.xcconfig
  • cmp-ios/Configs/iOSApp.xcconfig
  • cmp-ios/Configs/prodDebug.xcconfig
  • cmp-ios/Configs/prodRelease.xcconfig
  • cmp-ios/Configs/prodStaging.xcconfig
  • cmp-ios/iosApp.xcodeproj/project.pbxproj
  • cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/demoDebug.xcscheme
  • cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/demoRelease.xcscheme
  • cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/prodDebug.xcscheme
  • cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/prodRelease.xcscheme
  • docs/ADOPTION_KMP_PRODUCT_FLAVORS.md
💤 Files with no reviewable changes (2)
  • build-logic/convention/src/main/kotlin/org/convention/AppFlavor.kt
  • build-logic/convention/src/main/kotlin/org/convention/AppBuildType.kt
✅ Files skipped from review due to trivial changes (6)
  • cmp-ios/Configs/iOSApp.xcconfig
  • cmp-ios/Configs/demoStaging.xcconfig
  • cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/prodRelease.xcscheme
  • cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/demoDebug.xcscheme
  • cmp-ios/Configs/prodRelease.xcconfig
  • cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/demoRelease.xcscheme

Comment thread cmp-ios/Configs/demoDebug.xcconfig Outdated
Comment thread cmp-ios/Configs/demoRelease.xcconfig Outdated
Comment thread cmp-ios/Configs/prodDebug.xcconfig Outdated
KMPF_SHOW_DEBUG_OVERLAY = YES

// Optional: uncomment after running `pod install`
// #include? "../Pods/Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify CocoaPods xcconfig naming for flavor variants
fd -e xcconfig . cmp-ios/Pods --no-ignore

Repository: openMF/kmp-project-template

Length of output: 171


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== File exists check =="
ls -la cmp-ios/Configs || true
ls -la cmp-ios/iosApp.xcodeproj || true

echo "== prodDebug.xcconfig (context around include line) =="
if [ -f cmp-ios/Configs/prodDebug.xcconfig ]; then
  nl -ba cmp-ios/Configs/prodDebug.xcconfig | sed -n '1,120p'
else
  echo "Missing: cmp-ios/Configs/prodDebug.xcconfig"
fi

echo "== iOSApp.xcconfig (how per-configuration maps to variant xcconfig) =="
if [ -f cmp-ios/Configs/iOSApp.xcconfig ]; then
  nl -ba cmp-ios/Configs/iOSApp.xcconfig | sed -n '1,120p'
else
  echo "Missing: cmp-ios/Configs/iOSApp.xcconfig"
fi

echo "== Search for Pods-iosApp.*.xcconfig references in repo =="
rg -n --hidden --no-ignore-vcs "Pods-iosApp\.[A-Za-z0-9_+-]+\.xcconfig" -S cmp-ios || true

echo "== Search for potential variant-specific pods xcconfig names =="
rg -n --hidden --no-ignore-vcs "Pods-iosApp\.prodDebug\.xcconfig|Pods-iosApp\.prodRelease\.xcconfig|Pods-iosApp\.demoDebug\.xcconfig" -S . || true

echo "== pbxproj references to Pods-iosApp debug/release xcconfigs =="
if [ -f cmp-ios/iosApp.xcodeproj/project.pbxproj ]; then
  rg -n "Pods-iosApp\.(debug|release)\.xcconfig" cmp-ios/iosApp.xcodeproj/project.pbxproj || true
else
  echo "Missing: cmp-ios/iosApp.xcodeproj/project.pbxproj"
fi

Repository: openMF/kmp-project-template

Length of output: 1109


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "== prodDebug.xcconfig (show includes section) =="
if [ -f cmp-ios/Configs/prodDebug.xcconfig ]; then
  # print first 80 lines with line numbers using cat -n
  cat -n cmp-ios/Configs/prodDebug.xcconfig | sed -n '1,120p'
else
  echo "Missing: cmp-ios/Configs/prodDebug.xcconfig"
fi

echo "== iOSApp.xcconfig =="
if [ -f cmp-ios/Configs/iOSApp.xcconfig ]; then
  cat -n cmp-ios/Configs/iOSApp.xcconfig | sed -n '1,120p'
else
  echo "Missing: cmp-ios/Configs/iOSApp.xcconfig"
fi

echo "== Search for Pods-iosApp.*.xcconfig references (any) =="
rg -n --hidden --no-ignore-vcs "Pods-iosApp\.[A-Za-z0-9_+-]+\.xcconfig" -S cmp-ios || true

echo "== Search for variant-specific pods xcconfig names =="
rg -n --hidden --no-ignore-vcs "Pods-iosApp\.(prodDebug|prodRelease|demoDebug|demoRelease)\.xcconfig" -S . || true

echo "== pbxproj references to Pods-iosApp debug/release xcconfigs =="
rg -n "Pods-iosApp\.(debug|release)\.xcconfig" cmp-ios/iosApp.xcodeproj/project.pbxproj || true

Repository: openMF/kmp-project-template

Length of output: 4168


Use CocoaPods debug/release xcconfig mapping for flavor builds (no Pods-iosApp.prodDebug.xcconfig).

cmp-ios/Configs/prodDebug.xcconfig correctly references the CocoaPods-generated Pods-iosApp.debug.xcconfig (and the other flavor files map to Pods-iosApp.release.xcconfig); there are no Pods-iosApp.prodDebug.xcconfig/variant-specific pods xcconfigs referenced in project.pbxproj. Ensure the #include? line(s) are enabled after pod install, so the flavor configurations actually receive the CocoaPods settings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-ios/Configs/prodDebug.xcconfig` at line 18, Uncomment the CocoaPods
include in the prodDebug.xcconfig so the flavor config imports the generated
pods xcconfig (restore the line referencing Pods-iosApp.debug.xcconfig and keep
the conditional "`#include`?"), ensuring prodDebug.xcconfig (and other flavor
configs) actually receive CocoaPods settings after running pod install; update
the prodDebug.xcconfig by enabling the include line that currently reads
referencing Pods-iosApp.debug.xcconfig and re-run pod install to verify the
flavor mappings are applied.

Comment on lines +130 to +145
```
cmp-ios/Configs/
├── iOSApp.xcconfig ← umbrella: #include "$(CONFIGURATION).xcconfig"
├── demoDebug.xcconfig ← KMPF_VARIANT=demoDebug + all KMPF_* vars
├── demoStaging.xcconfig
├── demoRelease.xcconfig
├── prodDebug.xcconfig
├── prodRelease.xcconfig
└── prodStaging.xcconfig

cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/
├── demoDebug.xcscheme
├── demoRelease.xcscheme
├── prodDebug.xcscheme
└── prodRelease.xcscheme
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to this fenced block to satisfy markdownlint MD040.

The fence starting at Line 130 is unlabeled, which keeps lint warnings active.

Suggested fix
-```
+```text
 cmp-ios/Configs/
 ├── iOSApp.xcconfig          ← umbrella: `#include` "$(CONFIGURATION).xcconfig"
 ...
 └── prodRelease.xcscheme
-```
+```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
cmp-ios/Configs/
├── iOSApp.xcconfig ← umbrella: #include "$(CONFIGURATION).xcconfig"
├── demoDebug.xcconfig ← KMPF_VARIANT=demoDebug + all KMPF_* vars
├── demoStaging.xcconfig
├── demoRelease.xcconfig
├── prodDebug.xcconfig
├── prodRelease.xcconfig
└── prodStaging.xcconfig
cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/
├── demoDebug.xcscheme
├── demoRelease.xcscheme
├── prodDebug.xcscheme
└── prodRelease.xcscheme
```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ADOPTION_KMP_PRODUCT_FLAVORS.md` around lines 130 - 145, The fenced code
block beginning with "cmp-ios/Configs/" in docs/ADOPTION_KMP_PRODUCT_FLAVORS.md
is unlabeled and triggers markdownlint MD040; update the opening fence to
include a language tag (e.g., add "text" after the triple backticks) so the
block reads ```text and leave the closing fence unchanged, locating the block by
the line that starts with "cmp-ios/Configs/" and the surrounding
xcconfig/xcscheme listing.

- Apply kmp-flavors-convention to cmp-desktop and cmp-web so both platforms
  participate in the flavor system out of the box
- cmp-desktop: resolve active flavor via -PkmpFlavor Gradle property (falls back
  to DSL default); compose.desktop windowTitle, dockName, packageName, macOS
  bundleID all derived from FlavorConfig.desktopWindowTitleSuffix /
  applicationIdSuffix — no hardcoded strings
- cmp-web: ReplaceTokens injects flavor-aware title into @APP_DISPLAY_NAME@
  (appDisplayName + webTitleSuffix); no index.html changes required
- KMPFlavorsConventionPlugin: add desktopWindowTitleSuffix(" (Demo)") and
  webTitleSuffix(" (Demo)") to the demo flavor DSL block
- Add GenerateIosFlavorXcconfigsTask (new file) — reads live kmpFlavors DSL at
  execution time; auto-wired before iOS compilation; removes hardcoded xcconfig
  variants from SyncForkConfigPlugin
- Regenerate cmp-ios/Configs/*.xcconfig — lean format: KMPF_VARIANT,
  PRODUCT_BUNDLE_IDENTIFIER via $(APP_BUNDLE_ID) xcconfig substitution, TEAM_ID
  via $(TEAM_ID); all behavioral values removed (they live in KMP BuildConfig)
- Add local/LocalFlavors.kt scaffold — empty apply() with a fully-commented
  example bankA/bankB dimension block; uncomment + rename to add consumer flavors;
  all platform wiring (Android AGP, iOS xcconfigs, desktop, web) picks up new
  flavors automatically with no further changes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt`:
- Around line 129-146: The registerIosFlavorXcconfigsTask currently registers a
GenerateIosFlavorXcconfigsTask in every project and points outputDir to a shared
../cmp-ios/Configs, causing multi-project writers; change the code so the
generator is only created once (e.g., register it on the rootProject or guard
with if (project != rootProject) return) or register via
rootProject.tasks.register(...) and keep all providers (flavorBundleSuffixes,
buildTypeBundleSuffixes) reading from the extension, and set outputDir from the
root project layout; apply the same single-registration guard to the other
similar generator block (the second task registration at the other location) so
no project-level clobbering occurs.

In `@cmp-desktop/build.gradle.kts`:
- Around line 51-55: The build currently allows an explicit but invalid
-PkmpFlavor value to yield a null activeFlavorConfig and proceed; update the
logic around activeFlavor and activeFlavorConfig to fail fast: after computing
activeFlavor and looking up activeFlavorConfig via
kmpFlavorExt.flavors.findByName(activeFlavor), check if activeFlavorConfig is
null and throw a clear GradleException (or call error()) that includes the
invalid activeFlavor and a list of valid flavors (from kmpFlavorExt.flavors.map
{ it.name }) to stop the build and guide the user; reference activeFlavor and
activeFlavorConfig to locate where to add this null-check and exception.
- Line 68: Add the JVM system property for release builds by updating the
desktop packaging to include jvmArgs("-Dapp.release=true") alongside the
existing jvmArgs("-Dapp.name=$windowTitle") so System.getProperty("app.release")
is set for isReleaseBuild(); also update the parsing in BuildInfo.kt (the
isReleaseBuild() implementation in
core-base/security/src/desktopMain/kotlin/kpt/core/base/security/BuildInfo.kt)
to use toBooleanStrictOrNull() ?: false to defensively handle malformed values.

In `@cmp-web/build.gradle.kts`:
- Around line 78-83: The build currently silently falls back when an explicit
-PkmpFlavor value doesn't match any flavor (activeFlavor -> activeFlavorConfig),
resulting in an empty webTitleSuffix; change this to fail-fast: detect when
findProperty("kmpFlavor") returns a non-null String but
kmpFlavorExt.flavors.findByName(activeFlavor) yields null, and throw a clear
exception (e.g., GradleException) naming the invalid flavor and listing valid
flavor names so the build fails immediately instead of stripping suffixes;
update the logic around activeFlavor, activeFlavorConfig and webTitleSuffix
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7f37d664-94fe-44bf-9a24-9a0849bad53a

📥 Commits

Reviewing files that changed from the base of the PR and between 6b4e992 and 7adb6cc.

📒 Files selected for processing (12)
  • build-logic/convention/src/main/kotlin/GenerateIosFlavorXcconfigsTask.kt
  • build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt
  • build-logic/convention/src/main/kotlin/SyncForkConfigPlugin.kt
  • build-logic/convention/src/main/kotlin/local/LocalFlavors.kt
  • cmp-desktop/build.gradle.kts
  • cmp-ios/Configs/demoDebug.xcconfig
  • cmp-ios/Configs/demoRelease.xcconfig
  • cmp-ios/Configs/demoStaging.xcconfig
  • cmp-ios/Configs/prodDebug.xcconfig
  • cmp-ios/Configs/prodRelease.xcconfig
  • cmp-ios/Configs/prodStaging.xcconfig
  • cmp-web/build.gradle.kts
✅ Files skipped from review due to trivial changes (2)
  • cmp-ios/Configs/prodRelease.xcconfig
  • cmp-ios/Configs/demoDebug.xcconfig
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmp-ios/Configs/prodStaging.xcconfig

Comment on lines +129 to +146
private fun Project.registerIosFlavorXcconfigsTask() {
val ext = extensions.getByType<KmpFlavorExtension>()

val generateTask = tasks.register<GenerateIosFlavorXcconfigsTask>("generateIosFlavorXcconfigs") {
group = "kmp-flavors"
description = "Auto-generate cmp-ios/Configs/{variant}.xcconfig from kmpFlavors DSL."

// Lazy providers — Gradle reads these at execution time after DSL is fully
// configured (including LocalFlavors.kt additions).
flavorBundleSuffixes.set(provider {
ext.flavors.associate { f -> f.name to (f.bundleIdSuffix.orNull ?: "") }
})
buildTypeBundleSuffixes.set(provider {
ext.buildTypes.associate { bt -> bt.name to (bt.bundleIdSuffix.orNull ?: "") }
})
// Relative to :cmp-shared — cmp-ios is a sibling module.
outputDir.set(layout.projectDirectory.dir("../cmp-ios/Configs"))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent multi-project clobbering of cmp-ios/Configs.

Line 129 registers this generator in every project that applies the plugin, and Line 145 points all of them to the same output directory. In a multi-module build, that creates multiple writers for the same files and can produce nondeterministic outputs when tasks are run together.

Suggested fix
 private fun Project.registerIosFlavorXcconfigsTask() {
+    if (path != ":cmp-shared") return
+
     val ext = extensions.getByType<KmpFlavorExtension>()

Also applies to: 150-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build-logic/convention/src/main/kotlin/KMPFlavorsConventionPlugin.kt` around
lines 129 - 146, The registerIosFlavorXcconfigsTask currently registers a
GenerateIosFlavorXcconfigsTask in every project and points outputDir to a shared
../cmp-ios/Configs, causing multi-project writers; change the code so the
generator is only created once (e.g., register it on the rootProject or guard
with if (project != rootProject) return) or register via
rootProject.tasks.register(...) and keep all providers (flavorBundleSuffixes,
buildTypeBundleSuffixes) reading from the extension, and set outputDir from the
root project layout; apply the same single-registration guard to the other
similar generator block (the second task registration at the other location) so
no project-level clobbering occurs.

Comment on lines +51 to +55
val activeFlavor: String = (findProperty("kmpFlavor") as? String)
?: kmpFlavorExt.flavors.find { it.isDefault.getOrElse(false) }?.name
?: "prod"
val activeFlavorConfig = kmpFlavorExt.flavors.findByName(activeFlavor)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast for unknown -PkmpFlavor values.

An explicit but invalid flavor currently resolves to null config and silently builds with unsuffixed identity values. That can produce misbranded release artifacts instead of failing early.

Suggested fix
 val activeFlavor: String = (findProperty("kmpFlavor") as? String)
     ?: kmpFlavorExt.flavors.find { it.isDefault.getOrElse(false) }?.name
     ?: "prod"
 val activeFlavorConfig = kmpFlavorExt.flavors.findByName(activeFlavor)
+if (findProperty("kmpFlavor") != null && activeFlavorConfig == null) {
+    error("Unknown -PkmpFlavor='$activeFlavor'. Valid values: ${kmpFlavorExt.flavors.map { it.name }}")
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val activeFlavor: String = (findProperty("kmpFlavor") as? String)
?: kmpFlavorExt.flavors.find { it.isDefault.getOrElse(false) }?.name
?: "prod"
val activeFlavorConfig = kmpFlavorExt.flavors.findByName(activeFlavor)
val activeFlavor: String = (findProperty("kmpFlavor") as? String)
?: kmpFlavorExt.flavors.find { it.isDefault.getOrElse(false) }?.name
?: "prod"
val activeFlavorConfig = kmpFlavorExt.flavors.findByName(activeFlavor)
if (findProperty("kmpFlavor") != null && activeFlavorConfig == null) {
error("Unknown -PkmpFlavor='$activeFlavor'. Valid values: ${kmpFlavorExt.flavors.map { it.name }}")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-desktop/build.gradle.kts` around lines 51 - 55, The build currently
allows an explicit but invalid -PkmpFlavor value to yield a null
activeFlavorConfig and proceed; update the logic around activeFlavor and
activeFlavorConfig to fail fast: after computing activeFlavor and looking up
activeFlavorConfig via kmpFlavorExt.flavors.findByName(activeFlavor), check if
activeFlavorConfig is null and throw a clear GradleException (or call error())
that includes the invalid activeFlavor and a list of valid flavors (from
kmpFlavorExt.flavors.map { it.name }) to stop the build and guide the user;
reference activeFlavor and activeFlavorConfig to locate where to add this
null-check and exception.

languageVersion.set(JavaLanguageVersion.of(libs.versions.jvmToolchain.get().toInt()))
}.get().metadata.installationPath.asFile.absolutePath
jvmArgs("-Dapp.name=$appName")
jvmArgs("-Dapp.name=$windowTitle")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Where app.release is read:"
fd -i 'BuildInfo.kt' --exec rg -n -C2 'app\.release|System\.getProperty' {}

echo
echo "Where app.release is set in Gradle scripts:"
fd -i 'build.gradle.kts' --exec rg -n -C2 'app\.release|jvmArgs\(' {}

Repository: openMF/kmp-project-template

Length of output: 412


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search: any usage of app.release in Gradle/Kotlin build scripts ==="
rg -n --hidden --glob '!*node_modules/*' --glob '*.gradle.kts' --glob '*.gradle' --glob 'gradle.properties' 'app\.release' .

echo
echo "=== Search: jvmArgs occurrences for desktop target (esp. cmp-desktop) ==="
rg -n --hidden --glob '*.gradle.kts' 'jvmArgs\(' cmp-desktop || true

echo
echo "=== Inspect cmp-desktop/build.gradle.kts around line ~68 ==="
if [ -f "cmp-desktop/build.gradle.kts" ]; then
  nl -ba cmp-desktop/build.gradle.kts | sed -n '45,95p'
else
  echo "Missing cmp-desktop/build.gradle.kts"
fi

echo
echo "=== Locate any other BuildInfo.kt files that read app.release ==="
rg -n --hidden --glob 'BuildInfo.kt' 'app\.release' .

echo
echo "=== Search for -Dapp.release in all gradle scripts ==="
rg -n --hidden --glob '*.gradle.kts' --glob '*.gradle' '-Dapp\.release' . || true

Repository: openMF/kmp-project-template

Length of output: 143


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== List cmp-desktop Gradle files ==="
ls -la cmp-desktop || true
find cmp-desktop -maxdepth 2 -type f -name '*.gradle.kts' -o -name '*.gradle' 2>/dev/null || true

echo
echo "=== Inspect cmp-desktop/build.gradle.kts around line ~68 ==="
if [ -f "cmp-desktop/build.gradle.kts" ]; then
  nl -ba cmp-desktop/build.gradle.kts | sed -n '55,85p'
else
  echo "Missing cmp-desktop/build.gradle.kts"
fi

echo
echo "=== Search for app.release / -Dapp.release in Gradle scripts (non-fatal if none) ==="
rg -n --hidden --glob '!*node_modules/*' --glob '*.gradle.kts' --glob '*.gradle' 'app\.release' . || true
rg -n --hidden --glob '!*node_modules/*' --glob '*.gradle.kts' --glob '*.gradle' '-Dapp\.release' . || true

echo
echo "=== Search for jvmArgs in cmp-desktop/build.gradle.kts ==="
rg -n --hidden --glob '*.gradle.kts' 'jvmArgs\(' cmp-desktop/build.gradle.kts || true

echo
echo "=== Locate any jvmArgs that might be conditional on release/debug ==="
if [ -f "cmp-desktop/build.gradle.kts" ]; then
  rg -n --hidden 'jvmArgs\(|release|debug|isReleaseBuild|app\.name' cmp-desktop/build.gradle.kts || true
fi

echo
echo "=== Locate any other BuildInfo.kt files that read app.release ==="
rg -n --hidden --glob 'BuildInfo.kt' 'app\.release' . || true

Repository: openMF/kmp-project-template

Length of output: 744


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Inspect cmp-desktop/build.gradle.kts around line ~55-85 ==="
if [ -f "cmp-desktop/build.gradle.kts" ]; then
  cat -n cmp-desktop/build.gradle.kts | sed -n '55,90p'
else
  echo "Missing cmp-desktop/build.gradle.kts"
fi

echo
echo "=== Search for app.release / -Dapp.release in Gradle scripts ==="
rg -n --hidden --glob '!*node_modules/*' --glob '*.gradle.kts' --glob '*.gradle' 'app\.release' . || true
rg -n --hidden --glob '!*node_modules/*' --glob '*.gradle.kts' --glob '*.gradle' '-Dapp\.release' . || true

echo
echo "=== Search for jvmArgs( occurrences in cmp-desktop/build.gradle.kts ==="
rg -n --hidden --glob '*.gradle.kts' 'jvmArgs\(' cmp-desktop/build.gradle.kts || true

echo
echo "=== Search for any release/debug conditionals + app.name usage in cmp-desktop/build.gradle.kts ==="
rg -n --hidden '(release|debug|isReleaseBuild|app\.name|jvmArgs)' cmp-desktop/build.gradle.kts || true

Repository: openMF/kmp-project-template

Length of output: 2931


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search whole repo for app.release / Dapp.release ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'app\.release' .
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'Dapp\.release' .
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' '-Dapp\.release' . || true

echo
echo "=== Search whole repo for jvmArgs that set app.* system props ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'jvmArgs\("?-Dapp\.' . || true
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'systemProperty\("app\.' . || true
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'app\.name|appVersion|windowTitle' cmp-desktop/build.gradle.kts || true

echo
echo "=== Inspect desktop BuildInfo.kt around isReleaseBuild() ==="
FILE='core-base/security/src/desktopMain/kotlin/template/core/base/security/BuildInfo.kt'
if [ -f "$FILE" ]; then
  nl -ba "$FILE" | sed -n '1,80p'
else
  echo "Missing $FILE"
fi

Repository: openMF/kmp-project-template

Length of output: 1658


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Locate all BuildInfo.kt files under core-base/security desktopMain ==="
fd -i -g 'BuildInfo.kt' core-base/security/src/desktopMain

echo
echo "=== Inspect the matching BuildInfo.kt for isReleaseBuild() ==="
# Print first 120 lines (should be small); adjust if huge.
for f in $(fd -i -g 'BuildInfo.kt' core-base/security/src/desktopMain -x echo); do
  echo "--- $f (head) ---"
  head -n 140 "$f" | nl -ba
done

echo
echo "=== Search repo for setting -Dapp.release / app.release=true (fix rg option parsing) ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' --fixed-string -- '-Dapp.release' .
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' --fixed-string -- 'app.release=true' .
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'app\.release' --glob '*.gradle*' --glob '*.kt' --glob '*.kts' --glob '*.properties' || true

echo
echo "=== Search compose desktop jvmArgs for app.release specifically ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'jvmArgs\([^)]*app\.release' . || true

Repository: openMF/kmp-project-template

Length of output: 443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="core-base/security/src/desktopMain/kotlin/kpt/core/base/security/BuildInfo.kt"
echo "=== Show $FILE (first 160 lines) ==="
sed -n '1,160p' "$FILE" | cat -n

echo
echo "=== Search repo for setting -Dapp.release / app.release=true (proper rg option parsing) ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' --fixed-string -- '-Dapp.release' .
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' --fixed-string -- 'app.release=true' .

echo
echo "=== Search repo for jvmArgs (any) that might include app.release conditionals ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'jvmArgs\(' .

echo
echo "=== Search all gradle/kts/build scripts for app.release usage ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' --glob '*.gradle*' --glob '*.kts' 'app\.release' .

echo
echo "=== Inspect desktop build wiring file for any release-specific jvmArgs ==="
for f in cmp-desktop/build.gradle.kts compose-desktop.pro build.gradle.kts; do
  if [ -f "$f" ]; then
    echo "--- $f: lines 1-220 ---"
    sed -n '1,220p' "$f" | cat -n
  fi
done

Repository: openMF/kmp-project-template

Length of output: 1524


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search repo for '-Dapp.release' / 'app.release=true' ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' -- '-Dapp.release' .
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' -- 'app.release=true' .
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' -- 'app.release' .

echo
echo "=== Search repo for jvmArgs usage that might include app.release ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'jvmArgs\(' . || true
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'jvmArgs\([^)]*app\.release' . || true

echo
echo "=== Inspect cmp-desktop/build.gradle.kts around jvmArgs ==="
sed -n '55,90p' cmp-desktop/build.gradle.kts | cat -n

echo
echo "=== Inspect any other gradle scripts in repo for jvmArgs lines ==="
fd -i -g '*.gradle.kts' . | wc -l
fd -i -g '*.gradle.kts' . | xargs -I{} sh -c 'rg -n -- "{}" "jvmArgs\(" 2>/dev/null || true' | head -n 80

Repository: openMF/kmp-project-template

Length of output: 3026


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Inspect cmp-desktop/build.gradle.kts around buildTypes.release (line ~90-140) ==="
sed -n '90,160p' cmp-desktop/build.gradle.kts | cat -n

echo
echo "=== Search repo for isReleaseBuild() usage ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'isReleaseBuild\(' .

echo
echo "=== Search for app.release being set via other config (system properties) ==="
rg -n --hidden --glob '!*node_modules/*' --glob '!.git/*' 'System\.getProperty\("app\.release"\)|app\.release' . || true

Repository: openMF/kmp-project-template

Length of output: 3023


Ensure desktop release packaging injects -Dapp.release=true

  • cmp-desktop/build.gradle.kts currently sets only jvmArgs("-Dapp.name=$windowTitle") and never sets -Dapp.release=true.
  • Desktop isReleaseBuild() reads System.getProperty("app.release") and defaults to false when the property is absent, so release builds will be treated as non-release.
  • Add jvmArgs("-Dapp.release=true") to the desktop release packaging/build wiring (and consider switching parsing in core-base/security/src/desktopMain/kotlin/kpt/core/base/security/BuildInfo.kt to toBooleanStrictOrNull() ?: false to avoid malformed values).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-desktop/build.gradle.kts` at line 68, Add the JVM system property for
release builds by updating the desktop packaging to include
jvmArgs("-Dapp.release=true") alongside the existing
jvmArgs("-Dapp.name=$windowTitle") so System.getProperty("app.release") is set
for isReleaseBuild(); also update the parsing in BuildInfo.kt (the
isReleaseBuild() implementation in
core-base/security/src/desktopMain/kotlin/kpt/core/base/security/BuildInfo.kt)
to use toBooleanStrictOrNull() ?: false to defensively handle malformed values.

Comment thread cmp-web/build.gradle.kts
Comment on lines +78 to +83
val activeFlavor: String = (findProperty("kmpFlavor") as? String)
?: kmpFlavorExt.flavors.find { it.isDefault.getOrElse(false) }?.name
?: "prod"
val activeFlavorConfig = kmpFlavorExt.flavors.findByName(activeFlavor)
val webTitleSuffix = activeFlavorConfig?.webTitleSuffix?.orNull ?: ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unknown -PkmpFlavor instead of silently stripping suffixes.

If the supplied flavor name is invalid, webTitleSuffix becomes empty and the build proceeds with default branding semantics. Fail early when an explicit property is provided but not found.

Suggested fix
 val activeFlavor: String = (findProperty("kmpFlavor") as? String)
     ?: kmpFlavorExt.flavors.find { it.isDefault.getOrElse(false) }?.name
     ?: "prod"
 val activeFlavorConfig = kmpFlavorExt.flavors.findByName(activeFlavor)
+if (findProperty("kmpFlavor") != null && activeFlavorConfig == null) {
+    error("Unknown -PkmpFlavor='$activeFlavor'. Valid values: ${kmpFlavorExt.flavors.map { it.name }}")
+}
 val webTitleSuffix = activeFlavorConfig?.webTitleSuffix?.orNull ?: ""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val activeFlavor: String = (findProperty("kmpFlavor") as? String)
?: kmpFlavorExt.flavors.find { it.isDefault.getOrElse(false) }?.name
?: "prod"
val activeFlavorConfig = kmpFlavorExt.flavors.findByName(activeFlavor)
val webTitleSuffix = activeFlavorConfig?.webTitleSuffix?.orNull ?: ""
val activeFlavor: String = (findProperty("kmpFlavor") as? String)
?: kmpFlavorExt.flavors.find { it.isDefault.getOrElse(false) }?.name
?: "prod"
val activeFlavorConfig = kmpFlavorExt.flavors.findByName(activeFlavor)
if (findProperty("kmpFlavor") != null && activeFlavorConfig == null) {
error("Unknown -PkmpFlavor='$activeFlavor'. Valid values: ${kmpFlavorExt.flavors.map { it.name }}")
}
val webTitleSuffix = activeFlavorConfig?.webTitleSuffix?.orNull ?: ""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-web/build.gradle.kts` around lines 78 - 83, The build currently silently
falls back when an explicit -PkmpFlavor value doesn't match any flavor
(activeFlavor -> activeFlavorConfig), resulting in an empty webTitleSuffix;
change this to fail-fast: detect when findProperty("kmpFlavor") returns a
non-null String but kmpFlavorExt.flavors.findByName(activeFlavor) yields null,
and throw a clear exception (e.g., GradleException) naming the invalid flavor
and listing valid flavor names so the build fails immediately instead of
stripping suffixes; update the logic around activeFlavor, activeFlavorConfig and
webTitleSuffix accordingly.

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