mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 11:48:24 +00:00
fix(desktop): fix release build ProGuard rules that crash app on launch
ProGuard in release builds (packageReleaseDmg/Deb/Rpm) strips classes accessed via reflection, JNI, or service loading — causing multiple runtime failures: - Jackson ExceptionInInitializerError: enum constants stripped, NPE in MapperConfig.collectFeatureDefaults() (#2929, 5000 sats bounty) - JNA/VLCJ: static methods stripped, SIGABRT in native callbacks - secp256k1/SQLite/kmp-tor: JNI loader classes stripped - Coil/okhttp/okio: image loading and networking broken - Kotlin metadata stripped: Jackson can't call default constructors Changes: - Upgrade ProGuard 7.7.0 → 7.9.1 (Kotlin 2.3 metadata support) - Disable optimization (-dontoptimize) to prevent bytecode rewriting that produces VerifyError (Guardsquare/proguard#460) - Add -keep rules for all JNI/reflection-dependent libraries - Keep Kotlin @Metadata annotations for Jackson deserialization - Suppress Kotlin 2.3 compile-time stub warnings Tested: release DMG builds and launches without crash on macOS. Closes #2929 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
34f5dcac69
commit
935ad84ac3
@@ -91,6 +91,9 @@ compose.desktop {
|
||||
|
||||
jvmArgs += "-Xmx2g"
|
||||
|
||||
// VLC plugin path fallback — used if JNA setenv and bundled discovery both fail
|
||||
jvmArgs += "-Dvlc.plugin.path=\$APPDIR/resources/vlc/plugins"
|
||||
|
||||
// Forward platform-preview overrides from the gradle invocation to the
|
||||
// launched app's JVM so `./gradlew :desktopApp:run -Damethyst.platform=GNOME`
|
||||
// works in addition to the env-var form (`AMETHYST_PLATFORM=GNOME`).
|
||||
@@ -101,7 +104,15 @@ compose.desktop {
|
||||
nativeDistributions {
|
||||
appResourcesRootDir.set(project.layout.projectDirectory.dir("src/jvmMain/appResources"))
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.Rpm)
|
||||
modules("java.management") // Required by kmp-tor TorRuntime
|
||||
// Output of ./gradlew suggestRuntimeModules (+ java.management already present)
|
||||
modules(
|
||||
"java.instrument", // Runtime instrumentation (agent/profiler hooks)
|
||||
"java.management", // Required by kmp-tor TorRuntime
|
||||
"java.prefs", // java.util.prefs (desktop persistence)
|
||||
"java.sql", // JDBC metadata (Jackson, SQLite driver)
|
||||
"jdk.security.auth", // JAAS authentication callbacks
|
||||
"jdk.unsupported", // sun.misc.Unsafe (VLCJ ByteBufferFactory)
|
||||
)
|
||||
|
||||
packageName = "Amethyst"
|
||||
packageVersion = appVersion
|
||||
@@ -143,6 +154,7 @@ compose.desktop {
|
||||
// whose declared return type the JVM verifier rejects (R8 doesn't hit
|
||||
// this — it generates bridges differently from ProGuard).
|
||||
buildTypes.release.proguard {
|
||||
version.set("7.9.1") // Kotlin 2.3 metadata support
|
||||
configurationFiles.from(project.file("compose-rules.pro"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,16 @@
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# kmp-tor — loads native Tor daemon via JNI reflection
|
||||
-keep class io.matthewnelson.** { *; }
|
||||
|
||||
# Coil image loader — uses ServiceLoader for decoder/fetcher registration
|
||||
-keep class coil3.** { *; }
|
||||
|
||||
# OkHttp/Okio — platform detection and I/O via reflection
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep class okio.** { *; }
|
||||
|
||||
# ============================================================================
|
||||
# Optimize sub-pass — disable the one that produces invalid okio bytecode
|
||||
# ============================================================================
|
||||
@@ -185,3 +195,9 @@
|
||||
# to detect logging. We ship slf4j-nop; keep it intact so detection succeeds.
|
||||
-keep class org.slf4j.** { *; }
|
||||
-dontwarn org.slf4j.**
|
||||
|
||||
# ============================================================================
|
||||
# Kotlin 2.3 stdlib stubs — compile-time classes with no JVM runtime class
|
||||
# ============================================================================
|
||||
-dontwarn kotlin.concurrent.atomics.**
|
||||
-dontwarn kotlin.jvm.internal.EnhancedNullability
|
||||
|
||||
+27
-2
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.desktop.service.media
|
||||
|
||||
import com.sun.jna.Function
|
||||
import com.sun.jna.NativeLibrary
|
||||
import uk.co.caprica.vlcj.binding.lib.LibC
|
||||
import uk.co.caprica.vlcj.binding.support.runtime.RuntimeUtil
|
||||
import uk.co.caprica.vlcj.factory.discovery.strategy.BaseNativeDiscoveryStrategy
|
||||
|
||||
@@ -36,6 +36,14 @@ class MacOsVlcDiscoverer :
|
||||
arrayOf("libvlc\\.dylib", "libvlccore\\.dylib"),
|
||||
arrayOf("%s/plugins"),
|
||||
) {
|
||||
/** Plugin path discovered during [setPluginPath], available after discovery. */
|
||||
var discoveredPluginPath: String? = null
|
||||
private set
|
||||
|
||||
/** Whether [setPluginPath] successfully set the process env var. */
|
||||
var envVarSet: Boolean = false
|
||||
private set
|
||||
|
||||
override fun supported(): Boolean {
|
||||
val os = System.getProperty("os.name").lowercase()
|
||||
return "mac" in os
|
||||
@@ -52,5 +60,22 @@ class MacOsVlcDiscoverer :
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setPluginPath(pluginPath: String?): Boolean = LibC.INSTANCE.setenv(PLUGIN_ENV_NAME, pluginPath, 1) == 0
|
||||
override fun setPluginPath(pluginPath: String?): Boolean {
|
||||
if (pluginPath == null) return false
|
||||
discoveredPluginPath = pluginPath
|
||||
return try {
|
||||
// Call setenv directly via JNA Function API. This bypasses vlcj's
|
||||
// LibC interface binding which fails on macOS 13+ because dlsym
|
||||
// can't resolve the versioned symbol `setenv$3b99ba0d`.
|
||||
val setenv = Function.getFunction("c", "setenv")
|
||||
val result = setenv.invokeInt(arrayOf<Any>(PLUGIN_ENV_NAME, pluginPath, 1)) == 0
|
||||
envVarSet = result
|
||||
result
|
||||
} catch (_: Throwable) {
|
||||
// JNA Function call also failed — VlcjPlayerPool will use
|
||||
// --plugin-path factory arg as fallback.
|
||||
envVarSet = false
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user