bytedeco / javacv

Java interface to OpenCV, FFmpeg, and more
Other
7.45k stars 1.57k forks source link

Javacv 1.5 proguard for Android build #1198

Open xiaos opened 5 years ago

xiaos commented 5 years ago

In the latest version 1.5, the package names are changed, anyone can provide the proguard file on Android platform?

I tried to changed the package name in proguard file, but not working.

saudet commented 2 years ago

@HGuillemet @Nackloose Could you please make sure that we don't need to update any rules here? https://github.com/bytedeco/javacv/wiki/Configuring-Proguard-for-JavaCV

HGuillemet commented 2 years ago

This answer is not specific to OpenCV but to any JavaCPP preset, and not really specific to Android either.

If you keep the default configuration proguard-android.txt or proguard-android-optimize.txt, the annotations and the native name (for linkage) are already preserved, at least with recent versions of Android Studio.

What must be added is the preservation of the classes loaded by JavaCPP by Class.forName, of the members accessed by reflection, and of the methods and fields accessed from native code.

Any class targeted by a "global" or "target" property could be loaded using Class.forName. Such class doesn't necessary have an annotation, or even a native method. The only way I see to target such class with a proguard directive is that it extends a class with a @Properties annotation.

@saudet, is this always the case ?

Also, the type of the members of the classes mapping native classes are loaded with Class.forName (in Loader.putMemberOffset). We must thus preserve all classes mapping native type with their members and associated descriptor classes. These classes are JavaCPP-annotated and we can use this to target them with Proguard. This will somewhat limit the shrinking since a lot of unused classes could be preserved this way, but I don't think there is another option.

Java Method called by native code and that are not standard classes are Loader.putMemberOffset and Pointer.init. There are also callbacks. But I guess those are always in annotated classes.

Java Fields accessed from native code are the Pointer fields, Pointer.NativeDeallocator.ownerAddress and the value of Generator.*Enum.

A method accessed by reflection from Java code is the constructor of Pointer subclasses taking a Pointer.

@saudet, can you think of something else ?

So putting it all together, if we preserve:

we should be safe.

# The following should already be in proguard-android.txt or proguard-android-optimize.txt

-keepattributes *Annotation*,EnclosingMethod

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Add includedescriptorclasses, compared to proguard-android.txt

-keepclasseswithmembernames,includedescriptorclasses class * {
    native <methods>;
}

# JavaCPP specific

-keep,includedescriptorclasses @org.bytedeco.javacpp.annotation.* class * {
  *;
}

-keep,includedescriptorclasses class * extends @org.bytedeco.javacpp.annotation.* * {
  *;
}

-keep class org.bytedeco.javacpp.Loader {
  java.lang.Class putMemberOffset(java.lang.String, java.lang.String, int);
}

-keep class org.bytedeco.javacpp.Pointer {
  void init(long, long, long, long);
  <fields>;
}

-keep class org.bytedeco.javacpp.Pointer$NativeDeallocator {
  <fields>;
}

-keep class org.bytedeco.javacpp.tools.Generator$*Enum {
  <fields>;
}

-keepclassmembers class * extends org.bytedeco.javacpp.Pointer {
  <init>(org.bytedeco.javacpp.Pointer);
}

-dontwarn org.bytedeco.javacpp.**
-dontnote org.bytedeco.javacpp.**

Could you give it a try ? And it it doesn't work, what are the symptoms ? Any warning or exception ?

saudet commented 2 years ago

@HGuillemet Thanks for looking into this! It sounds about right, but I don't know exactly how specific we need to get with those rules. In the case of GraalVM Native Image, we only need to specify the names to keep for JNI and reflection, but just adding rules to keep all annotations from JavaCPP to that list should be enough, without making things too complicated: https://github.com/bytedeco/javacpp/blob/master/src/main/java/org/bytedeco/javacpp/tools/Generator.java#L1910-L1942 One thing missing from the rules above are subclasses from LoadEnabled and FunctionPointer, but also classes with @Virtual methods in them. Is there a way to tell ProGuard to keep the names of the methods of those classes?

In any case, let's try to make those rules as simple as possible by making them as general as possible, for example by just keeping everything (or almost everything) from defined sets of classes, for sets that are small enough. That will help reduce maintenance cost. If someone really needs to chop down things even further, it will always be possible to do it, if/when the need arises.

HGuillemet commented 2 years ago

I wasn't aware of these json files aiming GraalVM. The generic rules above that keep everything of the JavaCPP-annotated classes or their subclass should give similar result. I tested them on one of my application using the Pytorch, OpenCV and FFmpeg presets on Linux, shrinking all classes but the Java runtime ones, and it works fine.

Don't all classes implementing LoadEnabled or extending FunctionPointer also have a JavaCPP annotation ? If yes, this case is covered by the rule above.

Does it make sense to have a class with a @Virtual method that doesn't have an annotation itself ?

Once we converged on the proguard config, I suggest to replace the old wiki entry from Nackloose by one more general and up to date about Proguard and JavaCPP.

saudet commented 2 years ago

LoadEnabled probably always have annotations, but FunctionPointer and classes with @Virtual don't necessarily have annotations if they are enclosed in a class with annotations. I'm not sure if it's a good idea or not to keep everything for all Pointer classes either. In the case of GraalVM Native Image, the binary size severely blows up, but I don't think it's the case with Android, so that should be alright...

BTW, why did you include special rules for Pointer, Generator, and Loader if the idea is just to keep everything anyway?

Once we converged on the proguard config, I suggest to replace the old wiki entry from Nackloose by one more general and up to date about Proguard and JavaCPP.

Yup, that's the plan.

HGuillemet commented 2 years ago

LoadEnabled probably always have annotations, but FunctionPointer and classes with @Virtual don't necessarily have annotations if they are enclosed in a class with annotations.

Ok. I'm not sure it's possible to target "classes enclosed in a class with a JavaCPP annotation". So I added specific rules for @Virtual and FunctionPointer.

I also added InnerClasses in the list of kept attributes, because intropection is used in the code to find enclosing classes.

I'm not sure if it's a good idea or not to keep everything for all Pointer classes either. In the case of GraalVM Native Image, the binary size severely blows up, but I don't think it's the case with Android, so that should be alright...

I'm realizing that Pointer is annotated with @Properties. So indeed all Pointer subclasses will be kept, which is not necessary. This rule about subclasses of annotated classes is here for keeping classes that are "global" or "target". For instance if I remove it, my app fails with ClassNotFoundException: org.bytedeco.openblas.global.openblas_nolapack because openblas_nolapack is loaded with Class.forName when openblas is first loaded and it has no annotations.

We could add a dummy @Keep annotations or @Target just for this use case. Do you have a better idea ?

BTW, why did you include special rules for Pointer, Generator, and Loader if the idea is just to keep everything anyway?

The rules on Pointer and Loader are redundant indeed since they are annotated. I removed them. The rules for enums in Generator and for NativeDeallocator are needed,

New version:

# The following should already by in proguard-android.txt or proguard-android-optimize.txt

-keepattributes *Annotation*,EnclosingMethod,InnerClasses

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Add includedescriptorclasses, compared to proguard-android.txt

-keepclasseswithmembernames,includedescriptorclasses class * {
    native <methods>;
}

# JavaCPP specific

-keepclasseswithmember,includedescriptorclasses class * {
    @org.bytedeco.javacpp.annotation.Virtual <methods>;
}

-keepclasseswithmember,includedescriptorclasses class * extends org.bytedeco.javacpp.FunctionPointer {
    native <methods>;
}

-keep,includedescriptorclasses @org.bytedeco.javacpp.annotation.* class * {
  *;
}

-keep,includedescriptorclasses class * extends @org.bytedeco.javacpp.annotation.* * {
  *;
}

-keep class org.bytedeco.javacpp.Pointer$NativeDeallocator {
  <fields>;
}

-keep class org.bytedeco.javacpp.tools.Generator$*Enum {
  <fields>;
}

-dontwarn org.bytedeco.javacpp.**
-dontnote org.bytedeco.javacpp.**
saudet commented 2 years ago

This still seems unnecessarily long, for example, doesn't InnerClasses keep org.bytedeco.javacpp.Pointer$NativeDeallocator?

HGuillemet commented 2 years ago

No, InnerClasses is just the class attribute, that is the link between an enclosed class and the enclosing class, so that the information is available by reflection. It does't keep the inner classes themselves.

nature0101 commented 1 year ago

It is worked for me!

# JavaCV

-keep @org.bytedeco.javacpp.annotation interface * {
    *;
}

-keep @org.bytedeco.javacpp.annotation.Platform public class *

-keepclasseswithmembernames class * {
    @org.bytedeco.* <fields>;
}

-keepclasseswithmembernames class * {
    @org.bytedeco.* <methods>;
}

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclasseswithmembernames,includedescriptorclasses class * {
    native <methods>;
}

-keepclasseswithmembernames,includedescriptorclasses class * {
    @org.bytedeco.javacpp.annotation.Virtual <methods>;
}

-keepclasseswithmembernames,includedescriptorclasses class * extends org.bytedeco.javacpp.FunctionPointer {
    native <methods>;
}

-keep,includedescriptorclasses @org.bytedeco.javacpp.annotation.* class * {
  *;
}

-keep,includedescriptorclasses class * extends @org.bytedeco.javacpp.annotation.* * {
  *;
}

-keep class org.bytedeco.javacpp.Pointer$NativeDeallocator {
  <fields>;
}

-keep class org.bytedeco.javacpp.tools.Generator$*Enum {
  <fields>;
}

-keepattributes *Annotation*,EnclosingMethod,InnerClasses
-keep @interface org.bytedeco.javacpp.annotation.*,javax.inject.*

-keepattributes *Annotation*, Exceptions, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable, LocalVariableTable, LocalVariableTypeTable, Synthetic, EnclosingMethod, RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, AnnotationDefault, InnerClasses
-keep class org.bytedeco.javacpp.** {*;}
-dontwarn java.awt.**
-dontwarn org.bytedeco.javacv.**
-dontwarn org.bytedeco.javacpp.**
-dontwarn org.bytedeco.openblas.**
-dontwarn org.bytedeco.opencv.**