31e73e3865
The v1.09.1 desktop release (DMG/MSI/DEB/RPM) fails to launch on every
platform with:
Exception in thread "main" java.lang.ExceptionInInitializerError
at com.fasterxml.jackson.databind.ObjectMapper.<init>(ObjectMapper.java:700)
at com.vitorpamplona.amethyst.desktop.ui.deck.DeckState.<clinit>
Caused by: java.lang.NullPointerException:
Cannot read the array length because the return value of
"java.lang.Class.getEnumConstants()" is null
at com.fasterxml.jackson.databind.cfg.MapperConfig
.collectFeatureDefaults(MapperConfig.java:107)
Failed to launch JVM
Root cause: PR #2914 wired Compose 1.11.0's new ProGuard pass into the
release build but added only `-dontwarn` rules. ProGuard then optimized
Jackson's enum classes (`SerializationFeature`, `MapperFeature`, etc.) and
stripped their synthetic `values()` / `valueOf()` methods because nothing
in Amethyst's own bytecode calls them. Jackson does call them — but
reflectively, at `ObjectMapper` construction time, long after ProGuard
finished. The result is `Class.getEnumConstants()` returning null and the
JVM giving up.
The same regression silently broke VLC media playback (VLCj's
`ServiceLoader` lookup of `DiscoveryDirectoryProvider` impls printed
`ConfigDirConfigFileDiscoveryDirectoryProvider not found` because
ProGuard stripped both the META-INF/services file and the impl class).
Fix: add explicit `-keep` rules for the four reflection-heavy libraries
on the desktop classpath:
- Jackson (databind, core, annotations, module-kotlin): keep classes
intact and keep `values()`/`valueOf()` on every Jackson enum.
- JNA (com.sun.jna.\*\*): used by VLCj and kmp-tor — its `Structure`
field-order reflection requires field metadata to survive.
- VLCj (uk.co.caprica.vlcj.\*\*): `ServiceLoader`-based discovery providers
and reflective binding classes throughout.
- SLF4J: detected by Jackson via `Class.forName`.
Verified by building `:desktopApp:createReleaseDistributable` and
launching the resulting bundle on macOS arm64:
- No `ExceptionInInitializerError`
- `VLC: bundled discovery succeeded`
- `VLC: MediaPlayerFactory created successfully`
The same ProGuard pass runs on all four target formats (Dmg, Msi, Deb,
Rpm) so this single change fixes the regression on every desktop OS.
111 lines
6.0 KiB
Prolog
111 lines
6.0 KiB
Prolog
# ProGuard rules consumed by Compose Multiplatform's `proguardReleaseJars`
|
|
# task (see `compose.desktop.application.buildTypes.release.proguard` wiring
|
|
# in build.gradle.kts).
|
|
#
|
|
# Compose Multiplatform 1.11.0 ships a stricter default ruleset than 1.10.x
|
|
# and now treats unresolved references as fatal. The warnings below come from
|
|
# third-party libraries that have always carried optional code paths; they
|
|
# were silently tolerated on 1.10.3 and broke desktop packaging on 1.11.0.
|
|
#
|
|
# This file MUST keep rules for libraries that rely on reflection at runtime,
|
|
# in addition to the dontwarn rules for compile-time noise. Otherwise the
|
|
# release build will package classes whose reflectively-required methods
|
|
# (`values()`/`valueOf()` on enums, JNA `Structure` field accessors, VLCj
|
|
# discovery providers loaded via Class.forName) have been stripped or
|
|
# renamed, and the app crashes at startup. See the v1.09.1 desktop-DMG
|
|
# regression for an example: Jackson's `MapperConfig.collectFeatureDefaults`
|
|
# called `SerializationFeature.values()` reflectively, got
|
|
# `NoSuchMethodException` (because ProGuard had removed it as "unused"),
|
|
# and the JVM failed to launch.
|
|
|
|
# ============================================================================
|
|
# Jackson — required to keep app launchable
|
|
# ============================================================================
|
|
# Jackson uses reflection extensively at construction time (ObjectMapper,
|
|
# JsonMapper, SerializationConfig, DeserializationConfig). It enumerates
|
|
# feature enums via `Class.getEnumConstants()` -> `values()`, scans for
|
|
# `@JsonProperty`/`@JsonCreator`/etc. annotations on user types, and looks
|
|
# up sub-classes by name through `Class.forName`. None of that survives an
|
|
# optimize+shrink pass without explicit keep rules.
|
|
-keep class com.fasterxml.jackson.databind.** { *; }
|
|
-keep class com.fasterxml.jackson.annotation.** { *; }
|
|
-keep class com.fasterxml.jackson.core.** { *; }
|
|
-keep class com.fasterxml.jackson.module.kotlin.** { *; }
|
|
-keepclassmembers enum com.fasterxml.jackson.** {
|
|
public static **[] values();
|
|
public static ** valueOf(java.lang.String);
|
|
}
|
|
-keepattributes Signature,*Annotation*,EnclosingMethod,InnerClasses
|
|
# Jackson kotlin-module 2.21.x value-class converters call
|
|
# `MethodHandle.invokeExact(...)` with polymorphic signatures that ProGuard
|
|
# cannot resolve against the abstract MethodHandle declaration. Safe to
|
|
# suppress: only used reflectively when (de)serializing Kotlin `value class`
|
|
# types.
|
|
-dontwarn com.fasterxml.jackson.module.kotlin.**
|
|
|
|
# ============================================================================
|
|
# JNA + JNA platform — required by VLCj, kmp-tor, and any native binding
|
|
# ============================================================================
|
|
# JNA loads native libraries by reflecting on Structure subclasses (field
|
|
# order discovered via `getDeclaredFields`), reads JNA callback interfaces by
|
|
# name, and uses `Native.register` against class metadata. Without -keep,
|
|
# `Native.<clinit>` and `Structure.getFields` both fail at runtime.
|
|
-keep class com.sun.jna.** { *; }
|
|
-keep class * implements com.sun.jna.** { *; }
|
|
-keepclassmembers class * extends com.sun.jna.Structure {
|
|
<fields>;
|
|
}
|
|
# Suppress the inner-class accessor warnings from the new strict ruleset.
|
|
# `com.sun.jna.internal.Cleaner$CleanerThread` references package-private
|
|
# synthetic accessors (`access$100` …) that ProGuard's class member analysis
|
|
# cannot follow through nested inner classes. The accessors exist at runtime.
|
|
-dontwarn com.sun.jna.internal.Cleaner
|
|
-dontwarn com.sun.jna.internal.Cleaner$*
|
|
|
|
# ============================================================================
|
|
# VLCj — discovery providers loaded reflectively via java.util.ServiceLoader
|
|
# ============================================================================
|
|
# VLCj enumerates implementations of
|
|
# `uk.co.caprica.vlcj.factory.discovery.provider.DiscoveryDirectoryProvider`
|
|
# via the ServiceLoader mechanism, which reads class names from
|
|
# META-INF/services/* and resolves them with `Class.forName(...).newInstance()`.
|
|
# ProGuard's default settings strip both the service files and the impl
|
|
# classes (since nothing references them by name in user code). Keep
|
|
# everything VLCj ships; VLCj is reflection-heavy throughout.
|
|
-keep class uk.co.caprica.vlcj.** { *; }
|
|
-keepclassmembers class * implements uk.co.caprica.vlcj.factory.discovery.provider.DiscoveryDirectoryProvider {
|
|
public <init>(...);
|
|
}
|
|
-keepclassmembers class * implements uk.co.caprica.vlcj.factory.discovery.provider.DiscoveryProvider {
|
|
public <init>(...);
|
|
}
|
|
|
|
# ============================================================================
|
|
# OkHttp — optional TLS adapters discovered via Class.forName
|
|
# ============================================================================
|
|
# okhttp ships TLS adapters for GraalVM native-image, Conscrypt, BouncyCastle,
|
|
# OpenJSSE, and Jetty boot. None of these are on the desktop classpath; okhttp
|
|
# falls back to the JDK default via `Class.forName(...)`-style probes that
|
|
# catch NoClassDefFoundError. Suppress the warnings without keeping the
|
|
# unused classes.
|
|
-dontwarn okhttp3.internal.graal.**
|
|
-dontwarn okhttp3.internal.platform.BouncyCastlePlatform
|
|
-dontwarn okhttp3.internal.platform.BouncyCastlePlatform$*
|
|
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
|
-dontwarn okhttp3.internal.platform.ConscryptPlatform$*
|
|
-dontwarn okhttp3.internal.platform.OpenJSSEPlatform
|
|
-dontwarn okhttp3.internal.platform.OpenJSSEPlatform$*
|
|
-dontwarn org.conscrypt.**
|
|
-dontwarn org.bouncycastle.**
|
|
-dontwarn org.openjsse.**
|
|
-dontwarn org.graalvm.**
|
|
-dontwarn com.oracle.svm.**
|
|
|
|
# ============================================================================
|
|
# SLF4J / logging — used reflectively by transitive deps
|
|
# ============================================================================
|
|
# jackson-databind and a few other deps `Class.forName("org.slf4j.LoggerFactory")`
|
|
# to detect logging. We ship slf4j-nop; keep it intact so detection succeeds.
|
|
-keep class org.slf4j.** { *; }
|
|
-dontwarn org.slf4j.**
|