realm / realm-kotlin

Kotlin Multiplatform and Android SDK for the Realm Mobile Database: Build Better Apps Faster.
Apache License 2.0
906 stars 52 forks source link

Kotlin SDK does not run with R8/Proguard enabled #942

Closed DrirEmilius closed 1 year ago

DrirEmilius commented 2 years ago

SDK and version

SDK : Kotlin Realm Version: 1.0.1 (library base)

Observations

Crash log / stacktrace

2022-07-26 22:05:30.770 E/REALM: /Users/realm/workspace-realm-kotlin/releases/packages/cinterop/src/jvm/jni/java_class.cpp:49: [realm-core-12.1.0] Assertion failed: cls with (class_name) = ["io/realm/kotlin/internal/interop/sync/NetworkTransport"]

!!! IMPORTANT: Please report this at https://github.com/realm/realm-core/issues/new/choose #### Steps & Code to Reproduce Build app using Realm with R8/Proguard minify enabled. Attempt to open realm crashes: private val config = RealmConfiguration.Builder(setOf(MessageObject::class, MediaObject::class, ResponseObject::class, ContactObject::class)).build() val realm = Realm.open(config)
rorbech commented 2 years ago

Hi @DrirEmilius.

We provide a proguard consumer file that should included the rules for our SDK to operate with minification/obfuscation as as part of our artifact. I cannot reproduce your observation with our sample app when enabling minification in the release configruation of https://github.com/realm/realm-kotlin/blob/master/examples/kmm-sample/androidApp/build.gradle.kts

Have you maybe somehow disregarded our rules in your configuration?

DrirEmilius commented 2 years ago

Hi @rorbech,

Thank you for pointing me to the sample app. It gave me a starting point and I have it working now. I did have to rewrite the Proguard rules a bit to work for my situation as the package name seems to be different.

Package remapped: io.realm. --> io.realm.kotlin. -keep class io.realm.kotlin.types.RealmObject -keep class implements io.realm.kotlin.types.RealmObject { ; }

Added rule for everything in interop package: -keep class io.realm.kotlin.internal.interop.* { ; }

Removed as these seem to be absent entirely: -keep class io.realm.kotlin.internal.interop.sync.mongodb.AppException { ; } -keep class io.realm.mongodb.SyncException { ; }

For reference I followed this installation instruction: https://www.mongodb.com/docs/realm/sdk/kotlin/install/android/

For reference: my now working realm related proguard rules. Please note: My app runs with these rules, however I'm unsure that these rules are suitable and comprehensive for all situations.


#realm
# Keep all classes implemeting the RealmObject interface
-keep class io.realm.kotlin.types.RealmObject
-keep class * implements io.realm.kotlin.types.RealmObject { *; }
#-keep class **.$* implements io.realm.RealmObject { *; }

# Preserve all native method names and the names of their classes.
-keepclasseswithmembernames,includedescriptorclasses class * {
    native <methods>;
}

# Notification callback
-keep class io.realm.kotlin.internal.interop.NotificationCallback {
    *;
}

# Utils to convert core errors into Kotlin exceptions
-keep class io.realm.kotlin.internal.interop.CoreErrorUtils {
    *;
}

-keep class io.realm.kotlin.internal.interop.JVMScheduler {
    *;
}

# Prevent all RealmObjects from having their companions stripped
-keep class ** implements io.realm.kotlin.internal.RealmObjectCompanion {
    *;
}

# Interop, sync-specific classes
-keep class io.realm.kotlin.internal.interop.sync.NetworkTransport {
    # TODO OPTIMIZE Only keep actually required symbols
    *;
}
-keep class io.realm.kotlin.internal.interop.sync.Response {
    # TODO OPTIMIZE Only keep actually required symbols
    *;
}
-keep class io.realm.kotlin.internal.interop.LongPointerWrapper {
    # TODO OPTIMIZE Only keep actually required symbols
    *;
}

# ==== Added
#-keep class io.realm.kotlin.internal.interop.sync.JVMSyncSessionTransferCompletionCallback {
#    *;
#}
#-keep class io.realm.kotlin.internal.interop.sync.ResponseCallbackImpl {
#    *;
#}
#-keep class io.realm.kotlin.internal.interop.SubscriptionSetCallback {
#    *;
#}
#-keep class io.realm.kotlin.internal.interop.SyncBeforeClientResetHandler {
#   *;
#}
-keep class io.realm.kotlin.internal.interop.** { *; }
# ==== End added

# ==== Removed

#-keep class io.realm.kotlin.internal.interop.sync.mongodb.AppException {
    # TODO OPTIMIZE Only keep actually required symbols
#    *;
#}
#-keep class io.realm.mongodb.SyncException {
    # TODO OPTIMIZE Only keep actually required symbols
#    *;
#}
# ==== End removed

-keep class io.realm.kotlin.internal.interop.SyncLogCallback {
    # TODO OPTIMIZE Only keep actually required symbols
    *;
}
-keep class io.realm.kotlin.internal.interop.SyncErrorCallback {
    # TODO OPTIMIZE Only keep actually required symbols
    *;
}
-keep class io.realm.kotlin.internal.interop.AppCallback {
    *;
}

# Un-comment for debugging
#-printconfiguration /tmp/full-r8-config.txt
#-keepattributes LineNumberTable,SourceFile
#-printusage /tmp/removed_entries.txt
#-printseeds /tmp/kept_entries.txt```
rorbech commented 2 years ago

@DrirEmilius Thanks for the feedback. The package renaming should already be included in our proguard file https://github.com/realm/realm-kotlin/blob/master/packages/library-base/proguard-rules-consumer-common.pro

Are you pointing to some old version?

And as previously highlighted, the build system normally automatically picks up the proguard-rules-consumer-common.pro-files from each library and this works for our sample library, so maybe there is something conflicting in our configuration. Have you tried to remove any local configuration?

DrirEmilius commented 2 years ago

@rorbech

Your first link (https://github.com/realm/realm-kotlin/blob/master/examples/kmm-sample/androidApp/build.gradle.kts) referenced an empty proguard file: (https://github.com/realm/realm-kotlin/blob/master/examples/kmm-sample/androidApp/proguard-rules.pro). I did search the history of the repository, which is probably how I ended up with an older version.

My apps build.gradle.kts uses the default proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") I am not sure how that would mess with it picking up the proguard-rules-consumer-common.pro-file. I'm new to, and not yet too familiar with R8/Proguard.

I can confirm that manually placing the lines from proguard-rules-consumer-common.pro-file to my proguard-rules.pro-file (replacing my previous Realm related changes) works with one small correction:

# Utils to convert core errors into Kotlin exceptions
-keep class io.realm.kotlin.interop.CoreErrorUtils {
    *;
}

into

# Utils to convert core errors into Kotlin exceptions
-keep class io.realm.kotlin.internal.interop.CoreErrorUtils {
    *;
}