From cb5498ae4c932e1a20fe87fe84a915358f668faa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 01:39:08 +0000 Subject: [PATCH] perf(cli): drop Compose UI render stack from the amy runtime image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amy is headless and compiles against zero Compose UI (the Compose deps are `implementation` in :commons, so they never hit the CLI compile classpath), but they still rode the runtime classpath into the shipped image — ~29 MB of Compose desktop render stack, including skiko's native .dylibs that enlarged the macOS notarization surface. Exclude skiko + the org.jetbrains.compose UI groups (ui/foundation/material/ material3/animation) from :cli runtimeClasspath. Keep androidx.compose.runtime (snapshot state + @Stable/@Immutable) — that IS CLI-safe and used by commons models/state. This avoids the commons → commons/commons-ui module split: the single-module, feature-cohesive design (commons/ARCHITECTURE.md §1/§3) is preserved; only the runtime artifact is trimmed. Result: amy image lib 77 MB -> 48 MB (-38%), and all 4 Compose/skiko notary dylibs gone (only secp256k1/jna/sqlite natives remain — the ones actually loaded). A create-release.yml assertion fails the build if the UI stack ever leaks back. Verified with the SDK hidden + an offline amy command battery (init/whoami/ --json/relay/marmot/login, plus a real 6-relay key-package round-trip): zero NoClassDefFoundError/linkage errors; init derives a secp256k1 key cleanly. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD --- .github/workflows/create-release.yml | 18 ++++++++++++++++++ cli/build.gradle.kts | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 39b6394407..e923e01ac2 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -282,6 +282,24 @@ jobs: timeout_minutes: 15 command: ./gradlew --no-daemon :cli:${{ matrix.tasks }} + # amy is headless: the Compose UI render stack (skiko + its native dylibs, + # foundation/material/material3/ui/animation) must never reach the CLI + # image. cli/build.gradle.kts excludes it from runtimeClasspath; this + # guards against a transitive dep silently dragging it back (size + macOS + # notarization-surface regression). compose.runtime is CLI-safe and stays. + - name: Assert no Compose UI in the amy image + run: | + set -euo pipefail + LIB="cli/build/install/amy/lib" + leak="$(ls "$LIB" | grep -iE 'skiko|foundation(-layout)?-desktop|material3?-desktop|material-ripple|ui-desktop|animation(-core)?-desktop' || true)" + if [ -n "$leak" ]; then + echo "::error::Compose UI render stack leaked into the amy CLI image:" + echo "$leak" | sed 's/^/ /' + echo "Exclude it in cli/build.gradle.kts (configurations.runtimeClasspath)." + exit 1 + fi + echo "OK: no skiko / Compose UI render jars in the amy image ($(du -sh "$LIB" | cut -f1))." + # macOS only: import the Developer ID cert (no-op without the secret) so # the next step can codesign the jlink image. The jvm bundle for # Homebrew-core is NOT signed here — Homebrew strips quarantine itself. diff --git a/cli/build.gradle.kts b/cli/build.gradle.kts index 9296e49d5b..5fdf00d2fd 100644 --- a/cli/build.gradle.kts +++ b/cli/build.gradle.kts @@ -30,6 +30,28 @@ dependencies { implementation(libs.slf4j.nop) } +// amy is headless. It compiles against zero Compose UI (the Compose deps are +// `implementation` in :commons, so they never reach the CLI compile classpath — +// verified), yet they still ride the *runtime* classpath into the shipped image: +// ~29 MB of Compose desktop render stack, including skiko's native .dylibs that +// needlessly enlarge the macOS notarization surface. None of it is reachable +// from a CLI command, so drop the whole UI layer from the runtime image. Keep +// `androidx.compose.runtime` — snapshot state + the @Stable/@Immutable +// annotations ARE CLI-safe and used by commons models/state holders (see +// commons/ARCHITECTURE.md §1). A regression here is caught by the "no Compose UI +// in the amy image" assertion in .github/workflows/create-release.yml. +configurations.named("runtimeClasspath") { + // skiko (native renderer + its .dylibs) and the Compose UI bytecode layer. + // NB: Compose Multiplatform publishes UI under org.jetbrains.compose.* — only + // `runtime` relocates to androidx.compose.runtime, which we deliberately keep. + exclude(group = "org.jetbrains.skiko") + exclude(group = "org.jetbrains.compose.ui") + exclude(group = "org.jetbrains.compose.foundation") + exclude(group = "org.jetbrains.compose.material") + exclude(group = "org.jetbrains.compose.material3") + exclude(group = "org.jetbrains.compose.animation") +} + application { mainClass.set("com.vitorpamplona.amethyst.cli.MainKt") applicationName = "amy"