How to export dependencies whole? - java

I have a library B that depends on another library A.
A third library C depends on B and A.
I can satisfy C's dependencies with
dependencies {
implementation files('/path/A.jar')
implementation files('/path/B.jar')
}
but I would rather only declare B and build B.jar in such a way that it contains and exposes A as well.
I know that with api files('/path/A.jar') B can expose the parts of A that it uses in interfaces, but (my experience is that) it doesn't let consuming projects import anything from A explicitly.
How can B expose A completely?

files() is only for local flat file use, there are 2 mechanisms to share transitive dependencies ...
project(':other') (project dependency)
'group:artifact:version' (maven repository dependency)
https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:repository-types
if the source for B & A is available locally & can be built along with C ... then declaring like below is possible
implementation project(':A')
implementation project(':B')
else you go with proper maven repository artefact.

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 ...

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.

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)

What is the best way to manage project dependencies

I have following dependency hierarchy in a service:
Service A
-> Lib A
-> Lib B
-> Lib A
-> Lib C
-> Lib A
-> Lib D
-> Lib A
"->" means depends on.
There are lot of problems that pops up because of above structure. The one that requires most of the efforts is to keep the Lib A sync across all modules to avoid class conflicts and other issues.
I am using maven for dependency management but it doesn't solve the issue as I have to update all dependencies to avoid conflicts (semantic and functional)
Is there any better way to manage these dependencies?
Edit 1:
Scenario 1: Adding new code to Lib A which is only going to be used by Lib B.
In this case, it wouldn't be sufficient to change Lib B. I will have to add Latest version of Lib A in service A so that maven picks up the correct version.
Scenario 2: Non Backward compatible changes in A.
This will cause problem if I just update Lib A in Service A because other libs (B, C and D) doesn't know about this new change and it might break (e.g Adding a new argument in an existing method method). I will have to update all of them.
Scenario 3: Changing an existing method in Lib A.
This will work fine. If I update this Lib A in service A, maven will pick up latest Lib A and all libs (B,C and D) will use latest version of Lib A.
Not sure if i understand the question right , are you mentioning of dependency conflict between libraries.
If you have same dependencies across libraries , one way of sorting it out could be to use exclusions , that way you can add one dependency and exclude the others (https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html)
You might look into OSGi or Java 9 modules (not finished yet of course). I am not an expert but I believe these frameworks allow for a concept of modules/bundles where each module has its own class loader. This means if lib B and lib C were modules they could have different versions of lib A and everything would work smoothly. It does prevent lib B and lib C from communicating directly but given your dependency tree that should be desired.
--Update to match your update--
Scenario 1:
Update lib A version only in module B. Module C/D can remain unchanged.
Scenario 2:
You can update lib A in module B/C/D independently when each of those modules is ready to use the new version of lib A.
Scenario 3:
No issue.
Think about Domain Driven Design and more specifically Bounded Context Pattern. It allows code duplication to certain degree. The flexibility will allow you to decouple "jar A" from the rest very easily.
What exactly is a Bounded Context?
Lets imagine that we have User. This User in one context can be UserAdmin or in another context it can be PowerUser or in another context can be just User.
Now lets imaging that we have 3 services addressing three different functions of a User based on hist type. Each Service represents a different Context of the term User. Now the point here is that instead of creating one almighty "User" in your case it will go in the almighty "libA" we will create 3 users based on the context and will give different functions of these 3 users. Eventually we will end up with 3 domain objects AdminUser for service 1 PowerUser for service 2 and just User for service 3. This three services will not have dependencies on each other, unless it fits us and even if they have it will be piece of cake to decouple them at any point.
If we start thinking of a service as Onion. On the first layer we would have persistence. On the second layer we will start unwrapping our domain model. On the next layer we will start building our services. Then our network representation and so on.
Thinking in terms of Bounded Context will allow you to create a unique Bounded context on per service basis and this will allow you to not have this interdependencies in between the different Jars . This is because your service will not necessarily share code with other services.
Service A
-> Project Commons
-> Lib B_A (functional dependency, commons B no functional dependency with C and D)
-> Lib C_A (functional dependency, commons C no functional dependency with B and D)
-> Lib D_A (functional dependency, commons C no functional dependency with B and C)
-> Lib A (technical dependencies , commons in B C D)
-> pom_parent
-> Lib B
-> Lib A and Lib B_A
-> Lib C
-> Lib A and Lib C_A
-> Lib D
-> Lib A and Lib D_A
pom_parent (SNAPSHOT)

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

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.

Categories

Resources