kmp-build-check
Skill15 starsChanged 24 days agoApache-2.0View on GitHub
Use when you need to know whether a Kotlin Multiplatform change still builds and passes tests on both Android and iOS - "did I break iOS?", before pushing or opening a PR, after editing commonMain, iosMain, expect/actual, Gradle files or the version catalog, when a library change may break the apps that consume it, or when CI fails only on the iOS framework link or the iOS simulator tests.
In this repository34 other files it gives its agents.
- AGENTS.mdAGENTS.md
- android-to-kmp-migrationSkill
- compose-design-system-componentSkill
- compose-multiplatform-localizationSkill
- compose-multiplatform-navigationSkill
- compose-multiplatform-screenSkill
- figma-to-mobile-uiSkill
- kmp-api-endpointSkill
- kmp-build-failure-triageSkill
- kmp-ci-github-actionsSkill
- kmp-code-reviewSkill
- kmp-crash-triageSkill
- kmp-data-layerSkill
- kmp-dev-machine-cleanupSkill
- kmp-expect-actualSkill
- kmp-feature-moduleSkill
- kmp-flaky-test-triageSkill
- kmp-gradle-version-catalogSkill
- kmp-koin-diSkill
- kmp-module-healthSkill
- kmp-room-databaseSkill
- kmp-secure-storageSkill
- kmp-swiftui-interopSkill
- kmp-test-coverage-backfillSkill
- kmp-testing-patternsSkill
- kmp-xcframework-distributionSkill
- mobile-accessibility-test-idsSkill
- mobile-bug-fixSkill
- mobile-feature-implementSkill
- mobile-feature-planSkill
- mobile-performance-fixSkill
- mobile-platform-paritySkill
- mobile-releaseSkill
- mobile-security-reviewSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 7 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 20
Before pushing or opening a pull request on a KMP repository.
- Very specific: line 21
After a change to commonMain, iosMain, expect/actual, a build.gradle.kts, settings.gradle.kts or gradle/libs.versions.toml.
- Distinctive: line 24
When the user asks "does it still build on iOS?" or "did I break anything?".
- Distinctive: line 55
NOT RUN. Say so up front, so nobody reads a partial run as a full one.
- Distinctive: line 90
Run: Use a full run before a release or a tag.
- Distinctive: line 129
When the merge-base, the remote branch or the module layout cannot be found or read, the tool runs the unscoped plan and prints a warning.
- Distinctive: line 170
Every NOT RUN carries its reason.
- Distinctive: line 175
If CI runs the iOS half on macOS runners, say that CI can close the gap.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
KMP build check
A green Android build proves nothing about iOS. iosMain, actual declarations, cinterop and the
framework link are compiled only by Kotlin/Native tasks, so an Android-only build passes while the
iOS half is broken. This skill runs each check as its own Gradle invocation and reports every one as
PASS (Gradle exited 0), FAIL (Gradle exited non-zero) or NOT RUN (with the reason).
When to use
- Before pushing or opening a pull request on a KMP repository.
- After a change to
commonMain,iosMain,expect/actual, abuild.gradle.kts,settings.gradle.ktsorgradle/libs.versions.toml. - In a library repository: to rebuild the apps that consume the library against your local checkout.
- When the user asks "does it still build on iOS?" or "did I break anything?".
When not to use
- Writing or fixing tests: use
kmp-testing-patterns. - Raising Kover coverage: use
kmp-test-coverage-backfill. - UI-test locators and accessibility IDs: use
mobile-accessibility-test-ids. - Running the app on a device or simulator to see a change: this skill only builds and tests.
- Diagnosing why a build fails (linker, KSP, Xcode errors): use
kmp-build-failure-triage. This skill reports which check failed and its first error. - Setting up CI: use
kmp-ci-github-actions. Reviewing the code itself: usekmp-code-review.
Step 0 - Read the project first
- Find the Gradle root: the directory with
gradlewandsettings.gradle(.kts). A monorepo with several Gradle roots gets one run per root. - Look for
kmp-build-check.jsonat that root. If it exists, it is the source of truth. Never replace its tasks with task names from memory. - No config yet? Propose one (see Workflow step 1). Show it to the user before writing it into their repository.
- Read the CI configuration (
.github/workflows/,.gitlab-ci.yml, and so on). Prefer the tasks CI already runs, so that a local PASS predicts a CI pass. Note which Android plugin each module uses. It decides the unit-test task name:
Module setup Android/JVM unit-test task com.android.library+androidTarget()testDebugUnitTestcom.android.kotlin.multiplatform.librarytestAndroidHostTest, and only ifwithHostTest {}is configured. Host tests are off by default.jvm()targetjvmTest(<name>Testforjvm("<name>"))- Check the host. On macOS run
xcrun simctl list runtimes. Without Xcode the iOS checks are NOT RUN. Say so up front, so nobody reads a partial run as a full one.
Workflow
<skill-dir> below is the directory that contains this SKILL.md. Run the commands from the Gradle root.
Get a config.
python3 <skill-dir>/scripts/run_checks.py --detect gradle # reads ./gradlew tasks --all python3 <skill-dir>/scripts/run_checks.py --detect static # reads build files onlyBoth modes print a proposal and never overwrite a file. Add
--writeto save it askmp-build-check.json. The two modes trade accuracy for speed:Mode Strength Weakness gradleReal task names, including those made by convention plugins, flavours and the AGP KMP plugin Configures the whole build (seconds to minutes). Needs the Android SDK and dependency resolution to succeed. staticInstant and offline Guesses. Blind to convention plugins in build-logic/, to flavour names and to CocoaPods task names.Review the proposal: pick one flavour's
assemble<Flavour>Debug, and delete checks CI does not need. Copyassets/kmp-build-check.example.jsonwhen you want to write the file by hand. The schema and the consumer setup are in references/configuration.md. Done whenrun_checks.py --validateprintsvalid.Dry-run the plan.
python3 <skill-dir>/scripts/run_checks.py --dry-runDone when every check reads
dry run: would run ./gradlew ..., or shows a NOT RUN reason that you understand (no wrapper, consumer not checked out).Run. Use a full run before a release or a tag. For day-to-day work, scope the run to the diff:
python3 <skill-dir>/scripts/run_checks.py # every check python3 <skill-dir>/scripts/run_checks.py --base main # only what the diff vs origin/main can affect python3 <skill-dir>/scripts/run_checks.py --json build/kmp-build-check/result.jsonChecks run one after another. Never start two Gradle builds in the same project at once: they fight over the same locks and both slow down or time out. Done when the summary line
N PASS, N FAIL, N NOT RUNis printed.- Read every FAIL. The table shows the first compiler error (
File.kt:line:col message), the first failed test, or Gradle's "What went wrong" text. The full log path is in the JSONlogfield. Quote that line. Do not paste the whole log. Done when each FAIL has a one-line cause. - Fix only when asked. This skill reports. If the user asks for a fix, make it, run the failing
task directly (
./gradlew <task>) until it passes, then run the tool again so the final report comes from one complete run. - Report. Give the table, the summary line, and for each NOT RUN its reason and who can close it. Example: "iOS checks NOT RUN: this machine has no Xcode. CI's macOS job will run them."
What --base skips
--base <branch> fetches <remote>/<branch> and diffs against the merge-base with it. It never uses
the local branch, because a stale or unpushed local main changes the diff. Use --base-ref <ref>
to diff against an exact ref with no fetch. The diff counts committed, uncommitted and untracked files.
| What changed | Effect |
Only Android source sets (androidMain, src/main, androidHostTest ...) | iOS checks: NOT RUN |
Only Kotlin Apple source sets (iosMain, appleMain, nativeMain ...) | Android build and unit tests: NOT RUN |
| Only platform source sets | compileCommonMainKotlinMetadata: NOT RUN |
No build file, dependency, cinterop .def or Apple source set | the framework link becomes compileKotlin<Target> (faster; it does not link) |
| No build file changed, module layout readable | test tasks run only in the changed modules that have test sources |
| Only docs, CI, scripts, or only Swift / Xcode files | every check: NOT RUN, with that reason |
When the merge-base, the remote branch or the module layout cannot be found or read, the tool runs the unscoped plan and prints a warning. It never fails open into skipping checks.
Quick reference
| Flag | Effect |
--dry-run | Print the plan; run nothing |
--only android / --only ios | One platform's checks, plus the any ones |
--base BRANCH / --base-ref REF | Scope the run to the diff (see above) |
--json PATH | Write results as JSON; - sends JSON to stdout and the table to stderr |
--clean | Build a temporary git worktree of HEAD: committed code only |
--init-submodules | Run git submodule update --init --recursive when a submodule is missing |
--no-consumers | Skip the consumer checks |
--timeout-minutes N | Per Gradle run (default 45). A timeout is NOT RUN, never PASS. |
--validate | Check kmp-build-check.json and exit |
--detect gradle|static (+ --write, --force) | Propose a config |
| Check kind | Typical task | Catches |
| common | compileCommonMainKotlinMetadata | commonMain compile errors, expect without actual |
| android_build | :composeApp:assembleDebug, compileAndroidMain | androidMain, Android resources, manifest merge |
| unit_test | testDebugUnitTest, testAndroidHostTest, jvmTest | commonTest on the JVM, plus Android or JVM test sets |
| ios_link | linkDebugFrameworkIosSimulatorArm64 | iosMain, actuals, cinterop, linker flags, exported API |
| ios_test | iosSimulatorArm64Test | commonTest and iosTest on Kotlin/Native |
Exit status: 0 no FAIL, 1 at least one FAIL, 2 bad usage or config. Exit 0 with NOT RUN checks
is not "all green".
Verify
python3 <skill-dir>/scripts/run_checks.py --validate
python3 <skill-dir>/scripts/run_checks.py --json -
python3 -m unittest discover -s <skill-dir>/scripts -p "test_*.py" # the tool's own testsReporting rules:
- A check is PASS only when its Gradle process exited 0. The tool never reads log text to decide a pass.
- Every NOT RUN carries its reason. Some reasons are about the environment: no Xcode, not macOS,
no simulator runtime, no Android SDK, an uninitialised submodule, a timeout, or a consumer that is
not checked out. Others are about scope: the diff does not reach the check, or
--onlyfiltered it. - Quote the summary line verbatim. Never say "everything passes" while any check is NOT RUN. Name the checks that did not run.
- If CI runs the iOS half on macOS runners, say that CI can close the gap. Do not imply it has.
Common mistakes
| Mistake | Why it bites | Fix |
Treating assembleDebug as "it builds" | Kotlin/Native never compiled iosMain or linked the framework | Keep an iOS link check in every config |
| Task names from memory | A missing task FAILs and looks like a real break (testAndroidHostTest vs testDebugUnitTest) | --detect gradle, or ./gradlew <module>:tasks --all |
Diffing against a local main | Unpushed or stale commits hide or add changes | --base main always uses the fetched origin/main |
Untracked .kt files in the working tree | The build passes locally; the commit does not compile in CI | Commit them, or run with --clean |
| Reporting NOT RUN as passed | The iOS half was never compiled | Name every NOT RUN and its reason |
Using --base right before a release | The link is downgraded to a compile for common-only diffs | Run unscoped before tagging |
| Checking a library change by building the app normally | The app resolves the published artifact, not your edit | Add a consumer with a substitution property (references/configuration.md) |
AGP KMP library plugin without withHostTest {} | commonTest never runs on the JVM side, so no unit-test task exists | Add withHostTest {} in the module's android {} block |
iosArm64Test as the iOS test task | Device-target test binaries cannot run on the Mac | Use iosSimulatorArm64Test, or iosX64Test on an Intel Mac |
| Two builds in one project at once | Gradle lock contention; one build waits, then times out | Run checks sequentially (the tool does) |
Related skills
kmp-testing-patterns: write the tests these checks run.kmp-test-coverage-backfill: raise coverage after the build is green.mobile-accessibility-test-ids: stable locators for UI and end-to-end tests.kmp-build-failure-triage: diagnose a FAIL this skill reports.kmp-ci-github-actions: run the same checks on CI runners, including macOS for iOS.- android/skills: official Android agent skills.
- skydoves/android-testing-skills: Android testing skills.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

