How to avoid non fixed versions in any (transitive) dependency

527 views Asked by At

I am wondering if there is a maven enforcer rule or something similar to check my project for any 'opened' (not fixed) version in project (transitive) dependencies.

I would like to archive a stable reproducible build with maven, but I cannot guarantee this if a dependency of mine e.g. declares an open-ended version range for one of its dependencies.

A new release of that transitive dependencies would change the output of my 'otherwise' untouched build.

I haven't found any property or enforcer rule which fits this requirement.

Does anybody know how such a requirement can be done with maven?

3

There are 3 answers

3
J Fabian Meier On

Best bet would be to take the mvn dependency:list and fix all those versions in <dependencyManagement>

0
Andrey B. Panfilov On

Short story

it is (still) "possible" to determine whether there are transitive dependencies with open version range:

...
<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-assertions-generator</artifactId>
    <version>2.1.0</version>
</dependency>
...

...
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <!-- version makes sense -->
    <version>2.6</version>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
</plugin>
...

% mvn -X assembly:single -Dassembly.dryRun=true| grep 'setting version to'
[DEBUG]     org.assertj:assertj-core:jar:2.9.1:compile 
            (setting version to: 2.9.1 from range: [2.1.0,2.99.0])

Long story

maven 3 had adopted Aether project and, unfortunately, there is no option to intercept or influence on dependency resolution process, basically "project object model" provides information about direct dependencies, but exhaustive information about transitive dependencies is hidden behind aether, that is the reason why you didn't find desired functionality among maven plugins.

I succeeded to get some relevant information from maven-assembly-plugin just because it's old versions are still compatible with modern maven, so, technically it is still possible to implement a plugin with required functionality or even take advantage of gmavenplus-plugin and write groovy scriptlet:

<plugin>
    <groupId>org.codehaus.gmavenplus</groupId>
    <artifactId>gmavenplus-plugin</artifactId>
    <version>1.13.1</version>
    <executions>
        <execution>
            <phase>initialize</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
                <bindAllProjectProperties>true</bindAllProjectProperties>
                <scripts>
                    <script><![CDATA[
def resolver = session.container.lookup(org.apache.maven.artifact.resolver.ArtifactResolver.class)
def artifacts = resolver.resolveTransitively(
    project.dependencyArtifacts,
    project.artifact,
    project.managedVersionMap,
    session.getLocalRepository(),
    project.remoteArtifactRepositories,
    null
).artifacts.findAll {
    it.versionRange && it.versionRange.restrictions
    && !it.versionRange.recommendedVersion
    && (it.versionRange.restrictions.size() > 1
        || it.versionRange.restrictions[0].lowerBound
        || it.versionRange.restrictions[0].upperBound
    )
}.each {
    log.error("Found bad guy: $it -> $it.versionRange")
}
                    ]]></script>
                </scripts>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>3.0.9</version>
            <type>pom</type>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</plugin>
% mvn initialize
[INFO] Scanning for projects...
...
[INFO] Using plugin classloader, includes GMavenPlus and project classpath.
[INFO] Using Groovy 3.0.9 to perform execute.
Found bad guy: org.assertj:assertj-core:jar:2.9.1:compile -> [2.1.0,2.99.0]

UPD.

The idea of locking versions of all transitive dependencies in dependencyManagement from my perspective seems to be wrong. At first glance it looks attractive to run mvn dependency:list and put all it's output into dependencyManagement, no doubts, at next mvn package we will get the same artifact, but we also need to think about consequences of such "solution":

  1. output of mvn dependency:list is far from ideal: maven tries to do it's best, but it relies on numbers mentioned in version tag and knowns nothing about compatibility, bugs and security issues - we should not blindly trust that output, instead we always need to check everything manually, and the problem is maven answers the question What will we get? when the actual question is Why did we get that?.
  2. By locking versions of transitive dependencies in dependencyManagement we are taking responsibility for the things which we do not actually manage, the question is: how we are going to update versions of dependencies if we have locked versions of their dependencies? Why do we think we know better what to do than the developer of dependency?
1
sbernard On

No transitive dependency way

To have a reproducible build, you should probably fix version of all your direct and indirect dependencies in dependencyManagement.

This will allow :

To not forget flatten all your dependencies in dependencyManagement you can use banTransitiveDependencies rules from maven-enforcer-plugin.

If you have lot of dependency this could be painful to manage but maybe you can create a script to generate dependencyManagement section from mvn dependency:list

I created a new feature request for maven-dependency-plugin about this : https://issues.apache.org/jira/browse/MDEP-811

(See also : https://stackoverflow.com/a/35849405/5088764)

Fix range version only ?

Solution above works, but ideally we want to only fix version for :

  • convergence issue.
  • range version dependency.

You can add rules for dependency-convergence but AFAIK there is no kind of noRangeVersion rules.

I created a new feature request for maven-enforcer-plugin about this : https://issues.apache.org/jira/browse/MENFORCER-427

But waiting maybe this is possible to create your own rule : https://maven.apache.org/enforcer/enforcer-api/writing-a-custom-rule.html (or maybe somebody already do that ?)

dependency-lock-maven-plugin ?

I didn't test this but maybe dependency-lock-maven-plugin could help to solve this issue.

See : https://stackoverflow.com/a/54580971/5088764