7

Our team has multiple projects; most projects are just libraries. Let's assume for simplicity that the libraries don't depend on each other and there is a single project that uses them, e.g.:

Project Main:
    Project Lib-A:
        X (3rd-party library)
    Project Lib-B:
        X (3rd-party library)

To avoid surprises in 'Main', we'd like to make sure that all our own projects are using the same versions of the 3rd-party libraries, so that e.g. both 'Lib-A' and 'Lib-B' are built and tested with the same version of library X.

To achieve this we use a parent pom with <dependencyManagement> section detailing the versions of all relevant 3rd-party libraries and also their transitive dependencies. This parent pom is inherited by all the projects, i.e. 'Main', 'Lib-A', and 'Lib-B' from the above example. Then each child pom would only use <dependency> without specifying any version. We also have maven enforcer plugin's dependencyConvergence rule to make sure we have not missed any library conflict in any of our projects.

The problem: increasing the version of X: a developer of 'Lib-A' increases a version of X from 1.0 to 2.0. So he changes X's version in the parent pom, increases the version of parent, releases the parent pom, and notifies guys from 'Main' that they should now use a new parent. The situation becomes like this:

Main - inherits from Parent:2.0 and depends on:
    Lib-A:2.0 - inherits from Parent 2.0 and depends on X:2.0
    Lib-B:1.0 - inherits from Parent 1.0 and depends on X:1.0
    X:2.0 (taken from Parent:2.0 <dependencyManagement> section)

Everything, including 'Main', builds fine, 'maven enforcer plugin' does not detect any conflict because the version of X is clearly specified in the Parent:2.0 from which 'Main' inherits. So we release 'Main'.

Ooops.... Lib-B has never been built with X:2.0. It has great unit tests that would uncover the problem, but we never tried this. We forgot to update Lib-B, try it with X:2.0 and release it. Still 'Main' has been built without problems and maven enforcer plugin has never complained.

Question: we need maven to detect that there are dependencies that inherit from the same artifact but different major versions and fail the build.
In our case the build had to fail since 'Main' and 'Lib-A' inherit from Parent:2.0, but 'Lib-B' inherits from Parent:1.0.

My solution so far (a hack): in addition to inheriting, add an explicit dependency on the parent pom to all out projects (i.e. 'Main', 'Lib-A', and 'Lib-B'):

<dependency>
    <artifactId>Parent</artifactId>
    <type>pom</type>
    <version>${project.parent.version}</version>
</dependency>

Then use <bannedDependencies> rule of maven enforcer plugin to ban other major Parent versions (we could also use its <dependencyConvergence/> rule if we want to fail even on minor Parent version conflicts).

Is there a less hacky and cumbersome way to fail on conflicting major versions of parent pom?
May be our entire approach to managing maven dependencies is wrong, what is the recommended way then?


Update:
Tried writing my own rule for maven-enforcer-plugin as suggested by @JF Mayer and described here, before giving up. Reasons:

  1. First, the parent pom information is not available from the dependencies, at least not from the nodes built by maven's DependencyGraphBuilder
  2. OK, I've added my parent poms as explicit dependencies to the children and tried to use this DependencyGraphBuilder to detect dependencies on the parent with different major versions. No way! As could be seen with mvn dependency:tree that also uses this class, DependencyGraphBuilder does not provide all the dependencies so it can't be used to detect dependencies conflicts. That's why the <dependencyConvergence> maven enforcer rule is using a super-deprecated DefaultDependencyTreeBuilder that has been even deleted from the GitHub and everywhere else - not a good choice for a trouble-free custom solution.
Alexander
  • 2,761
  • 1
  • 28
  • 33
  • Did you think about building all your libraries together in one multi-module project? – J Fabian Meier May 14 '20 at 12:24
  • No, this is not an option at all. First, the code base is quite large. Second, in fact there are more than one 'Main' project. I've just tried to simplify things for the question. – Alexander May 14 '20 at 15:45
  • 1
    Then I would suggest that you write your own enforcer rule for the Maven enforcer plugin. – J Fabian Meier May 14 '20 at 15:50
  • Very interesting, thanks! We'll probably go with this. It's odd though that our problem seems to be so common and yet we might end up with writing a custom maven rule. – Alexander May 14 '20 at 15:57
  • What about my suggestion? – J Fabian Meier May 17 '20 at 14:48
  • I've tried this and could not make. See the update. – Alexander May 18 '20 at 09:02
  • 1
    "First, the parent pom information is not available" - why is it a problem? just add it into meta on build phase, when it is available. it should be trivial if you describe default plugin configurations in your root project (jar plugin). then custom enforcer plugin rule will work. – ursa May 18 '20 at 18:43
  • I didn't know about an option to add meta information during the build and then use it in maven API. Thanks, I'll try it. – Alexander May 19 '20 at 09:38
  • This is not a Maven only solution, but would it be an option to add code to your Libs reporting their dependencies (at runtime) and add code to your Main checking that the dependencies of you Libs agree. Then add a unit test that checks this. – Christian Fries May 23 '20 at 23:17
  • @ChristianFries I believe this is an overkill. It looks like a very custom solution to a very standard problem. In our case there are many libraries we have not touched for years. Adding a runtime behavior to them seems overcomplicated and also not very clean. Extending the libraries' code (i.e. business logic) with some maven stuff does not look right for me. – Alexander May 24 '20 at 07:42

2 Answers2

1

For completeness, my own poor-man's solution:

  1. Add an explicit dependency of type pom to the parent to every project so that maven-enforcer-plugin's <dependencyConvergence> rule would detect conflicting parent versions. No big deal with this one as we only add this section once and forget about it:

    <dependency>
        <groupId>${project.parent.groupId}</groupId>
        <artifactId>${project.parent.artifactId}</artifactId>
        <version>${project.parent.version}</version>
        <type>pom</type>
    </dependency>
    
  2. <dependencyConvergence> will fail a build on 'Main' even on 'increment' differences in the parent versions, e.g. 1.0.1 and 1.0.2. In this case, the developer of 'Main' can decide that it's OK to build it despite the parent version conflict, because it's insignificant (this was my original question).
    So he builds 'Main' with some special profile that excludes the dependency on the parent:
    mvn -P I-know-what-I-am-doing deploy.

I am not very happy with this solution because of the step 2 that requires the developers of 'Main' to build it with a special profile in case of a parent versions conflict. I'd prefer a solution that always fails on major parent version conflicts but ignores insignificant differences in the parent pom versions automatically, I just don't know how to achieve this.

Alexander
  • 2,761
  • 1
  • 28
  • 33
0

I think what you are looking for is Reactor Module Convergence enforcer:

https://maven.apache.org/enforcer/enforcer-rules/reactorModuleConvergence.html

Its not the same as the dependency convergence rule.

Adam Gent
  • 47,843
  • 23
  • 153
  • 203