Different scopes of the same artifact and transitive dependencies issue - java

I have a project A that use library L v1.0.0 with test scope. Project A also depends on project B (with scope compile), with B transitively depending on the library L v1.0.0 (with scope compile).
Why the final scope of the library L for project A is 'test'? It causes me NotClassDefFoundError at runtime. It seems that the dependency definition of project A on library L overrides those of the transitive dependencies on L.
What's wrong here? My project A only uses L for unit tests so I define the dependency with 'test' scope. But, at the end, I want L to be on my classpath since project A depends on project B for production, and B needs (transitively) library L.
Thanks for helping me

Are you using Maven? In that case, if I remember correctly Maven will use the "nearest" definition to determine the actual scope. In this case module A specifies test and the transitive scope from B is overridden, because A is nearest since you're actually in A :)
This gets more complicated when you have multiple modules with dependencies between them.
A common cure is to define all dependencies (and scopes and versions) in a common parent Pom.xml in the <dependencyManagement> tag.

As an alternative to Peter's suggestion, just leave L out of the dependencies for A. You should be able to access it anyway, and Maven will treat it as a compile-scoped dependency.
This hides that A's tests depend on L, though.

Related

fail gradle build if transitive dependency is used directly?

im looking for a way to fail my gradle build if my code directly uses (so imports) a transitive dependency.
what i mean is if my project has a (compile) dependency on module A, and module A depends on B (so B is in my transitive dependencies and available on my runtime classpath) and my code directly imports and uses classes from B, i want my build to fail.
here's a maven plugin that does what i want - https://github.com/Scout24/illegal-transitive-dependency-check - but i cant find a gradle one?
I think the 2 plugins below may be what you're looking for.
https://github.com/wfhartford/gradle-dependency-analyze
From the README:
This plugin attempts to replicate the functionality of the maven dependency plugin's analyze goals which fail the build if dependencies are declared but not used or used but not declared.
https://github.com/nebula-plugins/gradle-lint-plugin
On the Unused Dependency Rule wiki page:
Promotes transitive dependencies that are used directly by your code to explicit first order dependencies
I have to say I haven't used either one myself, but they seem to address your concern.
I think that you want to use the java library plugin and the api/implementation configurations.
For the legacy java plugin you could do
dependencies {
compile('group:module-b:1.0') { transitive = false }
}
This would force you to explicitly declare module-b's transitive dependencies if you need to use them.
Note: You'll probably get lots of ClassNotFoundException using module-b at runtime since there's now jars missing from your classpath. So this might be better
dependencies {
compile('group:module-b:1.0') { transitive = false }
runtime 'group:module-b:1.0'
}

NoClassDefFoundError when Maven test-scoped dependency overrides transitive compile-scoped dependency

We have several deployables, and have extracted common code into several libraries. Deployable app A has a (default-scoped) Maven dependency on library B, and a test-scoped dependency on library C.
I made a change to library B, which used a method from, and so created a dependency on, library C. This caused app A to fail at run-time with NoClassDefFoundErrors. App A's unit tests all passed, of course, so we only found this in our staging environment. (Other apps either had no direct dependency on library C, or had a default-scoped dependency on it, and so continued to work.)
The obvious fix worked, of course: I edited A's pom.xml to make the dependency on C default scope.
My question is: Are we doing something wrong? Or is this how things are supposed to work? Is anyone aware of an automated way of detecting the potential problem at build-time and failing the build?
You are not doing something wrong. Maven made a peculiar design choice here. See also
Maven: test vs. transitive compile, especially Tunakis comment on my question.
and
Maven dependency within dependency with different scope
My advice: Eliminate C from A's pom.xml. Only add test dependencies if these dependencies are not already on the dependency tree.

Transitive effect of dependencyManagement

In Maven you can override the version number of a transitive dependency by an entry in dependencyManagement because dependencyManagement takes precedence over transitive dependency definitions.
But what about dependencyManagement definitions in the poms of (transitive) dependencies? Are they considered at all? If so, what do they override, how are they overridden?
Dependency management is implied to be transitive. There doesn't need to be a special rule for this, but rather it's a consequence of the already mentioned rules: Transitive Dependencies.
Consider this example structure:
your module
A - dependency
D - transitive dependency
B - dependency
D - transitive dependency
When A or B are built, their corresponding dependencyManagement section is checked to pick version for D, if it's not explicitly specified. Here's the important part: exactly the same process is used when A or B are used as dependencies to determine which version of D they depend on. Consequently, they do not affect each other in any way.
This could result, for example, in A depending on D:1.0, and B depending on D:1.1, their dependencyManagement sections have already been applied at this point to determine this and will not be taken into account anymore. With this information as input, dependency mediation rules are applied to pick just one version of D for your module.
Dependency mediation rules are also described in the linked page. But in a nutshell, the nearest definition wins and ties are broken based on order. Naturally, definitions in your module itself are always the nearest.
Now let's say your module is used as a dependency. Your project could depend on D:1.2 based on all the rules above due to a definition in your dependencyManagement section, but that's where the scope of your dependencyManagement ends.
(Note that import scope is an exception as it behaves completely differently from the other scopes.)
dependencyManagement definitions in the poms of transitive dependencies are considered as long as they are not been overridden in the dependencyManagement of your project or a closer dependency (in the tree of dependencies).
in another words,
Dependency mediation: the rule is easy
"nearest definition" which means that it will use the version of the closest dependency to your project in the tree of dependencies.
if two dependency versions are at the same depth in the dependency tree, the first declaration wins (declaration order).
for more details see Transitive Dependency
hope this helps.

Maven: How do you deal with dependencies that are both direct and transitive?

I'm trying to determine an approach to the following situation:
There are 3 Maven artifacts: A, B, and C.
B depends on A. (i.e. it uses some of A's code)
C depends on both A and B (i.e. it uses some of A's code and B's code).
Assume I want to use the same version of A for both B and C.
What approach should be used?
1) Declare A as a dependency in C's pom.xml.
Pro: It's clear to the developer that C depends on A.
Con: If A's version changes, it needs to be updated in multiple places. (both B and C)
2) Don't declare A as a dependency in C's pom.xml.
Pro/Con: Opposite of option 1.
I think you should have all direct dependencies declared in your pom. Transitive dependencies are just a convenience for automagically resolving your dependencies dependencies.
If you change a version of a direct dependency, the transitive dependencies will likely change along with it, and thus potentially breaking the module. The module should build as an independent unit and thus should have well defined dependencies that will not break due to external changes.
I disagree that this violates the DRY principal, as maven defines things within the confines of a single project and its pom. And within this scope there is no repetition.
Update:
The reliance on transitive dependencies existing makes the project frail on it's own, and may also lead to more complex issues like when to include it.
For example, if C has a compile dependency on A, but a runtime dependency on B, then you now have to either add the dependency (since it is no longer in your build path) or declare B as compile even though it isn't. There is a lot to be said for clarity. Explicitly define what your dependencies are and what their scope is, and expect your dependencies to do the same. For the most part, your dependency is a black box, until it causes problems and you have to open it.
1) Declare A as a dependency in C's pom.xml.
The dependency is readableDependency is flexible. If you want to remove A's dependency from B, you do not need to think of the projects that dependend on B.As suggested in other answer, that it is a good practice to write down direct dependencies in pom.xml and let maven handle it.
2) Don't declare A as a dependency in C's pom.xml.
Mostly, no developer going to see pom.xml. And if they want they can see it by using mvn dependency:tree and it will show transitive dependency.There will be single point of change when a new version of A is released. If you define dependency at more than one place, you may forget to update all the places. In that case, Maven automatically uses the latest one. But, it does sting sometimes. Some people prefer this because, mostly, this type of dependency is common knowledge (e.g. MyWebApp -> MyWebAppLib -> MySharedLib and MyWebApp -> MySharedLib) and they want to avoid added step of updating versions at multiple places on each release.
I have written down pros-and-cons, you should evaluate what suits you the best yourself.
Edit#1: tsk! I have switched my comments.
Edit#2: updated the answer after a discussion done on this answer.

How do I properly setup a multi-module Maven project with sliding release cycles

I'm trying to work out the best way to setup our multi-module Apache Maven project in a way that allows for disparate release cycles of modules, and doesn't introduce dependency issues when debugging the project.
We currently have a setup along the lines of:
bigsystem#1.2
parent-1.1-SNAPSHOT
module a#1.4-SNAPSHOT
parented by parent#1.1-SNAPSHOT
module b#1.3-SNAPSHOT
parented by parent#1.1-SNAPSHOT
depends on a#1.1
module c#1.1-SNAPSHOT
parented by parent#1.1-SNAPSHOT
depends on a#1.2
depends on b#1.1
The dependencies declared in modules b and c contain the minimum version required to compile the module, which isn't necessarily the current version of the module, or the version of the module being deployed.
From a build perspective this works well, each module can be released/updated as needed, however when trying to debug the deployed application under IntelliJ IDEA (versions 8 and 9 EAPs) having opened the top level pom, IDEA decides that since we declared a dependency on a#1.2, that anytime we step into one of a's classes, it should open it from a-1.2-sources.jar rather than the current a#1.4 sources in the project. This is further confused by the fact that stepping into any of b's classes takes us to b#1.1 rather than b#1.3.
My initial attempt to work around this was to declare the version numbers in the parent pom's dependencyManagement section and just have the sub-modules inherit the version. This worked to the degree of solving the IDEA debug issue as the dependencyManagement section can point everyone to the current -SNAPSHOT versions.
This unfortunately causes a problem when doing a maven release due to having to release the parent pom before releasing the module, but as the parent may refer to multiple in-development -SNAPSHOTS it can't be released and we end up adding version references back to the modules pom to satisfy the release.
It would seem that using maven's dependencyManagement section would only really work well if we were releasing ALL bundles at the same time, regardless of if they changed, but as we're wanting to manage releases of each sub module only when needed this model doesn't seem to fit.
I have a suspicion I'm missing something, and that a combination of dependencyManagement and version ranges might satisfy out requirements although I've yet to see version ranges work properly.
Is there a better way? A proper way?
I would recommend not making them modules, but make their POMs independent. That way you do not have to worry about trying to satisfy parent POM dependencies. Since they are released independently, they really should have independent project object models. Think of Apache Commons as a template.
I think the problem with IDEA arises because you are using the root POM in your source structure to do two things that are usually mutually exclusive in Maven. You are first using the POM as a location to store common configuration information for unrelated (from a build perspective) Maven projects. Secondly you are using the POM as an aggregator for your build. You can do each of these without doing the other.
Like Rob said, remove your module a, b, etc. projects from the modules section of your parent POM. Secondly, move your parent POM down into its own directory as it is really a sibling of the other modules with respect to your build and release process. The way you have it now, it is more of a parent/aggregator.
The way you have it now also doesn't lend itself to tagging and releasing each module individually as a tag of your parent POM would likely needlessly include all of the module sub-folders.
Your file structure would look like:
parent
pom.xml
module a
pom.xml
module X
pom.xml
As for the thing you are missing, dependencyManagement isn't really well suited to manage versions for intra-project dependencies. That is dependencies between modules within an aggregated build. It is more well suited for declaring global versions for external dependencies.
The final/working solution we ended up using was fairly similar to what we started with. The actual project structure remains the same:
bigsystem#1.2
parent-1.1-SNAPSHOT
module a#1.4-SNAPSHOT
o parented by parent#1.1-SNAPSHOT
module b#1.3-SNAPSHOT
o parented by parent#1.1-SNAPSHOT
o depends on a#1.1
module c#1.1-SNAPSHOT
o parented by parent#1.1-SNAPSHOT
o depends on a#1.2
o depends on b#1.1
distribution a#1.2-SNAPSHOP
However the main differences are that:
parent module does not include any versions of project artifacts
individual modules fully declare their project dependencies and specify a version range, i.e. [1.0.0,1.1.0)
all modules start there version number cycles from .1, i.e 1.0.1-SNAPSHOT, this allows the version range to satisfied by initial snapshots (1.0.0-SNAPSHOT is earlier than 1.0.0 final, so not included).
distribution pom (not initially shown in question) identifies the exact version to be deployed/included in a specific release.
delete all project -SNAPSHOTS from local maven repository when releasing so that ranges pickup releases only ( or use -Dmaven.repo.local=/tmp/sometemprepo for a fresh local repo)
This makes each module more standalone and gives us the freedom to release and deploy new versions of our project artifacts with minimal fuss.
They certainly seem like separate modules. What benefits are you gaining by smashing them together if they have different dependencies, even within the multi-module project?

Categories