realm-kotlin icon indicating copy to clipboard operation
realm-kotlin copied to clipboard

Document Proguard configuration for non-Android JVM builds

Open alexmaryin opened this issue 2 years ago • 15 comments

Application with Realm database (KMM project for Desktop and Android) causes crush at start if package is built with Release version on any desktop platform (I have only tested on Win 10 and Ubuntu). The same project which built as 'Debug' version (Gradle task <package*> without word 'release') starts normally without any logging in Console. Crush is accompanied with the following output in log:

Exception in thread "main" java.lang.ExceptionInInitializerError at ComposableSingletons$MainKt$lambda-1$1$1.invoke(main.kt:1016) at org.koin.core.context.GlobalContext.startKoin(GlobalContext.kt:64) at ComposableSingletons$MainKt$lambda-1$1.invoke(main.kt:3031) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.ui.window.Application_desktopKt$application$1$1.invoke(Application.desktop.kt:2115) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1$1.invoke(Application.desktop.kt:1226) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:21107) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228) at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1.invoke(Application.desktop.kt:1221) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:21107) at AppKt.invokeComposable(App.kt:26073) at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:4248) at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341) at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:50175) at androidx.compose.runtime.ComposerImpl.composeContent$runtime(Composer.kt:3173) at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:587) at androidx.compose.runtime.Recomposer.composeInitial$runtime$5576a047(Recomposer.kt:950) at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519) at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2.invokeSuspend(Application.desktop.kt:219) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at java.desktop/java.awt.event.InvocationEvent.dispatch(Unknown Source) at java.desktop/java.awt.EventQueue.dispatchEventImpl(Unknown Source) at java.desktop/java.awt.EventQueue$4.run(Unknown Source) at java.desktop/java.awt.EventQueue$4.run(Unknown Source) at java.base/java.security.AccessController.doPrivileged(Unknown Source) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source) at java.desktop/java.awt.EventQueue.dispatchEvent(Unknown Source) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.desktop/java.awt.EventDispatchThread.run(Unknown Source) Caused by: java.lang.IllegalStateException: Incomplete hierarchy for class BaseRealmObject, unresolved classes [io.realm.kotlin.Deleteable] at kotlin.reflect.jvm.internal.impl.descriptors.runtime.components.RuntimeErrorReporter.reportIncompleteHierarchy(RuntimeErrorReporter.kt:26) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassTypeConstructor.computeSupertypes(DeserializedClassDescriptor.kt:272) at kotlin.reflect.jvm.internal.impl.types.AbstractTypeConstructor$supertypes$1.invoke(AbstractTypeConstructor.kt:78) at kotlin.reflect.jvm.internal.impl.types.AbstractTypeConstructor$supertypes$1.invoke(AbstractTypeConstructor.kt:77) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValueWithPostCompute.invoke(LockBasedStorageManager.java:481) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValueWithPostCompute.invoke(LockBasedStorageManager.java:512) at kotlin.reflect.jvm.internal.impl.types.AbstractTypeConstructor.getSupertypes(AbstractTypeConstructor.kt:27) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getNonDeclaredVariableNames(DeserializedClassDescriptor.kt:382) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:262) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:261) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation.getVariableNames(DeserializedMemberScope.kt:261) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope.getVariableNames(DeserializedMemberScope.kt:60) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computePropertyNames(LazyJavaClassMemberScope.kt:861) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$propertyNamesLazy$2.invoke(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$propertyNamesLazy$2.invoke(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope.getPropertyNamesLazy(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope.getVariableNames(LazyJavaScope.kt:264) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getNonDeclaredVariableNames(DeserializedClassDescriptor.kt:383) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:262) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:261) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation.getVariableNames(DeserializedMemberScope.kt:261) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope.getVariableNames(DeserializedMemberScope.kt:60) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getNonDeclaredVariableNames(DeserializedClassDescriptor.kt:383) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:262) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:261) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation.getVariableNames(DeserializedMemberScope.kt:261) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation.addFunctionsAndPropertiesTo(DeserializedMemberScope.kt:349) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope.computeDescriptors(DeserializedMemberScope.kt:115) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope$allDescriptors$1.invoke(DeserializedClassDescriptor.kt:301) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope$allDescriptors$1.invoke(DeserializedClassDescriptor.kt:300) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getContributedDescriptors(DeserializedClassDescriptor.kt:311) at kotlin.reflect.jvm.internal.impl.resolve.scopes.InnerClassesScopeWrapper.getContributedDescriptors(InnerClassesScopeWrapper.kt:35) at kotlin.reflect.jvm.internal.impl.resolve.scopes.InnerClassesScopeWrapper.getContributedDescriptors(InnerClassesScopeWrapper.kt:27) at kotlin.reflect.jvm.internal.impl.resolve.scopes.ResolutionScope$DefaultImpls.getContributedDescriptors$default(ResolutionScope.kt:50) at kotlin.reflect.jvm.internal.KClassImpl$Data$nestedClasses$2.invoke(KClassImpl.kt:100) at kotlin.reflect.jvm.internal.KClassImpl$Data$nestedClasses$2.invoke(KClassImpl.kt:99) at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93) at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) at kotlin.reflect.jvm.internal.KClassImpl$Data.getNestedClasses(KClassImpl.kt:99) at kotlin.reflect.jvm.internal.KClassImpl.getNestedClasses(KClassImpl.kt:240) at kotlin.reflect.full.KClasses.getCompanionObject(KClasses.kt:47) at kotlin.reflect.full.KClasses.getCompanionObjectInstance(KClasses.kt:57) at AppKt.realmObjectCompanionOrNull(App.kt:0) at io.realm.kotlin.Configuration$SharedBuilder.(Configuration.kt:406) at io.realm.kotlin.RealmConfiguration$Builder.(RealmConfiguration.kt:51) at di.RealmModuleKt$realmModule$1.invoke(realmModule.kt:1019) at AppKt.module$default$33a95d8f$7dff6feb(App.kt:0) at di.RealmModuleKt.(realmModule.kt:17) ... 37 more Failed to launch JVM

I believe that clue in this line in the bottom:

at AppKt.realmObjectCompanionOrNull(App.kt:0)

as I already found similar Issue on this tracker but for Android.

Kotlin 1.8.0 Realm 1.7.0 Gradle 7.5.1

alexmaryin avatar Mar 31 '23 12:03 alexmaryin

Hi @alexmaryin. This looks a bit like if our plugin has not been applied or when of our internal properties have been stripped with Proguard on Android. Can you elaborate a bit on your configuration, especially:

  • How you define your 'debug' and 'release' configurations,
  • If you are using obfuscators/minimizers like proguard, and
  • What version of Gradle, Realm and compose you are using

rorbech avatar Mar 31 '23 12:03 rorbech

I guess there is also a

Incomplete hierarchy for class BaseRealmObject, unresolved classes [io.realm.kotlin.Deleteable]

and some methods related to inner classes. Can you verify that our library is in fact a dependency and comment whether you are using inner classes in your model? I fail to see how this should cause different behavior for debug and release runs, but I guess that depends heavily on how you debug/release configuration differs.

rorbech avatar Mar 31 '23 12:03 rorbech

Hi, @rorbech , thank you for reply. Release version of desktop-compose build really differs from Debug at least by applying the Proguard. Here is the part from build.gradle.kts for desktop-module of the project:

compose.desktop {
    application {
        mainClass = "MainKt"

        buildTypes.release.proguard {
            configurationFiles.from(project.file("compose-desktop.pro"))
        }

        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
           ...
            modules("java.instrument", "java.management", "java.naming", "java.sql", "jdk.unsupported")
            ....
        }
    }
}

And the file with Proguard-rules. I am not very confident with Proguard, so I just copied it from another issue at Desktop-Compose github. compose-desktop.pro.txt

alexmaryin avatar Mar 31 '23 13:03 alexmaryin

@alexmaryin Then I guess you are most probably just missing some proguard rules. For Android there is a convention on how to supply library proguard files to consumers, but I am not aware of a similar convention for non-Android JVM builds. As an initial experiment you could try to add the rules that we supply on Android. You can grab them here:

  • Base-library: https://github.com/realm/realm-kotlin/blob/main/packages/library-base/proguard-rules-consumer-common.pro
  • Sync-library: https://github.com/realm/realm-kotlin/blob/main/packages/library-sync/proguard-rules-consumer-common.pro Then we can try to investigate if there are mechanism for supplying it as part of our distributables.

rorbech avatar Mar 31 '23 13:03 rorbech

@rorbech I copied Base-library rules (I don't use Sync there), but result is still the same, and log-output is the same. But when I disabled Proguard for 'release' build type in Gradle, it started to run successfully.

alexmaryin avatar Mar 31 '23 13:03 alexmaryin

@alexmaryin Thanks for the confirmation. I guess it is then a matter of adding the appropriate rules. Please start by adding rules for the immediate unresolvable classes (reported like the unresolved classes [io.realm.kotlin.Deleteable] in the stack trace) along with the proguard-rules-consumer-common.pro rules mentioned above. Then feel free to reach out if you cannot derive the missing ones and/or if you get different stack traces.

rorbech avatar Mar 31 '23 13:03 rorbech

I inserted the next rule:

-keep class io.realm.kotlin.Deleteable {
    *;
}

Now here are two errors which cause fail on start:

Caused by: java.lang.RuntimeException: Couldn't load Realm native libraries
Caused by: java.lang.NoSuchMethodException: io.realm.kotlin.jvm.SoLoader.load()

alexmaryin avatar Mar 31 '23 13:03 alexmaryin

Yeah, I guess the SoLoader is also loaded through reflection. Have you tried adding the io.realm.kotlin.jvm.SoLoader too?

rorbech avatar Apr 03 '23 06:04 rorbech

I do run it successfully at last. But this happened only as I set in proguard rules -dontshrink, -dontoptimize, -dontobfuscate as well. So I don't see a point in this. A difference between release version with Proguard and one without is only 300 Kb when whole package takes 60 Mb.

alexmaryin avatar Apr 03 '23 16:04 alexmaryin

What are the other issues that you experience that requires you to add -dontshrink, etc.? Maybe I can direct you on some less agressive rules if I you post the stack traces or at least what you should check for in the proguard outputs/seeds to know which rule that is causing troubles. It could be that your manual additions of our consumer-rules is somehow overruled by your other configuration

rorbech avatar Apr 04 '23 12:04 rorbech

@rorbech I have made separate issue on it here https://github.com/JetBrains/compose-multiplatform/issues/2969 and it concerns only Compose multiplatform managing of resources for app.

alexmaryin avatar Apr 05 '23 07:04 alexmaryin

@alexmaryin Thanks. So after adding

io.realm.kotlin.Deleteable
io.realm.kotlin.jvm.SoLoader

to the keep rules along with our proguard-consumer rules, you don't see issues related to realm?

rorbech avatar Apr 05 '23 09:04 rorbech

@rorbech No, I don't. These two rules were enough for Realm.

alexmaryin avatar Apr 05 '23 09:04 alexmaryin

@alexmaryin I have updated the title to reflect that this is in fact just proguard configuration. I don't think we can distribute non-Android proguard-consumer-files, so I guess the work around for now is to clone the Android proguard consumer file and add rules for

io.realm.kotlin.Deleteable
io.realm.kotlin.jvm.SoLoader

rorbech avatar Apr 12 '23 08:04 rorbech

Hi, I tried the above solution but still encountered an "ExceptionInInitializerError" crash when I called the realm function. packageMsi or packageReleaseMsi

OS: Windows 11 Compose multiplatform: 1.6.10-beta03 Realm: 1.15.0 Kotlin: 1.9.23 Jdk: 18

But it works well during debugging desktopRun

Can you help me?

Moriafly avatar May 01 '24 15:05 Moriafly