Gradle nested dependency issue in Android project - java

My app has a module (lets call it myModule) which uses a 3rd party library(lets call it 3rdParty). This 3rdParty library is an aar file that uses a video compression library(lets call it videoCompressor). And myModule is also using the same compression library with the same version.
p.s. A module can't use aar file directly, so I had to put it in the app and use it in myModule as mentioned in this post.
So here is the dependency hierarchy
app
dependencies {
implementation(':myModule')
implementation(':3rdParty')
implementation 'videoCompressor:1.2.3'
}
myModule
dependencies {
api project(':3rdParty')
implementation 'videoCompressor:1.2.3'
}
3rdParty is an aar file and it also uses videoCompressor:1.2.3 internally.
While trying to build the project I get this error
Type com.abedelazizshe.lightcompressorlibrary.data.AtomsKt is defined multiple times: /Users/xx/Documents/xx/temp/xx/app/build/intermediates/mixed_scope_dex_archive/devDebug/out/d557fe27570a7fdcab8e1782ef61e44224719c80849bd367030f40a11145859c_1.jar:classes.dex, /Users/xx/Documents/xx/temp/xx/app/build/intermediates/mixed_scope_dex_archive/devDebug/out/23b9eb6594f48cb29b67fd834a2ea73b6ec11312577ac3867a8befdc5815129a_1.jar:classes.dex
This seems to be because there are duplicate classes from the videoCompressor library. How can this be fixed?
I tried removing videoCompressor from the app and myModule, build gets created but there is runtime crash about NoClassDefFoundError.

Related

Gradle project does not export implementation-dependency to other projects

My gradle project contains 3 sub-projects with one source file each:
root-project\
sub-project-abstract\
...AbstractFoo.java
sub-project-commons\
...ConcreteFoo.java (extends AbstractFoo)
sub-project-main\
...Main.java (instantiates ConcreteFoo)
build.gradle of sub-project-commons:
dependencies {
implementation(project(:sub-project-abstract))
}
build.gradle of sub-project-main:
dependencies {
implementation(project(:sub-project-commons))
}
The Main-class in sub-project-main is aware of ConcreteFoo, however, compilation fails with cannot access AbstractFoo.
For some reason, I expected sub-project-commons to "export" ConcreteFoo and AbstractFoo, since it's a implementation-dependency. Or in other words, form the perspective of sub-project-main, AbstractFoo is a transitive dependency.
However, this doesn't seem to be the case.
I know that I could probably make it work by explicitly adding sub-project-abstract as a direct dependency to sub-project-main. However, that's something I want to avoid due to the nature of the commons project (my actual project contains up to 10 subprojects, and it should be possible to reuse the commons-project without declaring a dependency to sub-project-abstract every single time the commons-project is referenced.
Is there a way to make the Main-class aware of AbstractFoo without directly declaring sub-project-abstract as a dependency (but indirectly via sub-project-commons)?
This is expected behavior for the implementation configuration. You should apply the Java Library Plugin and use the api configuration.
The key difference between the standard Java plugin and the Java Library plugin is that the latter introduces the concept of an API exposed to consumers. A library is a Java component meant to be consumed by other components. It’s a very common use case in multi-project builds [emphasis added], but also as soon as you have external dependencies.
The plugin exposes two configurations that can be used to declare dependencies: api and implementation. The api configuration should be used to declare dependencies which are exported by the library API, whereas the implementation configuration should be used to declare dependencies which are internal to the component.
[...]
Dependencies appearing in the api configurations will be transitively exposed to consumers of the library, and as such will appear on the compile classpath of consumers. Dependencies found in the implementation configuration will, on the other hand, not be exposed to consumers, and therefore not leak into the consumers' compile classpath. [...]
In sub-project-commons (Kotlin DSL):
plugins {
...
`java-library`
}
...
dependencies {
api(project(":sub-project-abstract"))
}
...

How to publish a android library with java module?

My Project
- moduleA
- moduleAPT
- moduleJ
Above is my project structure. The moduleA is a shared library module, and moduleJ is a java module that contains some annotation classes, and moduleAPT is an annotation processing tool module that handles moduleJ's annotation classes.
Both moduleA and moduleAPT have a dependence line in their build.gradle file like this:
api project(':moduleJ')
Now i want to publish the moduleA to jcenter. The problem is the generated aar file do not contains source files in moduleJ.
How to merge moduleJ's classes to moduleA's aar file when compiling.
You can use this library to package aar
fat-aar-android
I'm using it, it can merge multiple module

Resolving to correct dependencies when loading classes with custom classloaders

So I have this generic backend server that loads shaded jars in memory and then loads it through a custom Classloader.
E.g.
MyClass class = c.newInstance();
It works fine until the shaded Jar dependencies conflicts with the server classes.
E.g.
Server contains (with Custom Classloader):
com.fasterxml.jackson.jackson-databind:2.6.0
While the shaded jar contains
com.fasterxml.jackson.jackson-databind:2.9.9
When the method in the class that requires the said library e.g. class.doSomeThing(); it throws an error Caused by java.lang.NoSuchFieldError: because the loaded jackson-databind is 2.6.0 instead of 2.9.9
The question here is when the class is loaded from the shaded jar is there a way to make sure that the shaded dependencies are the ones used?
The question here is when the class is loaded from the shaded jar is there a way to make sure that the shaded dependencies are the ones used?
If you are using the default Class loader then the order of resolution will work as the order of the classpath. Within your code you can use
System.out.println(System.getProperty("system.class.path").replaceAll(":", "\n"));
And inspect the classpath. Usually such runtime environment (for example apache spark) has such features to allow you to prepend the classpath. You can check with your runtime server environment for such feature.

Reuse a Spring-boot submodule in another non Spring project

I'm working on a multi module spring-boot project to build a REST API. Here is my project structure:
Parent project (packaging is pom)
core module (#SpringBootApplication + handle path like / or /status)
restControllerA module (Handle path like /routeA/*)
restControllerB module (Handle path like /routeB/*)
Everything is working in this project :)
In another non Spring project I would like to reuse a service of restControllerB. This service return the result of the request body validation.
First I try to add the restControllerB.jar as a dependency to this new project... But this jar does not contain its depedencies (who are in the fatJAR "core.jar"). When I run the project, I get a lot of ClassNotFoundException.
How can I manage to reuse this service as a dependency ? I thought to create a validator module which implements the validatorService interface, but I'm not sure if it is the best solution.
After few hours googling, It seems that creating an external librairy is the right choice. I create an external module and add it as a dependecy to restControllerB.

Shadow Plugin Gradle: What does mergeServiceFiles() do?

In my build.gradle file I need to add the line:
shadowJar {
mergeServiceFiles()
}
Otherwise the jar does not run properly. I wonder what this line does exactly?
I use the Gradle plugin in Eclipse Luna. I create the jar on one Java project which depends on another one.
mergeServiceFiles is declared exactly here and its implementation is as follows:
/**
* Syntactic sugar for merging service files in JARs
* #return
*/
public ShadowJar mergeServiceFiles() {
try {
transform(ServiceFileTransformer.class);
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
}
return this;
}
As you can see it uses ServiceFileTransfomer which is defined here. From its docs:
Modified from org.apache.maven.plugins.shade.resource.ServiceResourceTransformer.java
Resources transformer that appends entries in META-INF/services resources into a single resource. For example, if there are several META-INF/services/org.apache.maven.project.ProjectBuilder resources
spread across many JARs the individual entries will all be
concatenated into a single
META-INF/services/org.apache.maven.project.ProjectBuilder resource
packaged into the resultant JAR produced by the shading process.
TL;DR - It merges the service files in the META-INF/services folder.
Long Answer
Some libraries (e.g. Micronaut) create a few service files in the META-INF/services folder. These files can contain any information useful at runtime. In case of Micronaut framework, it creates a file that lists the Bean References (or beans that are instantiated) in a file called io.micronaut.inject.BeanDefinitionReference under META-INF/services.
Usually, if you just have one application, it works fine even without mergeServiceFiles(). But if there are two micronaut projects that you are bundling into a single jar (e.g. a micronaut lib project with some utils and a micronaut app project with core business logic), you will have two io.micronaut.inject.BeanDefinitionReference. Each one will contain the beans of it's own project.
If you don't use mergeServiceFiles(), one of the BeanDefinitionReference files will be overwritten by the other. In that case, you will get a runtime exception saying BeanNotInstantiated or something of that sort.
Using mergeServiceFiles() merges (or concatenates in this case) the BeanDefinitionReference files of both the projects so that at runtime, you get all the beans defined.
More details can be found in the gradle forum topic here.

Categories