Why isn't Maven overridden scope recognized transitively? - java

This is an interesting state of affairs in Maven that I didn't expect. Maybe someone can explain exactly why this is happening.
I have a parent POM foobar-parent that declares logback-classic with a test scope in the <dependencyManagement> section.
I have a separate project project example that has its own example-parent, which inherits from foobar-parent and also serves as a parent to its submodules.
One submodule example-foo overrides the dependency logback-classic and gives it compile scope:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>compile</scope>
</dependency>
Lastly I have another submodule example-bar which uses example-foo as a dependency.
Strangely, for the effective POM of example-bar, it shows that logback-classic has test scope!! Since example-foo declares logback-classic to be of compile scope (meaning it is required at compile time), and since example-bar has a compile-time dependency to example-foo, I expected example-bar to bring in logback-classic as a transitive dependency.
The way I interpret this is that the test scope specified in the <dependencyManagement> management section of a parent POM will override the scope of a transitive dependency from the compile scope!! Is this a correct interpretation, and is that how Maven is supposed to work?

You are right: "dependency management takes precedence over dependency mediation for transitive dependencies" (taken from Introduction to the Dependency Mechanism)

Related

Maven dependencies exclusions not working

I'm using enforcer plugin of Maven and I see a behavior that I dont quite understand and it's dangerous.
Let's say that I have a conflict since dependency A has bla.jar:1.0 and is in conflict with my dependnecy B which has bla.jar:2.0
Then to fix the conflict, I make an exclude of bla.jar:1.0 from A
<dependency>
<groupId>com.foo</groupId>
<artifactId>A</artifactId>
<version>a.version.bla</version>
<exclusions>
<exclusion>
<groupId>com.omg</groupId>
<artifactId>bla</artifactId>
</exclusion>
</exclusions>
</dependency>
expecting the application will get the bla.jar:2.0 fron classpath. But then I see when I run some unit test that the java proce3ss cannot find bla.jar ion the classpath at all and is giving me ClassNotFound in runtime.
Any idea what's wrong here?
I have in my pom defined from top to bottom B and then A
Please note that exclusions are not the best way to resolve dependency version conflicts.
The best approach is to use <dependencyManagement>. It allows you to set a version that replaces all transitive versions of that dependency.
In your case, I would first change the exclusion to <dependencyManagement>. Then I would proceed in the following way:
Check mvn dependency:list which version of the dependency is on the classpath. It should be the one specified in <dependencyManagement> unless there is no version of that dependency in your dependency tree. If you find more than one, then probably the groupId changed at some point. Then you need exclusions.
Check the scope of the dependency and verify that it is indeed compile.
Then open the dependency jar and see whether this jar really contains the class for which you get ClassNotFound. Often classes change from version to version.

Why Maven dependency exclusion would not cause compile error?

Newly exposed to Maven, I can understand the use case of the <exclusion> tag, but not sure why it wouldn't cause compile error:
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
</exclusion>
</exclusions>
</dependency>
...
Is this only possible only when you have another direct dependency on maven-core? otherwise, compile error should happen. (assuming maven-core is used somewhere in maven-embedder)
You are excluding that artifact from that specific dependency, but it could be getting pulled in from another dependency. Using something mvn dependency:tree -Dverbose -Dincludes=maven-core should show you what else is introducing the dependency. The Maven Enforcer plugin can also help exclude transitive dependencies.
There are different possibilities:
As Carl said: Check your dependency:tree if the dependency is not pulled in from somewhere else.
It is possible that maven-core is not used at all, even if maven-embedder indeed uses it: Assume e.g. that maven-embedder has two classes A and B. You only use A, but maven-core is only used by B. Then (if A and B do not use each other), your project might be entirely independent of maven-core. (A side remark: some jars should logically be two separate jars, but where merged together by whatever reason - in our example, one should think about putting A and B in separate artifacts).
It is possible that transitive dependencies are not necessary at compile time, but are used at runtime.
The error will not thrown in the compile time, It will thrown in run time if you use any feature depends on maven-core

Need to configure Maven project dependency tree, but *not for me*--it's only for those using my projects via Maven Central

I have a new library, called Codelet. It depends on two other libraries I've created, called Template Featherweight and XBN-Java. But the primary library--the one I'm trying to get other people to use!--is Codelet. The other two are secondary. Both are required, but they are only directly used when advanced features are needed.
I build all aspects of all three of these projects with Ant. I use Maven for one reason only: To sign the jars
codelet-0.1.0.jar
codelet-0.1.0-sources.jar
codelet-0.1.0-javadoc.jar
and push them to Maven Central. After shedding significant blood, sweat, tears, and soul in the past week, as evidenced by these four questions
How to use Maven to only sign three jars and push them to Maven Central?
Followup questions: Using Maven to only sign and deploy jars to Maven Central. Build and compilation is done entirely with Ant
Followup part 2 -- Using Maven to only sign and deploy jars to Maven Central. Build and compilation is done entirely with Ant
Why am I getting a "401 Unauthorized" error in Maven?
I have finally gotten the jars to upload to Maven Central via mvn deploy, at least once anyway, so that's big progress.
I'm realizing, however, that in order for other people to actually use Codelet, they need the entire dependency tree to be mapped out in Codelet's POM. I believe this is correct.
I am concerned that mapping this three-project dependency tree, in their three POMs, will essentially require that I duplicate much of my Ant build process in Maven. I am hoping with all hope that I don't need to do this, as Ant works well for me, and Maven and I do not get along.
Here are the dependency trees:
Dependencies for compilation of core library classes and example code, only
All items are listed by Maven groupId / artifactId / version.
XBN-Java 0.1.3 depends on
org.apache.commons / commons-collections4 / 4.0
org.apache.commons / commons-io / 2.4
org.apache.commons / commons-lang3 / 3.3.2
com.google.guava / guava / 16.0
Template Featherweight 0.1.0 depends on
com.github.aliteralmind / xbnjava / 0.1.3 (and its dependencies)
Codelet 0.1.0 depends on
com.github.aliteralmind / templatefeather / 0.1.0 (and its dependencies)
${java.home}/../lib/tools.jar (This is considered "provided", since it's part of the JDK and not on Maven Central)
Dependencies for compilation and execution of unit tests, only
These are in addition to those needed for core-compilation.
For all projects: junit / junit / 4.11 (and its dependency: hamcrest core)
For compilation of "Codelets" (which are used only by javadoc.exe), and execution of javadoc.exe
These are in addition to those needed for core-compilation.
For all projects: com.github.aliteralmind / codelet / 0.1.0 (and all its "core-compilation" dependencies)
(Some background: Codelet automates the insertion of example code into JavaDoc, using inline taglets like
{#.codelet.and.out com.github.mylibrary.examples.AGoodExample}
These optional "Codelet classes", called "customizers", are compiled before running javadoc.exe. They are used to customize how example code is displayed. Once compiled, Codelet is executed automatically, as are all inline taglets, via javadoc.exe.)
For core-compilation it's pretty much linear:
XBN-Java is the root
Template Feather depends on XBN-Java, and
Codelet depends on Template Feather
But for "javadoc", all three projects depend on Codelet. So even XBN-Java depends on Codelet...which depends on Template Feather...which depends on XBN-Java.
The POMs in all three projects are working, although this is only as far as it concerns signing and pushing the jars to Maven Central.
After reading Maven's dependency Mechanism documentation, it seems that all three projects could have the same flat dependency tree
<dependencies>
<dependency>
<groupId>com.github.aliteralmind</groupId>
<artifactId>templatefeather</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>com.github.aliteralmind</groupId>
<artifactId>codelet</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
But the right way is to have a parent project, which is inherited by the other two. It seems that XBN-Java should be the parent, but given the recursive nature of the dependencies, I'm not sure.
I am not getting the difference between dependencies and dependencyManagement (why some dependencies blocks can go right into the project proper, and others are sub-blocks in dependencyManagement...although it seems related to parent-child), and I also don't understand how "javadoc" fits into the "scope" attribute. While compile and test are explicitely listed, the word "doc" doesn't even exist on the page.
I would appreciate some advice. Thank you.
If your code needs something, but not for compile-time (just for run-time) then declare the dependency, but add a runtime scope.
Now if you want to provide a "starter parent" pom.xml, then release a "starter parent" pom.xml but don't use it in your actual build chain.

Get classpath for integration tests of multi-module project with maven-ant-task

I have a multi-module project built with maven. I need to run the project's integration tests daily. It is not possible to do this during the standard maven build cycle, because on runtime the integration tests defined within the modules have circular dependencies, which are illegal for me to declare on their poms.
Instead, I have created a separate project named Global that lists all modules jars and test-jars as its dependencies. Global has the same parent as all the modules. The idea is that using maven-ant-tasks I will be able to get a classpath of all modules jars and test-jars and go on from there. Global's pom.xml dependency section is as follows:
<dependency>
<groupId>mygroup</groupId>
<artifactId>A</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>mygroup</groupId>
<artifactId>A</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mygroup</groupId>
<artifactId>B</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>mygroup</groupId>
<artifactId>B</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
...etc
The problem is that I cannot seem to get a classpath that contains all jars and test-jars declared on Global's pom.xml (and their runtime dependencies) using the ant tasks available. I have tried (among other things):
<dependencies pathId="cp1" type="jar" usescope="runtime">
<pom file="${basedir}/pom.xml">
<profile id="DEV" />
</pom>
</dependencies>
[1] This one fetches all runtime dependencies. Nothing wrong with that.
<dependencies pathId="cp2">
<dependency groupId="mygroup" artifactId="Global" version="myVersion" scope="test" type="test-jar"/>
</dependencies>
[2] This one fetches all runtime dependencies along with Global-myversion-tests.jar, but no other test-jar.
<dependencies pathId="cp3" type="test-jar" usescope="test">
<pom file="${basedir}/pom.xml">
<profile id="DEV" />
</pom>
</dependencies>
[3] This one fetches nothing.
Obviously, declaring something like [2] once for each module will do the trick, but I am looking to create a setup that will not need to edit a gazillion files each time a new module is added or removed. BTW I am using maven-ant-task-2.1.3.
Thanks for any input.
---Edits for #yannisf accepted answer---
You should not ever have cyclic dependencies
I assume you mean for maven builds. Having cyclic dependencies on runtime is pretty common, for example:
Module A declares interface: UploadToDocumentManagementSystem
Module B implements it in : UploadToCoolDms (that way in the future, when the DMS system changes to CoolerDms module B can be replaced by a new implementation with no side-effects to the rest of the app).
Module B depends on A compile time (and, by definition, runtime as well)
Module A depends on B on runtime
Maven does not allow to declare this. The reason, that I can sympathize with, is that maven needs to complete build cycles (including tests) of multi-module projects in a specific order. Thing is, it is not really necessary to declare it if you get rid of any runtime dependecy to B for the tests of A (which is good practice and should happen anyway).
You should do things the maven way instead of resorting to ant-tasks
Fair enough, I can see how maven-ant-tasks was not made for this use.
In your global pom you are declaring dual types for the same artifact (jar, test-jar)
Is that a problem in general? For example module A contains some samples for its tests that I would like to use in the tests of module B as well. Is it wrong (by maven best practices standards) to declare that B depends on A jar (compile scope) and on A test-jar (test scope)? Won't an integration tests project justify to depend on a module as well as the same module's samples and resources used for its unit tests?
tl;dr version: I will attempt to rearrange the tests declared on the modules and create separate module(s) for integration tests (assuming I can get 20 developers to play ball). Thanks for the answer and for making me admit defeat and stop trying to make maven work with the project instead of making the project work with maven :).
You are trying to break the maven conventions in many ways. 1. You should not ever have cyclic dependencies, 2. You should do things the maven way instead of resorting to ant-tasks 3. In your global pom you are declaring dual types for the same artifact (jar, test-jar).
Although at first this might not seem to answer your question, you should take a step back and rethink your layout. Integration tests need all the dependencies and are much more demanding than unit tests. So, instead of trying to fit them into the existing projects, create a separate maven project in the same group, that will only host integration tests (under src/java/test, main will be blank) and will have as dependencies all the other projects.

Maven 2 - different dependency versions in test and compile

I have project that depends on commons-httpclient [2.0] (compile).
I would like to write some jbehave tests - jbehave-core 3.4.5 (test).
Both this dependencies depend on commons-lang but in different versions - 1.0.1 and 2.5.
When I execute mvn package I get [BUID FAILURE] in tests section.
There is an exception for my testcase in surefire-plugin output:
java.lang.NoSuchMethodError: org.apache.commons.lang.StringUtils.substringBeforeLast(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
As I looked in source code - in commons-lang 1.0.1 - indeed, there is no StringUtils.substringBeforeLast(...) method.
Why maven uses commons-lang from commons-httpclient (compile) and not from jbehave-core in testing?
I can't afford to exclude this conflicting dependency in commons-httpclient so it must stay in compile time.
So how can this be resolved - commons-lang 2.5 version in testing and 1.0.1 in compile time?
Maven 3:
Maven 3 will attempt to obtain the nearest dependency, effectively ensuring that only one of the compile or test scoped dependency is used for both the compile and test phases.
(Thanks Vineet Reynolds)
Maven 2 (OLD):
try to define 2 different <dependency> tags with different versions and scopes. Use tag <scope>test</scope> inside dependency for tests and <scope>compile</scope> for compilation.
In Maven 3, you can trick maven by adding a dot after groupId
<dependency>
<groupId>groupId.</groupId>
<artifactId>artifactId</artifactId>
<version>version1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<version>version2</version>
<scope>compile</scope>
</dependency>
The sequence matters here. need to have test first and then compile.
<dependency>
<groupId>groupId.</groupId>
<artifactId>artifactId</artifactId>
<version>version1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<version>version2</version>
<scope>compile</scope>
</dependency>
Adding a dot doesnt work in pom.xml as . is converted to slash which in return generated incorrect URL.
Is thr any other way to do this
It's a really bad idea to have two different versions for compile and test dependency:
Your non-test code might rely on behavior of the newer JAR and fail when using classes of the older JAR.
When you use the older JAR in your tests, the non-test code would fail with the old JAR.
Otherwise you could have used the older JAR anywhere, with the same version...
If you get both JAR versions into your classpath, you cannot know which one gets picked up when running your tests. That's also a bad idea.
Hence you should get non-test and test to the same JAR version dependency.

Categories

Resources