Package Repacking in Gradle

In my article I want to talk about another trick that can be quite easily implemented using Gradle - repackaging library packages. Everyone who has even worked a little with this build system knows that it automatically knows how to resolve conflicts of different versions of libraries, and if you wish, you can influence this, for example, overfix a specific version of a library:







configurations.all { resolutionStrategy { force "org.ow2.asm:asm:7.2" } }
      
      





Unfortunately, this does not always help solve the version conflict problem. For example, there is a known problem that some htc devices in the firmware already have a gson library, and if your version of gson differs from the built-in version, then problems may occur, since ClassLoader will load only one class into memory, and in this case it will be a system one.







This problem can also occur when developing libraries. If you connect 2 libraries to your project that use the same third-party library of different versions, for example 1 and 2, then Gradle will resolve and take the newest version, the second. But if there is no backward compatibility in this third-party library and the second version cannot just be used instead of the first, then there will be problems that will surely be very difficult to track by trace. A library awaiting the first version will receive the second classes and just crash.







I encountered a version conflict when writing a grad plugin , it uses the asm library, which conflicted. After writing the plugin, I checked its performance on a test project: everything is fine, checked on a pet project, everything is fine, too, but when I connected it to a real working project with a bunch of third-party dependencies, I ran into a problem.













The solution to the problem under the cut.







Yet it worked, what went wrong?



We get the full error trace:













We see that the error in the constructor of the asm ClassVisitor



library class is on line 79. Let's look there, but when trying to open ClassVisitor



, the studio offered 2 options













My plugin uses asm version 7.2



, so we go there and on line 79 we see the following:













This is clearly not what we need. Now go to ClassVisitor



version 6:













Just our IllegalArgumentException



without a message. My plugin uses the ASM api 7 version of Opcodes.ASM7



, and in the 6 version of the library this api does not yet exist, and therefore an IllegalArgumentException



in the constructor flies. We can conclude that the plugin receives an incorrect version of the library.







Garbage question, I thought, and did this:







 configurations.all { resolutionStrategy { force "org.ow2.asm:asm:7.2" } }
      
      





To my regret, this had absolutely no effect. I still could not find out the exact reason why it is not possible to overfix the version of asm, although the ./gradlew app:dependencies



command indicates that the version has been replaced with 7.2. If someone has thoughts or assumptions, I will be glad to hear an opinion.







The problem must be solved somehow



A series of googling and deepening into the work of the hail began. As a result, I went to the asm website, maybe they know something about this. It turned out that they really know, the answer to my question was in the FAQ section. They say to replace the asm package with another, they even offer a utility for this. Ok, let's try. You just need to connect the plug-in and make a little setup:







 apply plugin: 'org.anarres.jarjar' ... dependencies { implementation fileTree(dir: 'build/jarjar', include: ['*.jar']) implementation jarjar.repackage('asm') { from 'org.ow2.asm:asm:7.2' classRename "org.objectweb.asm.**", "stater.org.objectweb.asm.@1" } }
      
      





build/jarjar



in this case, the directory into which the asm library jar file with repackaged packages will be generated, so you need to open dependency access to this directory via fileTree



. The library will now be available with import stater.org.objectweb.asm.*



Instead of org.objectweb.asm.*



. This plugin still has various settings, but in my example, just changing the packages was enough.







Next, go through the entire project and change imports everywhere from org.objectweb.asm



to

stater.org.objectweb.asm



. In my opinion, it’s a very convenient utility, many times easier than doing it by hand, especially when updating the library, we just change from 'org.ow2.asm:asm:7.2'



to the new version and the repackaged jar nickname with the new version will be generated on automatic machine.







If you just have a project (not a library), then this will be enough for you to resolve insoluble conflicts, like the gson mentioned at the beginning of the article. But if you, like me, write a library, then that’s not all.







We solved the repackaging problem, but now asm



connected to the project not through dependency on the remote maven repository, but through the local jar file, which simply gets lost when your library is deployed and there will be such a NoClassDefFoundError



error. But this problem is quite simple to solve:







  1. In our gradle file, create a new configuration:







     configurations { extraLibs implementation.extendsFrom(extraLibs) }
          
          





  2. Next we change







     implementation fileTree(dir: 'build/jarjar', include: ['*.jar'])
          
          





    on







     extraLibs fileTree(dir: 'build/jarjar', include: ['*.jar'])
          
          





  3. We redefine the task that is responsible for collecting your final jar file and write all the libraries with our new configuration to the final jar nickname:







     jar { from { configurations.extraLibs.collect { it.isDirectory() ? it : zipTree(it) } } }
          
          







That's all, deploy our plugin as before, connect to the project where there were unsolvable conflicts and everything works fine.

Such repackaging makes our library more fault-tolerant when connected to various kinds of projects with other libraries.







And if you just connect the jar file of the conflicting library to the plugin without repackaging?



Bad idea, it will not lead to anything good. In the process of building the project, there is such an interesting task check...DuplicateClasses



, which simply cuts down files with the same packages. That is, files obtained from the jar file of the connected library and files from the same library connected through the remote repository. The result will be such an error:













That's all. Thanks to everyone who read!







Tulsa for repacking

Example plugin








All Articles