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"