| name | gradle-expert |
| description | Build optimization, dependency resolution, and multi-module KMP troubleshooting for AmethystMultiplatform. Use when working with: (1) Gradle build files (build.gradle.kts, settings.gradle), (2) Version catalog (libs.versions.toml), (3) Build errors and dependency conflicts, (4) Module dependencies and source sets, (5) Desktop packaging (DMG/MSI/DEB), (6) Build performance optimization, (7) Proguard/R8 configuration, (8) Common KMP + Android Gradle issues (Compose conflicts, secp256k1 JNI variants, source set problems). |
Gradle Expert
Build system expertise for AmethystMultiplatform's 4-module KMP architecture. Focus: practical troubleshooting, dependency resolution, and project-specific optimizations.
Build Architecture Mental Model
Think of this project as 4 layers:
┌─────────────┬─────────────┐
│ :amethyst │ :desktopApp │ ← Platform apps (navigation, layouts)
│ (Android) │ (JVM) │
└──────┬──────┴──────┬──────┘
│ │
└──────┬──────┘
▼
┌─────────────┐
│ :commons │ ← Shared UI (KMP with jvmAndroid)
│ (KMP UI) │
└──────┬──────┘
▼
┌─────────────┐
│ :quartz │ ← Core library (KMP: Android/JVM/iOS)
│(KMP Library)│
└─────────────┘
Key insight: Dependencies flow DOWN. Lower modules never depend on upper modules. This enables code sharing without circular dependencies.
The jvmAndroid pattern: Unique to this project. A custom source set between commonMain and {androidMain, jvmMain} for JVM-specific code shared by Android and Desktop. Not standard KMP, but critical for this architecture.
Version Catalog Philosophy
All dependencies centralized in gradle/libs.versions.toml. Think "single source of truth."
Pattern:
[versions]
kotlin = "2.3.0"
[libraries]
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
Usage:
dependencies {
implementation(libs.okhttp)
}
Critical alignments:
- Kotlin ecosystem: All Kotlin plugins MUST share same version
- Compose ecosystem: Compose Multiplatform version → Kotlin version (check compatibility matrix)
- secp256k1 variants: All three variants (common, jni-android, jni-jvm) MUST share same version
See references/version-catalog-guide.md for comprehensive patterns.
Common Build Tasks
Quick Reference
./gradlew build
./gradlew clean build
./gradlew :desktopApp:run
./gradlew :desktopApp:packageDmg
./gradlew :quartz:build
./gradlew :commons:build
./gradlew dependencies
./gradlew build --scan
See references/build-commands.md for comprehensive command reference.
Module Structure & Dependencies
Dependency Flow
Desktop build chain:
:desktopApp → :commons (jvmMain) → :quartz (jvmMain → jvmAndroid → commonMain)
Android build chain:
:amethyst → :commons (androidMain) → :quartz (androidMain → jvmAndroid → commonMain)
Key source set pattern (quartz & commons):
commonMain # Truly cross-platform code
│
├─ jvmAndroid # JVM-specific, shared by Android + Desktop
│ ├─ androidMain
│ └─ jvmMain
│
└─ iosMain # iOS-specific (quartz only)
Dependency config types:
- Use
api when types appear in module's public API or expect/actual declarations
- Use
implementation for internal implementation details
- Example: quartz exposes secp256k1 (
api), but hides okhttp (implementation)
See references/dependency-graph.md for module visualization and transitive dependency flow.
Critical Dependency Patterns
1. secp256k1 (Crypto Library)
The problem: KMP library with platform-specific JNI bindings. Wrong variant = runtime crash.
Pattern:
api(libs.secp256k1.kmp.common)
api(libs.secp256k1.kmp.jni.android)
implementation(libs.secp256k1.kmp.jni.jvm)
Why api in androidMain? Types leak to consumers (:amethyst).
Common error: Desktop using jni-android variant → UnsatisfiedLinkError: no secp256k1jni in java.library.path
Fix: Check source set dependencies. jvmMain must use jni-jvm, never jni-android.
2. JNA (for LibSodium Encryption)
The problem: Android needs AAR packaging, JVM needs JAR. Same library, different artifact types.
Pattern:
implementation("com.goterl:lazysodium-android:5.2.0@aar")
implementation("net.java.dev.jna:jna:5.18.1@aar")
implementation(libs.lazysodium.java)
implementation(libs.jna)
Critical: Never put JNA in jvmAndroid or commonMain. Platform-specific packaging only.
3. Compose Versions
The problem: Two Compose ecosystems (Multiplatform + AndroidX) must align, or duplicate classes.
Current project config:
composeMultiplatform = "1.9.3"
composeBom = "2025.12.01"
kotlin = "2.3.0"
Rule: Compose Multiplatform version must be compatible with Kotlin version. Check: https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compatibility-and-versioning.html
In KMP modules (quartz, commons):
implementation(compose.ui)
implementation(compose.material3)
In Android-only modules (amethyst):
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
Desktop Packaging Basics
TargetFormat options:
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "Amethyst"
packageVersion = "1.0.0"
macOS {
bundleID = "com.vitorpamplona.amethyst.desktop"
iconFile.set(project.file("src/jvmMain/resources/icon.icns"))
}
}
Package tasks:
./gradlew :desktopApp:packageDmg
./gradlew :desktopApp:packageMsi
./gradlew :desktopApp:packageDeb
Output locations:
- macOS:
desktopApp/build/compose/binaries/main/dmg/
- Windows:
desktopApp/build/compose/binaries/main/msi/
- Linux:
desktopApp/build/compose/binaries/main/deb/
Icon requirements:
- macOS:
.icns (multi-resolution: 512, 256, 128, 32)
- Windows:
.ico (256, 128, 64, 32, 16)
- Linux:
.png (512x512)
Common issues:
- Main class not found → Verify
mainClass = "...MainKt" (Kotlin adds Kt suffix)
- Native libs missing → Ensure secp256k1-kmp-jni-jvm in dependencies
- Icon not found → Check file exists at path, use absolute path if needed
Build Performance Optimization
Add to gradle.properties:
# Daemon (faster subsequent builds)
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g
# Parallel execution (multi-module speedup)
org.gradle.parallel=true
org.gradle.workers.max=8
# Caching (incremental builds)
org.gradle.caching=true
org.gradle.configuration-cache=true
# Kotlin daemon
kotlin.incremental=true
kotlin.daemon.jvmargs=-Xmx2g
Impact: Typically 30-50% faster builds after first run.
Measure impact:
./gradlew clean build --profile
When to clean build:
- After changing version catalog
- After adding/removing source sets
- When seeing unexplained errors
When NOT to clean:
- Regular development iteration
- Small code changes
- Incremental compilation works fine
Use script: scripts/analyze-build-time.sh for automated profiling.
Troubleshooting: Practical Patterns
Pattern 1: Version Conflict
Symptom: Duplicate class or NoSuchMethodError
Diagnosis:
./gradlew dependencyInsight --dependency <library-name>
Fix options:
- Align versions in libs.versions.toml (preferred)
- Force resolution:
configurations.all {
resolutionStrategy {
force(libs.okhttp.get().toString())
}
}
Pattern 2: Source Set Issues
Symptom: Unresolved reference to JVM library in shared code
Diagnosis: Check source set hierarchy. JVM-only libs (jackson, okhttp) can't be in commonMain.
Fix: Move to jvmAndroid or platform-specific source set.
commonMain {
dependencies {
implementation(libs.jackson.module.kotlin)
}
}
val jvmAndroid = create("jvmAndroid") {
dependsOn(commonMain.get())
dependencies {
api(libs.jackson.module.kotlin)
}
}
Pattern 3: Proguard Stripping Native Libs
Symptom: NoClassDefFoundError for secp256k1, JNA, or LibSodium in release builds
Fix: Update proguard rules in quartz/proguard-rules.pro:
# Native libraries
-keep class fr.acinq.secp256k1.** { *; }
-keep class com.goterl.lazysodium.** { *; }
-keep class com.sun.jna.** { *; }
# Jackson (reflection-based)
-keep class com.vitorpamplona.quartz.** { *; }
-keepattributes *Annotation*
-keepattributes Signature
Pattern 4: Compose Compiler Mismatch
Symptom: IllegalStateException: Version mismatch: runtime 1.10.0 but compiler 1.9.0
Fix: Update Compose Multiplatform version in libs.versions.toml to match Kotlin version compatibility.
Check: https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compatibility-and-versioning.html
Pattern 5: Wrong JVM Target
Symptom: Unsupported class file major version 65
Fix: Ensure Java 21 everywhere:
java -version
export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
./gradlew --stop
Verify all build files use JVM 21:
kotlin {
jvm {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
}
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
}
Comprehensive Error Guide
For detailed troubleshooting of specific errors, see references/common-errors.md. Covers:
- Compose version conflicts
- secp256k1 JNI errors
- Source set dependency issues
- Proguard/R8 problems
- Desktop packaging errors
- Kotlin compilation errors
- Dependency resolution failures
- JVM/JDK version issues
Each error includes: symptom, cause, solution, verification steps.
Quick Diagnostic Commands
./gradlew :quartz:dependencies
./gradlew dependencyInsight --dependency okhttp
./gradlew build --info
./gradlew build --scan
./gradlew clean build --profile
./gradlew --stop
./gradlew --version
Scripts & References
Diagnostic Scripts
scripts/analyze-build-time.sh - Profile build performance, generate optimization report
scripts/fix-dependency-conflicts.sh - Diagnose common dependency conflicts, suggest fixes
Reference Docs
references/build-commands.md - Comprehensive command reference for all tasks
references/dependency-graph.md - Module dependencies, source set hierarchy, transitive deps
references/version-catalog-guide.md - Version catalog patterns, usage, best practices
references/common-errors.md - Troubleshooting guide for frequent build issues
Workflow Examples
Example 1: Adding New Dependency
Task: Add kotlinx.datetime to quartz
Steps:
- Update version catalog (gradle/libs.versions.toml):
[versions]
kotlinxDatetime = "0.6.0"
[libraries]
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
- Add to build file (quartz/build.gradle.kts):
sourceSets {
commonMain {
dependencies {
implementation(libs.kotlinx.datetime)
}
}
}
- Sync & verify:
./gradlew :quartz:dependencies | grep datetime
Example 2: Fixing secp256k1 Error on Desktop
Error: UnsatisfiedLinkError: no secp256k1jni in java.library.path when running desktop app
Diagnosis:
./gradlew :desktopApp:dependencies --configuration runtimeClasspath | grep secp256k1
Fix:
jvmMain {
dependencies {
implementation(libs.secp256k1.kmp.jni.jvm)
}
}
Verify:
./gradlew :desktopApp:dependencies --configuration runtimeClasspath | grep secp256k1
./gradlew :desktopApp:run
Example 3: Optimizing Build Time
Current: Clean build takes 5 minutes
Steps:
- Baseline measurement:
./gradlew clean build --profile
- Add optimizations to gradle.properties:
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.jvmargs=-Xmx4g
kotlin.incremental=true
- Re-measure:
./gradlew clean build --profile
Expected improvement: 30-50% faster on subsequent builds (incremental builds much faster).
Delegation Patterns
When to delegate to other skills:
- Source set architecture (jvmAndroid pattern, expect/actual) → Use
kotlin-multiplatform skill
- Compose UI issues (composables, state management) → Use
compose-expert skill (when available)
- Kotlin language issues (Flow, sealed classes, DSLs) → Use
kotlin-expert skill
- Desktop-specific features (Window management, MenuBar, tray) → Use
desktop-expert skill
This skill handles: Build system, dependencies, versioning, module structure, packaging, performance.
Core Principles for This Build System
-
Centralize versions: Never hardcode versions in build.gradle.kts. Always use libs.versions.toml.
-
Respect source set hierarchy: Dependencies flow downward. jvmAndroid depends on commonMain, never the reverse.
-
Platform-specific variants matter: secp256k1, JNA must use correct variant per platform. Check when errors occur.
-
Clean builds are expensive: Use incremental compilation. Only clean when truly needed (source set changes, version updates).
-
Compose alignment is critical: Compose Multiplatform version must match Kotlin version. Check compatibility matrix.
-
Proguard for native libs: All JNI libraries need explicit -keep rules in release builds.
-
Java 21 everywhere: All modules, all targets, consistent JVM version.