Consider the following scenario.
I have a Java 1.8 project that depends on a java 8 artifact called foo-bar and a java 5 artifact called baz-qux. baz-qux also depends on foo-bar. But it uses the special slimmed down java 5 build called foo-bar-java5. Thus by including baz-qux into my project, I transitively bring in foo-bar-java5. In the end I have an undesirable state where I now have foo-bar and foo-bar-java5. Apparently there's no way to do a global exclude. So I can't just exclude foo-bar-java5. Instead, I must clutter up my cluttered pom and exclude it everywhere it will be pulled in transitively.
With that said, is there any way I can specify that foo-bar provides foo-bar-java5? Or is the only option to truly exclude foo-bar-java5 everywhere?
Here is an answer from another question that may work for you:
https://stackoverflow.com/a/9623517/2879838
Basically, explicitly list the foo-bar-java5 as a dependency in your project and list it as provided. This will tell maven not to put that jar into the archive during the build. This should be a lot less messier than excluding it everywhere.
Related
I have a parent POM and a normal Maven project.
Both define BOMs in their dependencyManagement. In some cases, these BOMs may overlap, e.g. both specify a version for log4j.
From my tests it seems that:
For overlapping BOMs in the parent POM, the first one wins, i.e. supplies the version for the artifact.
If a BOM from the child and from the parent overlap, then the version from the BOM in the child wins.
Unfortunately, I did not find any documentation about this.
Am I right and can I rely on this behaviour?
Logging framework traditionally lives "next to" your actual code, so it is a bit unclear how to handle this.
I found that separating the build phase dependencies from the deployment phase dependencies works for me.
The basic idea is that you write the code only requiring a dependency to the API of the logging framework (slf4j used to be a natural choice), and then you have several deployment Maven configurations (one for JBoss, one for WebSphere, one for running in your IDE etc) where you add the dependencies relevant to that deployment.
So my suggestion is to change your codebase accordingly to only have API dependencies for the logging framework, and then create a new Maven project for each actual deployment type. You might also want to bake in deployment specific configuration files if needed.
I've recently been introduced to the concept of a dependency version lock file when reading about package managers like NPM, Yarn, Paket, Cargo, etc. My understanding is that it is a file that lists all direct and transitive dependencies along with their exact version number so subsequent builds are guaranteed to use an equivalent set of dependencies. This seems to be a desirable feature since many package managers have or are adopting the concept.
My questions are then:
Why doesn't Maven or Gradle use a lock file? Or if they do, why haven't I seen it?
What are the pros and cons of allowing version ranges in a package manager's dependency resolution strategy vs only allowing exact versions?
Maven does not have a way of to achieve what you are asking for. Even if you set specific versions for your direct dependencies, which you should, your transitive dependencies can easily be unintentionally changed due to a seemingly unrelated change. For example, adding a dependency on a new library can give you an older version of an existing transitive dependency.
What you need is to have a dependencyManagement section that lists all your direct and transitive dependencies. You will still not be able to detect if a transitive dependency is removed or added which is a feature that, for example, NPM provides. The problem with missing that is that all your dependencies are no longer in the dependencyManagement section. To detect those changes you could use something like dependency-lock-maven-plugin which I have written. Using it will also make it less important to have everything in a dependencyManagement section since changes in transitive dependencies will be detected.
I would also recommend having https://maven.apache.org/enforcer/enforcer-rules/requireUpperBoundDeps.html in your build since Maven chooses the versions of the transitive dependencies that are closes in the tree and not, as you would expect, the highest version.
I have seen many runtime problems caused by developers accidentally changing transitive dependencies.
TL;DR: You do need something like a lock file in Maven, but it is not there due to historical ideological reasons.
I would not recommend using version ranges since they make your build not reproducible.
Neither does it behave as you would believe when it comes to transitive dependencies.
Dependency locking was a feature that achieved some maturity by Gradle 5.0:
https://docs.gradle.org/current/userguide/dependency_locking.html
Gradle's implementation was inspired by the Nebula plugin: https://github.com/nebula-plugins/gradle-dependency-lock-plugin
Version ranges do work well, when used as input to whatever updates your locking mechanism. So, for Gradle, you can actually just target specific dependencies that will look to resolve version ranges you've specified for:
gradle classes --update-locks org.apache.commons:commons-lang3,org.slf4j:slf4j-api
Or, you can just say "go update all my deps":
gradle dependencies --write-locks
Specifying resolution strategies is also worth reviewing, if you're looking into automation: https://docs.gradle.org/current/userguide/dependency_resolution.html
Both Maven, SBT and Gradle have what you're describing. It's called "using released (or fixed) versions". A released version looks like 1.2.3, as compared to a version range [1.2.3,), or a snapshot (1.2.3-SNAPSHOT).
If all your dependencies are using released versions, you will achieve what you're describing.
Version ranges are a valid form of versions as well, depending on your use case, but I would normally advise against them, unless they're used for parent POM-s, or just during active development. Version ranges can come handy when you'd like to not have to keep updating the fixed version of a third-party, or parent POM, if you're certain that the respective artifact can in no way break things for you (and, trust me, this does happen a lot with version ranges). Fixed versions should be used when you'd like to guarantee that the code will build and work against what you originally devised and tested it.
There is no need to have a feature such as "lock file", or anything like this, if your pom.xml strictly defines the versions of your dependencies.
If you read the documentation regarding dependency management, you will see that this is indeed so:
Maven
Gradle
SBT
The module declaration defines, among other things, a module's dependencies. If I use Maven as a build tool, this is redundant because the pom.xml already contains these (and more) information. Based on that, couldn't Maven generate the module-info.java for me?
One might expect that most of the dependencies are indeed required modules as well. However, requirements can also point to modules of the JDK/JRE, which are not specified in the pom.xml. So yes, if you only look at the dependencies, probably most of them could be transformed to a required module reference.
But a module-descriptor contains much more information, which are all based on decisions to be made by the developer.
I've written an article about it which describes in detail why it is not possible to fully generate this file.
As far as I know, bnd-maven-plugin can generate module-info.class based on the configured dependencies. If you are working with maven-bundle-plugin, you need to specify the version of bndlib manually, for the latest version of maven-bundle-plugin(5.1.3) is still using the 5.x version of bndlib, and bndlib requires 6.x to support jpms.
Document: https://bnd.bndtools.org/releases/6.1.0/chapters/330-jpms.html
We have a case in our dependency tree where we have two version of the same jar coming in from different sources, the version we want to use is lower in the tree, so maven is picking the other version using the nearest wins method.
To resolve this, We could either exclude this incorrect version from its source dependency, or directly add that version as another dependency in our pom.
I preferred the latter since it is quicker. But I am not sure if that is the best way of doing it, or are their any drawbacks of adding a dependency. So, is adding an exclusion better than what I did? In what cases and how?
I would suggest to explicitly define your dependency in order to make it clear which version you are expecting through transitive dependencies.
Although you would normally declare only dependencies directly referenced by your code (i.e. you use import statements in Java to include classes from these dependencies), it is also recommended to have control over your dependencies resolution via explicit declaration. Explicit declaration will have priority over Maven dependencies mediation.
Exclusions make your build harder to maintain and potentially you would need to replicate the same exclusion over and over if, for instance, the same dependency would be required transitively via other dependencies and every time with a different version (not the one you want). Moreover, exclusions can have undesired effects as it may take precedence when it is not expected.
Declaring it as part of your dependencies makes it clear, centralized and easier to update/maintain in the future.
Update: An even better approach is to use the dependenciesManagement section, which also takes priority over Maven dependencies mediation, as stated in the official documentation, here
dependency management takes precedence over dependency mediation for transitive dependencies
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?