The ProGuard rules in the README are so strict that they prevent json model classes and generated serializer classes from being optimized and obfuscated.
After some tests, these seem enough:
-keepclassmembers,allowoptimization class com.yourcompany.yourpackage.** {
*** Companion;
}
-keepclassmembers,allowoptimization class com.yourcompany.yourpackage.** {
kotlinx.serialization.KSerializer serializer(...);
}
The first rule keeps the Companion field and the Companion class of json model classes. The field cannot be renamed because of the reflection usage but its class can. This rule does not prevent the model class itself and its Companion class from being obfuscated.
The second rule keeps the serializer() method of the Companion class of json model classes. This method also cannot be renamed because of this. Since this method will return the serializer instance for this model class, the specific serializer class will also be kept. Serializer classes don't use reflection so they (including their fields and methods) can be obfuscated and this rule does not prevent that.
If this looks good to you, I'm glad to submit a PR.
I'm using
# Kotlin serialization looks up the generated serializer classes through a function on companion
# objects. The companions are looked up reflectively so we need to explicitly keep these functions.
-keepclasseswithmembers class **.*$Companion {
kotlinx.serialization.KSerializer serializer(...);
}
# If a companion has the serializer function, keep the companion field on the original type so that
# the reflective lookup succeeds.
-if class **.*$Companion {
kotlinx.serialization.KSerializer serializer(...);
}
-keepclassmembers class <1>.<2> {
<1>.<2>$Companion Companion;
}
which is a bit more general but potentially not as permissive with regard to obfuscation (as i don't obfuscate).
Perhaps we could combine and embed the rules so that they're applied automatically (similar to what we've done with coroutines, and many other libraries).
What's the difference from the current proguard configuration? I can see only keepclassmembers vs keepclasseswithmembers and 'allowoptimization' flag.
Also, line
-keep,includedescriptorclasses class com.yourcompany.yourpackage.**$$serializer { *; } is needed in case the class has named companion — in this case, lookup tries to access generated class directly without Companion.serializer() invocation.
@jw Am I right that
-keepclasseswithmembers class **.*$Companion {
kotlinx.serialization.KSerializer serializer(...);
}
allows to keep all the Companion.serializer() functions regardless of the package? Then we likely can embed this rules in the library — without the need for users to fill in their app's package.
Yes. It assumes the companion is named Companion (I'm not sure if serialization supports named companions) and it also means that you potentially keep more code than is needed (if you are consuming a library with serialization code but it's unused).
The first issue could probably be fixed by replacing Companion with another wildcard (*), and the second issue is just something I live with since in practice it likely doesn't happen much, if at all.
@sandwwraith
keepclassmembers keeps the specified members if the class is not shrinked while keepclasseswithmembers always keeps the class and the members. The word keep in ProGuard rules basically means keep from being shrinked and obfuscated so keepclasseswithmembers will prevent the class from being obfuscated. Another difference is that if the user isn't actually using a model class, keepclassmembers will allow the class to be shrinked but keepclasseswithmembers will keep the class anyway.
I didn't realized the support of named companions. In that case the classes cannot be obfuscated. However, I don't think includedescriptorclasses is needed as the generated serializers do not use reflection.
Obfuscation is important to me and is actually one of the reasons I migrated from Moshi as the Moshi codegen generates ProGuard rules that prevent obfuscation. I don't think there is any ways to exclude part of the rules from libraries, so I would prefer not embedding the rules as they are intrusive (they affect user code and cannot be disabled).
Was there any decision / progress made on this?
On a similar vein, what is the purpose/requirement for this rule?
-keepclassmembers class com.yourcompany.yourpackage.** { # <-- change package name to your app's
*** Companion;
}
This seems like it would be keeping EVERY Companion object's classmembers, even if they aren't using Kotlin Serialization? Is that really necessary?
includedescriptorclasses
Specifies that any classes in the type descriptors of the methods and fields that the -keep option keeps should be kept as well. This is typically useful when keeping native method names, to make sure that the parameter types of native methods aren't renamed either. Their signatures then remain completely unchanged and compatible with the native libraries.
Seems that we need only keep the name of serializers without includedescriptorclasses
And then keep the relationship between a class and its inner classes and outer classes. (-keepattributes InnerClasses)
Most helpful comment
I'm using
which is a bit more general but potentially not as permissive with regard to obfuscation (as i don't obfuscate).
Perhaps we could combine and embed the rules so that they're applied automatically (similar to what we've done with coroutines, and many other libraries).