Skip to main content
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 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).
インストールへ移動 Skills Marketplace コミュニティが作成したAIスキルを発見・探索
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
直接コマンドでは確認用 Prompt が省略されます。実行前にソースを確認してください。
npx skills add https://github.com/vitorpamplona/amethyst --skill gradle-expertコマンドは1行のまま表示されます。コピー前に横へスクロールして全体を確認してください。
ローカルで確認しますか?SkillsMP が現在取得できるファイルをダウンロードできます。
Zipをダウンロード ダウンロード中... このリポジトリの他の Skills 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`, `FeedMetadataCoordinator`), preloaders (`MetadataPreloader`, `MetadataRateLimiter`), EOSE managers, or any feature that needs to talk to relays lifecycle-aware from a composable. Complements `nostr-expert` (protocol filter syntax) and `kotlin-coroutines` (callbackFlow patterns).
The NIP-85 trusted-assertions model in Quartz (`nip85TrustedAssertions/`) — kind 10040 trust-provider lists, kind 30382 contact cards / user assertions, 30383 event assertions, 30384 addressable assertions, 30385 external-id assertions. Use when building or parsing these events, working with the typed tags (RankTag, HopsTag, FollowerCountTag, ServiceProviderTag/ServiceType, …), wiring a consumer that resolves a 10040 provider entry to the 30382s it signs, ranking on assertion values, or touching the GrapeRank publisher, contact-card nicknames, or the trust projection of an external store.
The NIP-50 indexing surface of Quartz — the `SearchableEvent` interface, which event kinds are searchable, exactly what text each kind's `indexableContent()` contributes, how the SQLite/filesystem stores consume it, and the NIP-50 `SearchQuery` extension grammar plus `SearchRelayListEvent` (kind 10007). Use when making a kind searchable, changing what a kind indexes, diffing the searchable set at a Quartz version bump (external search engines mirror this table), debugging why an event is or isn't found by search, or working with search extensions (`include:spam`, `domain:`, …).
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 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."
[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" }
dependencies {
implementation(libs.okhttp)
}
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
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
Module Structure & Dependencies
Dependency Flow :desktopApp → :commons (jvmMain) → :quartz (jvmMain → jvmAndroid → commonMain)
: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)
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)
Critical Dependency Patterns
1. secp256k1 (Crypto Library) The problem: KMP library with platform-specific JNI bindings. Wrong variant = runtime crash.
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.
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 (always re-check gradle/libs.versions.toml — these drift):
composeMultiplatform = "1.11.1"
composeBom = "2026.05.01"
kotlin = "2.3.21"
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
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" ))
}
}
./gradlew :desktopApp:packageDmg
./gradlew :desktopApp:packageMsi
./gradlew :desktopApp:packageDeb
macOS: desktopApp/build/compose/binaries/main/dmg/
Windows: desktopApp/build/compose/binaries/main/msi/
Linux: desktopApp/build/compose/binaries/main/deb/
macOS: .icns (multi-resolution: 512, 256, 128, 32)
Windows: .ico (256, 128, 64, 32, 16)
Linux: .png (512x512)
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.
./gradlew clean build --profile
After changing version catalog
After adding/removing source sets
When seeing unexplained errors
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
./gradlew dependencyInsight --dependency <library-name>
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.
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
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
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)
}
}
}
./gradlew :quartz:dependencies | grep datetime
Example 2: Fixing secp256k1 Error on Desktop Error: UnsatisfiedLinkError: no secp256k1jni in java.library.path when running desktop app
./gradlew :desktopApp:dependencies --configuration runtimeClasspath | grep secp256k1
jvmMain {
dependencies {
implementation(libs.secp256k1.kmp.jni.jvm)
}
}
./gradlew :desktopApp:dependencies --configuration runtimeClasspath | grep secp256k1
./gradlew :desktopApp:run
Example 3: Optimizing Build Time Current: Clean build takes 5 minutes
./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
./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.