blob: 2127c25ad780b35fe20992773ee41bf0ca57d204 [file] [log] [blame]
------
Optional Dependencies and Dependency Exclusions
------
Allan Ramirez
------
23 January 2006
------
~~ Licensed to the Apache Software Foundation (ASF) under one
~~ or more contributor license agreements. See the NOTICE file
~~ distributed with this work for additional information
~~ regarding copyright ownership. The ASF licenses this file
~~ to you under the Apache License, Version 2.0 (the
~~ "License"); you may not use this file except in compliance
~~ with the License. You may obtain a copy of the License at
~~
~~ http://www.apache.org/licenses/LICENSE-2.0
~~
~~ Unless required by applicable law or agreed to in writing,
~~ software distributed under the License is distributed on an
~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~~ KIND, either express or implied. See the License for the
~~ specific language governing permissions and limitations
~~ under the License.
~~ NOTE: For help with the syntax of this file, see:
~~ http://maven.apache.org/doxia/references/apt-format.html
Introduction
This section discusses the functionality of optional dependencies and
dependency exclusions. This will help users to understand what are they,
how to use them, how they work and when is the best way to use them.
It also explains why exclusions are made as per dependency basis not in
a POM level.
* Optional Dependencies
Optional dependencies are used when it's not really possible
(for whatever reason) to split a project up into sub-modules.
The idea is that some of the dependencies are only used for certain features
in the project, and will not be needed if that feature isn't used. Ideally,
such a feature would be split into a sub-module that depended on the core
functionality project...this new subproject would have only non-optional
dependencies, since you'd need them all if you decided to use the
subproject's functionality.
However, since the project cannot be split up (again, for whatever reason),
these dependencies are declared optional. If a user wants to use
functionality related to an optional dependency, they will have to redeclare
that optional dependency in their own project. This is not the most clear
way to handle this situation, but then again both optional dependencies and
dependency exclusions are stop-gap solutions.
** Why use optional dependencies?
It's not only important to declare optional dependencies in order to save
space/memory/etc. It's vital to control the list of actual dependencies a
person needs in order to use a project, since these jars may eventually make
it into a WAR, EAR, EJB, etc. Inclusion of the wrong jars may violate a
license agreement, cause classpath issues, etc.
** How do I use the optional tag?
A dependency is declared as optional by simply setting the \<optional\> tag
to true in your dependency declaration. See the sample below:
+---+
<project>
...
<dependencies>
<!-- declare the dependency to be set as optional -->
<dependency>
<groupId>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0</version>
<scope>compile</scope>
<optional>true</optional> <!-- value will be true or false only -->
</dependency>
</dependencies>
</project>
+---+
** How do optional dependencies work?
+---+
Project-A -> Project-B
+---+
The diagram above says that Project-A depends on Project-B. When A
declares B as an optional dependency in its POM, this relationship remains
unchanged. Its just like a normal build where Project-B will be added in its
classpath.
+---+
Project-X -> Project-A
+---+
But when another project(Project-X) declares Project-A as a dependency in
its POM, the optional dependency takes effect. You'll notice that
Project-B is not included in the classpath of Project-X; you will
need to declare it directly in your POM in order for B to be included
in X's classpath.
** Example
Let us say that there is a project named <X2> that has similar functions
with <Hibernate> which supports many database drivers/dependencies such as
mysql, postgre, oracle etc. All of these dependencies are needed for X2
to build but not for your project, so it is very practical for X2 to
declare these dependencies as optional, so that whenever your project
declares X2 as a direct dependency in your POM, all the drivers supported
by the X2 will not be automatically included to your project's classpath
instead you'll have to declare it directly on what driver/dependency of the
database you are going to use.
* Dependency Exclusions
Since maven 2.x resolves dependencies transitively, it is possible
for unwanted dependencies to be included in your project's classpath.
Projects that you depend on may not have declared their set of dependencies
correctly, for example. In order to address this special situtation,
maven 2.x has incorporated the notion of explicit dependency exclusion.
Exclusions are set on a specific dependency in your POM, and are targeted
at a specific groupId and artifactId. When you build your project, that
artifact will not be added to your project's classpath <by way of the
dependency in which the exclusion was declared>.
** How to use dependency exclusions
We add the \<exclusions\> tag under the \<dependency\> section of the pom.
+---+
<project>
...
<dependencies>
<dependency>
<groupId>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0</version>
<scope>compile</scope>
<exclusions>
<exclusion> <!-- declare the exclusion here -->
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
+---+
** How dependency exclusion works and when to use it <<( as a last resort! )>>
+---+
Project-A
-> Project-B
-> Project-D <! -- This dependency should be excluded -->
-> Project-E
-> Project-F
-> Project C
+---+
The diagram shows that Project-A depends on both Project-B and C. Project-B
depends on Project-D. Project-D depends on both Project-E and F. By default,
Project A's classpath will include:
+---+
B, C, D, E, F
+---+
What if we dont want project D and its dependencies to be added to
Project A's classpath because we know some of Project-D's dependencies
(maybe Project-E for example) was missing from the repository, and you
don't need/want the functionality in Project-B that depends on Project-D
anyway. In this case, Project-B's developers could provide a dependency on
Project-D that is \<optional\>true\</optional\>, like this:
+---+
<dependency>
<groupId>sample.ProjectD</groupId>
<artifactId>ProjectD</artifactId>
<version>1.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>
+---+
HOWEVER, they didn't. As a last resort, you still have the option to exclude it
on your side, in Project-A, like this:
+---+
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
...
<dependencies>
<dependency>
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>sample.ProjectD</groupId> <!-- Exclude Project-D from Project-B -->
<artifactId>Project-D</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
+---+
If we deploy the Project-A to a repository, and Project-X declares a
normal dependency on Project-A, will Project-D be excluded from the
classpath still?
+---+
Project-X -> Project-A
+---+
The answer is <<Yes>>. Project-A has declared that it doesn't need
Project-D to run, so it won't be brought in as a transitive dependency
of Project-A.
Now, consider that Project-X depends on Project-Y, as in the diagram below:
+---+
Project-X -> Project-Y
-> Project-B
-> Project-D
...
+---+
Project-Y also has a dependency on Project-B, and it does need the features
supported by Project-D. Therefore, it will NOT place an exclusion on
Project-D in its dependency list. It may also supply an additional
repository, from which we can resolve Project-E. In this case, it's important
that Project-D <<is not>> excluded globally, since it is a legitimate
dependency of Project-Y.
As another scenario, what if the dependency we don't want is Project-E
instead of Project-D. How will we exclude it? See the diagram below:
+---+
Project-A
-> Project-B
-> Project-D
-> Project-E <!-- Exclude this dependency -->
-> Project-F
-> Project C
+---+
Exclusions work on the entire dependency graph below the point where they
are declared. If you wanted to exclude Project-E instead of Project-D,
you'd simply change the exclusion to point at Project-E, but you wouldn't
move the exclusion down to Project-D...you cannot change Project-D's POM.
If you could, you would use optional dependencies instead of exclusions,
or split Project-D up into multiple subprojects, each with nothing but
normal dependencies.
+---+
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
...
<dependencies>
<dependency>
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>sample.ProjectE</groupId> <!-- Exclude Project-E from Project-B -->
<artifactId>Project-E</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
+---+
** Why exclusions are made on a per-dependency basis, rather than at the POM level
This is mainly done to be sure the dependency graph is predictable, and to
keep inheritance effects from excluding a dependency that should not be
excluded. If you get to the method of last resort and have to put in an
exclusion, you should be absolutely certain which of your dependencies is
bringing in that unwanted transitive dependency.