Why aren't 'provided' Maven dependencies 'transitive'? - java

Why doesn't Maven inherit provided dependencies?
My situation:
I have 2 independent projects A and B.
I don't own project A.
A and B use a some of the same libraries:
reflections-0.9.9-RC1.jar
guava-11.0.2.jar
xml-apis-1.0.b2.jar
javassist-3.16.1-GA.jar
dom4j-1.6.1.jar
jsr305-1.3.9.jar
I made project C, which is a plugin for project A, but also uses project B.
Project C pom.xml:
<dependencies>
<dependency>
<groupId>com.a</groupId>
<artifactId>a</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.b</groupId>
<artifactId>b</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Now I want to make plugins for project C but I can't.
If I create project D with a dependency to project C,
it won't inherit the dependency to project A.
It will if I set the scope to compile but that would shade it into project C which is not useful and would cause duplicates.
So now I have to add dependency to both A and B with every plugin I make.
Compile -This is the default scope, used if none is specified. Compile dependencies are available in all classpaths of a project. Furthermore, those dependencies are propagated to dependent projects.
Provided - This is much like compile, but indicates you expect the JDK or a container to provide the dependency at runtime. For example, when building a web application for the Java Enterprise Edition, you would set the dependency on the Servlet API and related Java EE APIs to scope provided because the web container provides those classes. This scope is only available on the compilation and test classpath, and is not transitive.
Why not?

There is an open bug for that exact requirement: MNG-2205. It is currently in the backlog for version 3 of Maven but I wouldn't get your hopes up: it was created in April 2006 (!).
Quoting Jason van Zyl from that bug report:
It is unlikely we will change the behavior of the provided scope, but it would be possible to create a new 'provided-transitive' if we really wanted this. Changing the definition of existing scopes would be problematic.
Also, quoting Andrew Williams, still from that bug report:
if C wants to use Sybase JConnect then it must declare this as a dependency. A could at any time change it's dependencies and "break" this assumption of C's.
It is wrong to use a dependency that you do not declare.
There is no better answer to this question: the documentation is quite clear on the subject: provided dependencies are not currently transitive. The reason it was initially done this probably revolves around the fact that you should explicitely declare a dependency if you intend to use it.

Related

Component index vs classpath scanning in Spring 5 [duplicate]

Spring Framework 5 apparently contains support for a "component index" which lives in META-INF/spring.components and can be used to avoid the need for class-path scanning, and thus, I assume, improve a webapps' startup time.
See:
The "what's new in spring 5" mention
The jira issue under which the support was developed
Some examples of what the spring.components format seems to be from the change implementing it
How can I create such a component index for an existing web app I plan to upgrade to Spring 5?
(Ideally it would get generated automatically at build time with Maven I imagine, but any other workable approaches would at least give me a starting point to work from)
Spring 5 Has added a new feature to improve startup performance of large applications.
it creates a list of component candidates at compilation time.
In this mode, all modules of the application must use this mechanism as, when the ApplicationContext detects such index, it will automatically use it rather than scanning the classpath.
To generate the index, we just need to add below dependency to each module
Maven:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.0.3.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
Gradle
dependencies {
compileOnly("org.springframework:spring-context-indexer:5.0.3.RELEASE")
}
This process will generate a META-INF/spring.components file that is going to be included in the jar.
Reference : 1.10.9. Generating an index of candidate components
The META-INF/spring.components files are generated by an annotation processor library called spring-context-indexer. If you add this library as "annotation processor path" to the maven-compiler-plugin, the files will be generated automatically at build time:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.0.6.RELEASE</version>
</path>
</annotationProcessorPaths>
...
</configuration>
</plugin>
This setup requires maven-compiler-plugin version 3.5 or greater.
See also: https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#annotationProcessorPaths
Kotlin + Maven:
To generate the Spring Component Index when building with Maven and Kotlin:
Kotlin Maven Plugin includes Kapt - Kotlin Annotation Processing Tool. It has a goal kapt which needs to execute before compile (it uses the sources, not the bytecode.
See also:
https://kotlinlang.org/docs/kapt.html#using-in-maven
https://maven.apache.org/ref/3.8.6/maven-core/lifecycles.html
I put the execution into a profile, so that I can get rid of this if not needed.
mvn install -PcreateSpringComponentIndex
Important: You need to keep this updated for each compilation as a matter of habit, otherwise Spring won't pick the new(ly) annotated classes as components! That also means, that if skipping the generation, you need to mvn clean.
Important: When using "shading" (putting all classes and resources into a single flat jar), the files META-INF/spring.components need to be merged! Otherwise one of them will be picked randomly and Spring won't detect any other components. (It's better to avoid shading and pack the dependencies as JARs within a JAR).
Example:
<!-- May speed up the app boot by a couple of seconds. See https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-scanning-index -->
<profile>
<id>createSpringComponentIndex</id>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-maven-plugin</artifactId><version>${kotlin.version}</version>
<executions><execution><id>kapt</id><goals><goal>kapt</goal></goals><phase>process-classes</phase></execution></executions>
<configuration>
<sourceDirs><sourceDir>src/main/kotlin</sourceDir><sourceDir>src/main/java</sourceDir></sourceDirs>
<annotationProcessorPaths>
<annotationProcessorPath><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId><version>5.3.23</version></annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId><version>5.3.23</version><scope>provided</scope></dependency>
</dependencies>
</profile>

Classes in test package not available at compile time in IntelliJ

I have a setup like the following:
Where I have two modules: modulea and moduleb, in this case, moduleb has a dependency to modulea defined as:
<dependency>
<groupId>org.example</groupId>
<artifactId>module-a</artifactId>
<version>1.0-SNAPSHOT</version>
<type>test-jar</type>
</dependency>
This allows me to use ClassInTestA in ClassInSourceB without any issues while developing:
However, when I try to build the project, this error prevents IntelliJ to complete the build:
I have come across similar questions in SO:
Question 1
Question 2
Question 3
However, none of the proposed solutions has been able to help my case. I have created an MVCE that is available here as zip and in GitHub.
The real-world project I'm working is neo4j, which follows this structure. Moreover, compilations using mvn install/package work without any issue, the problem appears when working inside IntelliJ.
In general, it makes sense to "open" a new project by building it first outside of IntelliJ with mvn clean package and then import it by just "open"ing the parent module. This worked for me:
And even after a rebuild:
If you don't want to reimport your project by deleting all IDEA folders and files and use the described way above, you can try to build the project via the Maven toolbar (clean and package on the parent module) and then use the "Reimport all Maven projects" button:
At least sometimes this works for me, but honestly not always.
Test classes aren't packed in the final artifact. To share the test classes you'll have to use the jar maven-jar-plugin in modulea:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
and add a dependency in moduleb's pom.xml:
<dependency>
<groupId>org.example</groupId>
<artifactId>module-a</artifactId>
<version>1.0-SNAPSHOT</version>
<classifier>tests</classifier>
</dependency>
Test sources are not included during the compile phase
See Apache Maven Compiler Plugin:
compiler:compile is bound to the compile phase and is used to compile
the main source files.
I think main sources should not depend on test sources. Test sources are only for testing the main sources. You could place ClassInTestA under module-a/src/main/java.

How can I create a Spring 5 component index?

Spring Framework 5 apparently contains support for a "component index" which lives in META-INF/spring.components and can be used to avoid the need for class-path scanning, and thus, I assume, improve a webapps' startup time.
See:
The "what's new in spring 5" mention
The jira issue under which the support was developed
Some examples of what the spring.components format seems to be from the change implementing it
How can I create such a component index for an existing web app I plan to upgrade to Spring 5?
(Ideally it would get generated automatically at build time with Maven I imagine, but any other workable approaches would at least give me a starting point to work from)
Spring 5 Has added a new feature to improve startup performance of large applications.
it creates a list of component candidates at compilation time.
In this mode, all modules of the application must use this mechanism as, when the ApplicationContext detects such index, it will automatically use it rather than scanning the classpath.
To generate the index, we just need to add below dependency to each module
Maven:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.0.3.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
Gradle
dependencies {
compileOnly("org.springframework:spring-context-indexer:5.0.3.RELEASE")
}
This process will generate a META-INF/spring.components file that is going to be included in the jar.
Reference : 1.10.9. Generating an index of candidate components
The META-INF/spring.components files are generated by an annotation processor library called spring-context-indexer. If you add this library as "annotation processor path" to the maven-compiler-plugin, the files will be generated automatically at build time:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.0.6.RELEASE</version>
</path>
</annotationProcessorPaths>
...
</configuration>
</plugin>
This setup requires maven-compiler-plugin version 3.5 or greater.
See also: https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#annotationProcessorPaths
Kotlin + Maven:
To generate the Spring Component Index when building with Maven and Kotlin:
Kotlin Maven Plugin includes Kapt - Kotlin Annotation Processing Tool. It has a goal kapt which needs to execute before compile (it uses the sources, not the bytecode.
See also:
https://kotlinlang.org/docs/kapt.html#using-in-maven
https://maven.apache.org/ref/3.8.6/maven-core/lifecycles.html
I put the execution into a profile, so that I can get rid of this if not needed.
mvn install -PcreateSpringComponentIndex
Important: You need to keep this updated for each compilation as a matter of habit, otherwise Spring won't pick the new(ly) annotated classes as components! That also means, that if skipping the generation, you need to mvn clean.
Important: When using "shading" (putting all classes and resources into a single flat jar), the files META-INF/spring.components need to be merged! Otherwise one of them will be picked randomly and Spring won't detect any other components. (It's better to avoid shading and pack the dependencies as JARs within a JAR).
Example:
<!-- May speed up the app boot by a couple of seconds. See https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-scanning-index -->
<profile>
<id>createSpringComponentIndex</id>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-maven-plugin</artifactId><version>${kotlin.version}</version>
<executions><execution><id>kapt</id><goals><goal>kapt</goal></goals><phase>process-classes</phase></execution></executions>
<configuration>
<sourceDirs><sourceDir>src/main/kotlin</sourceDir><sourceDir>src/main/java</sourceDir></sourceDirs>
<annotationProcessorPaths>
<annotationProcessorPath><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId><version>5.3.23</version></annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId><version>5.3.23</version><scope>provided</scope></dependency>
</dependencies>
</profile>

How to exclude a direct dependency of a Maven Plugin

I want to exclude a direct dependency of a Maven plugin and the approach described in this answer does not work (as indicated by this comment).
As a particular example:
<build>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.2</version>
<!-- more config -->
<dependencies>
<dependency>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.2</version>
<exclusions>
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
I still see javax.xml.bind:jaxb-api in the list of dependencies (with mvn ... -X). What am I doing wrong?
(In case someone has an idea for how to replace the dependency on that artifact with the JDK 9 equivalent for that API [as seems to happen on Java 8, where "JAXB API os loaded from the [jar:...jre/lib/rt.jar]"], I'm happy to open a new issue for that.)
Update
Running out of ideas and this being an experiment anyways, I excluded the dependency by editing the plugin's pom.xml in my local repository. Now mvn ... -X shows that there is also an indirect dependency (in this case by org.jvnet.jaxb2.maven2:maven-jaxb22-plugin) that I can successfully exclude with the mechanism above. Just using both excludes, from maven-jaxb2-plugin and maven-jaxb22-plugin, does not do the trick. This indicates that exclusion works in general but apparently not on a plugin's direct dependency.
(By the way, this indeed lead to "Java JAXB API is loaded from the [jrt:/java.xml.bind]", which was my goal.)
Up until now there hasn't been any reason to do this, but this seems like a valid one. Most clean solution I can think of is allowing to override the scope with "none" for plugin dependencies.
I've created MNG-6222 for it, not sure if we'll fix this for a Maven3, but it makes sense to do it at least for the next major.
I had a similar situation with maven-linkcheck-plugin in the end I did a more brute force approach to remove the doxia-linkcheck dependency and make it use my fork by forking maven-linkcheck-plugin and creating my own with the proper dependencies.
Check your dependency list with dependency:tree and solve whichever lib is introducing that lib dependency, then exclude it. Maybe your dependency couldn't be a direct one.
Just follow your dependency hierarchy

Does Maven have a way to get a dependency version as a property?

I'm using a BOM to import dependencies from another project to mine, and I need a way to reference a dependency's version that is already declared in said BOM. So far, I've attempted to list the dependency version as a property in the BOM, but this approach fails because properties don't get imported with BOMs.
I've seen where the Dependency Plugin's dependency:properties goal does almost exactly what I need, but instead of giving me a full path of the artifact I need the version as a property. Is there something out there that can give me the version of a resolved artifact as a property?
UPDATE - 'Why not use a parent pom?'
I commonly find myself working in application server environments, where the dependencies provided are specified with BOM artifacts (as it appears that this has become a somewhat common/standard way to distribute groups of inter-related artifacts, i.e. widlfly). As such, I want to treat the BOM as the single source of truth. The idea of doing something like re-delcaring a dependency version property that has already been defined in a BOM seems incorrect.
If I were to define properties in a parent pom that mirrored an application server's environment, I now have to worry about keeping parent pom properties and BOM properties in sync - why even have a BOM at all at that point?
The information is already available on the dependency tree, it's just a matter of exposing it...
Couldn't find any existing maven or plugin functionality for this, so I forked the old dependencypath-maven-plugin and altered it to use versions. Now I can drop in a plugin like this:
<build>
.
.
<plugins>
.
.
<plugin>
<groupId>io.reformanda.semper</groupId>
<artifactId>dependencyversion-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>set-all</id>
<goals>
<goal>set-version</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
And access properties like this:
groupId:artifactId:type[:classifier].version
I.E.
io.undertow:undertow-core:jar.version=1.3.15.Final
Check out the README for more info on how to use the plugin. It's available # Maven Central:
<dependency>
<groupId>io.reformanda.semper</groupId>
<artifactId>dependencyversion-maven-plugin</artifactId>
<version>1.0.0</version>
</dependency>
... plugins all the way down ...
Short answer - yes, you can.
In details, your root pom.xml:
<properties>
<slf4j.version>1.7.21</slf4j.version>
</properties>
...
<dependencyManagement>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
...
</dependencyManagement>
In modules pom.xml:
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
...
</dependencies>
Also you can use ${slf4j.version} value to filter resources or in plugin configurations.
Update
In case you cannot use properties in the parent POM, you can either
retreive all dependencies and their versions with dependency:list plugin; or
use together dependency:list + antrun:run plugin; or
configure CI server scripts to do it for you (e.g. with this example); or
write a custom plugin to handle your versions logic.
This maven plugin is on Github (https://github.com/semper-reformanda/dependencyversion-maven-plugin) and it is a must for anyone dealing with Dependency versions, for instance when using Webjars dependencies - you can inject Webjar version numbers directly into your web resources.
I had been looking for such a functionality for a long time, I hope more people come across it and that it gets up on Maven central (I actually think it should come with Maven out of the box)

Categories

Resources