vitorpamplona

gradle-expert

1,327
179
# Install this skill:
npx skills add vitorpamplona/amethyst --skill "gradle-expert"

Install specific skill from multi-skill repository

# 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).

# SKILL.md


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)  // Type-safe, IDE-autocompleted
}

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

# Full builds
./gradlew build                    # All modules
./gradlew clean build              # Clean build

# Desktop
./gradlew :desktopApp:run          # Run desktop app
./gradlew :desktopApp:packageDmg   # macOS package

# Module-specific
./gradlew :quartz:build            # KMP library only
./gradlew :commons:build           # Shared UI only

# Analysis
./gradlew dependencies             # Dependency tree
./gradlew build --scan             # Online diagnostics

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:

// commonMain - API only
api(libs.secp256k1.kmp.common)

// androidMain - Android JNI
api(libs.secp256k1.kmp.jni.android)

// jvmMain - Desktop JVM JNI
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:

// androidMain
implementation("com.goterl:lazysodium-android:5.2.0@aar")  // @aar explicit
implementation("net.java.dev.jna:jna:5.18.1@aar")

// jvmMain
implementation(libs.lazysodium.java)  // JAR implicit
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"  # Plugin + runtime
composeBom = "2025.12.01"       # AndroidX Compose BOM
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):

// βœ… Use Compose Multiplatform
implementation(compose.ui)
implementation(compose.material3)

// ❌ DON'T use AndroidX BOM in KMP modules
// implementation(libs.androidx.compose.bom)

In Android-only modules (amethyst):

// Can use AndroidX BOM
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)

Desktop Packaging Basics

TargetFormat options:

// In desktopApp/build.gradle.kts
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  # macOS
./gradlew :desktopApp:packageMsi  # Windows
./gradlew :desktopApp:packageDeb  # Linux

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
# Report: build/reports/profile/profile-<timestamp>.html

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:
1. Align versions in libs.versions.toml (preferred)
2. 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.

// ❌ Wrong
commonMain {
    dependencies {
        implementation(libs.jackson.module.kotlin)  // JVM-only!
    }
}

// βœ… Correct
val jvmAndroid = create("jvmAndroid") {
    dependsOn(commonMain.get())
    dependencies {
        api(libs.jackson.module.kotlin)  // JVM code, shared by Android + Desktop
    }
}

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:

# Check current Java
java -version  # Should show 21

# Set JAVA_HOME
export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home

# Stop Gradle daemon to pick up new Java
./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

# Check dependencies for specific module
./gradlew :quartz:dependencies

# Find specific library in dependency tree
./gradlew dependencyInsight --dependency okhttp

# Build with detailed logging
./gradlew build --info

# Generate interactive build scan (best diagnostics)
./gradlew build --scan

# Profile build performance
./gradlew clean build --profile

# Stop all Gradle daemons (fresh start)
./gradlew --stop

# Check Gradle version
./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:
1. 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" }
  1. Add to build file (quartz/build.gradle.kts):
sourceSets {
    commonMain {
        dependencies {
            implementation(libs.kotlinx.datetime)  // KMP library, goes in commonMain
        }
    }
}
  1. 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
# Shows: secp256k1-kmp-jni-android  ← WRONG!

Fix:

// In quartz/build.gradle.kts
jvmMain {
    dependencies {
        // Change from:
        // implementation(libs.secp256k1.kmp.jni.android)  ❌

        // To:
        implementation(libs.secp256k1.kmp.jni.jvm)  // βœ…
    }
}

Verify:

./gradlew :desktopApp:dependencies --configuration runtimeClasspath | grep secp256k1
# Now shows: secp256k1-kmp-jni-jvm  βœ…

./gradlew :desktopApp:run  # Should work

Example 3: Optimizing Build Time

Current: Clean build takes 5 minutes

Steps:
1. Baseline measurement:

./gradlew clean build --profile
# Check: build/reports/profile/profile-*.html
  1. 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
  1. 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

  1. Centralize versions: Never hardcode versions in build.gradle.kts. Always use libs.versions.toml.

  2. Respect source set hierarchy: Dependencies flow downward. jvmAndroid depends on commonMain, never the reverse.

  3. Platform-specific variants matter: secp256k1, JNA must use correct variant per platform. Check when errors occur.

  4. Clean builds are expensive: Use incremental compilation. Only clean when truly needed (source set changes, version updates).

  5. Compose alignment is critical: Compose Multiplatform version must match Kotlin version. Check compatibility matrix.

  6. Proguard for native libs: All JNI libraries need explicit -keep rules in release builds.

  7. Java 21 everywhere: All modules, all targets, consistent JVM version.

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.