Skip to content
gradle-expert logo

Gradle Expert

gradle-expert

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 dependencie...

vitorpamplona/amethyst0installs1.6Kstars

SKILL.md

Full skill instructions

Gradle Expert

Build system expertise for AmethystMultiplatform's 10-module KMP architecture (amethyst, benchmark, quartz, geode, commons, quic, nestsClient, desktopApp, cli, quic-interop — see settings.gradle.kts). Focus: practical troubleshooting, dependency resolution, and project-specific optimizations.

Build Architecture Mental Model

The core app stack is 4 layers (the other modules hang off it: cli and geode are JVM apps over commons/quartz, nestsClient sits on quic, benchmark and quic-interop are test harnesses):

┌─────────────┬─────────────┐
│ :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 (always re-check gradle/​libs.versions.toml — these drift):

composeMultiplatform = "1.11.0"  # Plugin + runtime
composeBom = "2026.05.01"        # AndroidX Compose BOM
kotlin = "2.3.21"

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.

More skills from vitorpamplona

feed-patterns logo
vitorpamplona/amethyst

feed-patterns

Feed composition and data-access layer patterns in Amethyst. Use when adding or modifying a feed (home, profile, hashtag, bookmarks, notifications, DMs, communities), working with `FeedFilter` / `AdditiveComplexFeedFilter` / `ChangesFlowFilter` / `FilterByListParams` in `amethyst/.../ui/dal/`, or...

1.6K 0
View
quartz-integration logo
vitorpamplona/amethyst

quartz-integration

Integration guide for using the Quartz Nostr KMP library in external projects. Use when: (1) adding Quartz as a Gradle dependency, (2) setting up NostrClient with WebSocket, (3) creating/signing/sending events, (4) building relay subscriptions with Filter, (5) handling keys with KeyPair/NostrSign...

1.6K 0
View
amy-expert logo
vitorpamplona/amethyst

amy-expert

Patterns for extending `amy`, the Amethyst CLI in `cli/`. Use when adding an `amy <verb>` command, touching files under `cli/src/main/kotlin/…/cli/`, wiring a new subcommand into `Main.kt`, writing an interop test script that drives Amy, or extracting logic out of `amethyst/` into `commons/` so a...

1.6K 0
View
auth-signers logo
vitorpamplona/amethyst

auth-signers

Signer abstraction patterns in Amethyst. Use when working with event signing, choosing between a local keypair (`NostrSignerInternal`), a remote NIP-46 bunker signer (`NostrSignerRemote`), or a NIP-55 Android external-app signer (`NostrSignerExternal`). Covers the abstract `NostrSigner` base clas...

1.6K 0
View
account-state logo
vitorpamplona/amethyst

account-state

Account state and in-memory event store patterns in Amethyst. Use when working with `Account.kt` (per-user StateFlow properties — follow list, relays, settings, mutes, bookmarks), `LocalCache` (the object-level event store backed by `LargeCache`), `User`/`Note` model classes, or any ViewModel tha...

1.6K 0
View
android-expert logo
vitorpamplona/amethyst

android-expert

Android platform patterns for the `amethyst/` module. Use when working with (1) Android navigation (Navigation Compose, type-safe routes, bottom nav), (2) runtime permissions (camera, notifications, biometrics), (3) platform APIs (Intent, Context, Activity, ContentResolver), (4) Material3 theming...

1.6K 0
View
kotlin-coroutines logo
vitorpamplona/amethyst

kotlin-coroutines

Advanced Kotlin coroutines patterns for AmethystMultiplatform. Use when working with: (1) Structured concurrency (supervisorScope, coroutineScope), (2) Advanced Flow operators (flatMapLatest, combine, merge, shareIn, stateIn), (3) Channels and callbackFlow, (4) Dispatcher management and context s...

1.6K 0
View
relay-client logo
vitorpamplona/amethyst

relay-client

Subscription and filter-assembly patterns for the Amethyst relay client layer in `commons/.../relayClient/`. Use when working with compose-scoped subscriptions (`ComposeSubscriptionManager`, `Subscribable`), filter assemblers (`MetadataFilterAssembler`, `ReactionsFilterAssembler`, `FeedMetadataCo...

1.6K 0
View
Amethyst logo
vitorpamplona/amethyst

Amethyst

Build customized Amethyst Nostr clients for Android. Fork, rebrand, customize, and distribute your own version.

1.6K 0
View
kotlin-expert logo
vitorpamplona/amethyst

kotlin-expert

Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1)...

1.6K 0
View
find-missing-translations logo
vitorpamplona/amethyst

find-missing-translations

Use when comparing Android strings.xml locale files to find untranslated string resources, missing translation keys, or preparing translation work for a specific language

1.6K 0
View
find-non-lambda-logs logo
vitorpamplona/amethyst

find-non-lambda-logs

Use when auditing or migrating Log calls — flags both interpolated Log.d/i/w/e that should use the lambda overload (allocation hygiene) and catch-block Log.w/e that interpolate ${e.message} but drop the throwable (lost stack traces)

1.6K 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View