Shade (relocate) one version of a transitive dependency, but not the other - java

I've got a Maven project that contains two dependencies, A and B. Each of these depends transitively on C, but they depend on different versions of C. Let's say that A depends on C version 1, and B depends on C version 2.
Unfortunately, A is not bytecode-compatible with version 2, nor B with version 1. (As it happens, A is source-compatible with version 2, but I don't think that will help us here.)
This means that I need both versions of the transitive dependency in my project, and I need A to use version 1, and B to use version 2.
Is there a way of doing this?
I had assumed that I would need to use the shade plugin to relocate the package name of A and all its dependencies, but this doesn't seem to be possible. If I shade A, its dependencies don't get shaded, and it still picks up version 2, and fails to run.

Create another project wrapper A named A-wrapper. Relocate C in A-wrapper.
Then in your main project, depends on A-wrapper and B.
I've met a similar problem on pb2 and pb3 and it is resolved using this way.
https://stackoverflow.com/a/41394239/1395722

Assuming dependency A requires v1 of C and dependency B requires v2 of C. You can create an uber jar of A containing v1 of C but changing the packaging using shade plugin,
Example jar A has contents of C with new packaging "v1.c.something". Do the same for B, so jar B has contents of C with new packaging "v2.c.something". You need to include only the conflicting dependencies not all.

Related

How to reference local version of a project in another Gradle project dependencies

I have a Gradle project (say project A) that depends on another Gradle project (say B). Each project live in their own git repository. Sometimes when I have to make a non-trivial change across both projects, I will make the code changes in B and push to remote where our CI will pick it up and release it as a new version.
Then in project A, I will update the version for B with the new version number, then run ./gradlew generateLock saveLock and then I will be able to use the new changes made in B to implement the feature in A.
I want to avoid having to push B to remote and release a new version before I can start using the new code in A. Ideally, I want to make change to B locally and have A reference the local version of the project. That way I can test both and test the whole feature before I push any code. How can I achieve that using Gradle. I am using Gradle 7.5?
I declare my dependency on B in A's build.gradle as implementation 'com.something.project-b'. Note that B might have a bunch of other library dependencies of its own declared in its own build.gradle which should still be transitively pulled when I want to use the local version of B in A.
Any pointers?
There is a local JAR option in Gradle but that will not pull all the transitive dependencies of B and I do not want to build a combined JAR of B and all its dependencies and use that since then I lose all dependency resolution benefits in case of conflicts.
Also note that B is an independent project and cannot simply be moved to Project A as a sub-project
You don't need to do any sort of local workstation hodgepodge. All you need is the following:
Declare mavenLocal() as the first repository in the dependent project (you probably already have mavenCentral().
Publish the dependency to Maven local repository using the task .gradlew publishToMavenLocal.
Now, your dependent project can use the latest code from the dependency.
in the project 'A'
settings.gradle
include ':prjB'
project (':prjB').projectDir = new File(settingsDir, '../relativePath')
build.gradle
def prjBExists = file('path-to-prjB').exists
dependencies {
...
if (prjBExists) {
implementation project(':prjB')
} else {
implementation 'g:a:v'
}
}
PS - this referencing is under discussions as gradle-8 is expected to deprecate ...

Gradle does not resolve transitive dependency inherited from parent pom

I was converting a maven project into a gradle project,but I encountered a problem.
Here is all i have:
project A;
Library B from third party, and also a dependency of A
Pom C, parent of B
Library D, a dependency of C
So, as expected, project A should have a direct dependency B, and a transitive dependency D
it works fine in maven form that project A can access library D.But in gradle form , gradle does not treat D as a dependency of project A,it just ignores this transitive denpendency.
`implementation 'group:B:version'`
How should it declear the depencency of B in gradle to get same result as in maven
I know it is not a regular way to access D from A without declearing dependency,but I just want to archive the same goal as maven with gradle.
Thanks for your answer ,any help will be much appreciated.
Use compile instead implementation in a both modules (A and B) if need to access to project C from project A.
P.S. This is not recommended and may indicate an incorrectly designed architecture. In particular, the violation of D from SOLID.

Including dependency from imported maven library

A little fuzzy on Gradle/maven, but generally here is the idea.
I have a web application that uses a common library (A) as a dependency with source implemented under com.mydomain.utils package. There is another legacy package (B) written under a different namespace, com.mydomain.legacy, that I would like included within A, such that when I include A as a dependency in my primary application, library B's resources can be resolved as normal:
import com.mydomain.legacy.someutility
If B is a dependency of A, and A is a maven artifact, then B is already included in A, otherwise, it would not be possible to build A.
If B is not a dependency of A, then you need to list both A and B as dependencies of your project.

Maven - How to use different version dependency in two projects that depend on each other

General Description:
I have two projects A and B.
Project A, must use the version v1 of the L library/API.
Project B, must use the version v2 of the L library/API.
Project A has a dependency on project B (In project A, i need to call a method contained in B).
Concrete description:
Project A is actually a machine learner which has a collection of algorithms which are using an older version of spark-mllib.
I want to integrate the XGBOOST-spark algorithm in project A.
The problem is that the XGBOOST api, specifically: ml.dmlc.xgboost4j.scala.spark.XGBoost.train() method, expects an RDD<org.apache.spark.ml.feature.LabeledPoint>. But the org.apache.spark.ml.feature.LabeledPoint is only available in the newer version of spark-mllib. And from project A (which uses the older version of spark-mllib), I only have acces to an org.apache.spark.mllib.regression.LabeledPoint. So I cannot directly integrate XGBOOST in project A without upgrading the spark-mllib version of project A.
Fortunately, the newer version of spark-mllib has a method of converting from the old LabeledPoint (org.apache.spark.mllib.regression.LabeledPoint) to the new LabeledPoint (org.apache.spark.ml.feature.LabeledPoint). The method is: org.apache.spark.mllib.regression.LabeledPoint.asML().
So, the question is: Is there any clever way of using that method .asML() which is available only in the newer version of spark, so that I can convert the LabeledPoint and pass it to the XGBOOST API?
I am not familiar with how the dependencies are treated by maven but I thought of something like:
Create a project B that uses the newer version of spark-mllib, and the XGBOOST-API, and in which we have a class and a method that receives the parameters (from project A), converts the old LabeledPoint to the new LabeledPoint, calls the XGBoost.train() method which generates a model, and then we pass back the model to project A. We import that class in project A (from project B), call it's method, get the model, and we continue with our business as usual.
Of course, I tried to do that. But it doesn't work. I think that's because of the fact that we can only have one version of spark-mllib in the whole dependency tree. Since the class from project B throws java.lang.NoSuchMethodError: org.apache.spark.mllib.regression.LabeledPoint.asML()Lorg/apache/spark/ml/feature/LabeledPoint; , it seems that in the whole dependency tree, we actually use the older version of spark-mllib (and that happens because the older version is closer to the root of the dependency tree). Even though in project B we use the newer version of spark-mllib, which has the asML() method available.
So, the actual question is: Is there any clever way of making this work? Without upgrading the spark-mllib version on project A? Upgrading is not a viable option. Project A is big and if I upgrade that version, I screw up just about everything.
[Update]
I even tried to use a ClassLoader (URLClassLoader) in order to load the class directly from spark-mllib_2.11-2.3.0.jar and print all the available methods. Code here:
URLClassLoader clsLoader = URLClassLoader.newInstance(new URL[] {
new URL("file:///home/myhome/spark-mllib_2.11-2.3.0.jar")
});
Class cls = clsLoader.loadClass("org.apache.spark.mllib.regression.LabeledPoint");
Method[] m = cls.getDeclaredMethods();
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
In my .pom file of this project, if I add a dependency of:
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-mllib_2.11</artifactId>
<version>2.3.0</version>
</dependency>
The method public org.apache.spark.ml.feature.LabeledPoint org.apache.spark.mllib.regression.LabeledPoint.asML() is present the results if i use the 2.3.0 version.
But when I use the version 1.6.2 of spark-mllib, it isn't there anymore.
Even though the asML() method is within the spark-mllib's jar. Which is kind of weird.
You can achieve this by creating a shaded dependency of Project B and using it in Project A. Refer to this answer for understanding maven shading and how to use it.

How to solve circular dependency in gradle multi-project build

Consider the following situation. I have two gradle (sub-)projects called "A" and "B". A defines some classes/interfaces that are being referenced by B. So B has a compile dependency to A. Now A is a web server that should be started with B on the classpath. How do you achieve that with gradle?
Of course it is not possible to add B as compile dependency to A because that would mean a circular dependency between A and B. Even adding B as runtime dependency to A did not work because then compile errors in B state that referenced classes from A do not exist. But why?
One solution would be to move code from B into A but I really would like to separate that code because there might be another implementation of B later that I want to swap easily in A (e.g. by exchanging the jar in runtime classpath).
Another solution I was thinking about is to separate classes from A referenced by B into a new module and make both A and B depend on that new module. This sounds valid but that would imply to move persistence layer from A to that new module which feels wrong.
Additional information: A is a Spring boot web application with persistence layer, web services etc, B produces a JAR.
Circular dependencies are a well-known problem when you try to get Dependency Injection. In this case, you have something similar but at a module level
The only way I see you can solve your issue is by creating a third module C with the common code (probably the A interfaces referenced by B)
This way you can compile C (it doesn't have any dependencies), A (it depends on C), and B (it depends on C) and launch A with B in its classpath
Everytime you end up with circular dependency you probably should introduce another entity to break the cycle.
Have a look at my explanation in this other QA article (it's dealing with packages and classes, but idea is the same): What does it mean and how to fix SonarQube Java issue "Cycles between packages should be removed" (squid:CycleBetweenPackages)

Categories

Resources