Gradle: Is there a way to disallow version ranges in transitive dependencies?

When I declared the snapshot repository in my Gradle config, is there a way to prevent transient version range dependencies from resolving to SNAPSHOT versions? Alternatively, can I completely disallow version ranges in transitive dependencies? For example, consider this very simple Gradle project:

repositories {
    mavenCentral()
    maven {
        url 'https://oss.sonatype.org/content/repositories/snapshots/'
    }
}

apply plugin: "java"

dependencies {
    compile "org.reactfx:reactfx:1.4"
    compile "org.fxmisc.undo:undofx:1.0.1"
}

      

This will lead to a conflict and version resolution as it undofx

depends on reactfx

the version [1.4,1.5)

(the most recent version is 1.4.x available). Here's a description of the dependencies for reactfx

:

gradlew dependencyInsight --dependency reactfx

      

org.reactfx:reactfx:1.4.1-SNAPSHOT (conflict resolution)

org.reactfx:reactfx:1.4 -> 1.4.1-SNAPSHOT
\--- compile

org.reactfx:reactfx:[1.4,1.5) -> 1.4.1-SNAPSHOT
\--- org.fxmisc.undo:undofx:1.0.1
     \--- compile

      

Maven will also resolve reactfx:1.4.1-SNAPSHOT

as a dependency for undofx

. However, it appears that once added reactfx

to the project, Maven resolves the conflict using the version declared as a first level dependency. Here's the Maven POM I used for testing:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>example</groupId>
    <artifactId>example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>maven-central</id>
            <url>http://repo1.maven.org/maven2</url>
        </repository>
        <repository>
            <id>oss-snapshots</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.fxmisc.undo</groupId>
            <artifactId>undofx</artifactId>
            <version>1.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.reactfx</groupId>
            <artifactId>reactfx</artifactId>
            <version>1.4</version>
        </dependency>
    </dependencies>

</project>

      

This is also the permission type behavior I was expecting from Gradle, but only based on guesswork. I realized that if undofx

any version is allowed 1.4.x

reactfx

and the only other declared version reactfx

falls within that range, the conflict will be resolved using my declared version.

However, I'm less interested in resolving the conflict than on failing a build if any transitive dependencies are using ranked versions. I would rather identify these dependencies and install them for specific versions. I don't think I would have noticed this using a version range if I hadn't created the above conflict.

What's the simplest way to identify and handle transitive dependencies using version ranges?

Update

Building on Peter's answer, here is a complete list of the code I'm using to do this. Note that it uses the Gradle API features marked as @Incubating

.

import org.gradle.api.artifacts.component.ModuleComponentSelector

if(!project.plugins.hasPlugin(JavaPlugin)) {
    apply plugin: "java"
}

repositories {
    mavenCentral()
    maven {
        url 'https://oss.sonatype.org/content/repositories/snapshots/'
    }
}

dependencies {
    compile "org.fxmisc.undo:undofx:1.0.1" // depends on reactfx:[1.4,1.5)
}

configurations {
    //noinspection GroovyAssignabilityCheck
    all {
        /*
        Once the dependencies in all configurations are resolved check the
        version of all module (not project) components that were resolved
        successfully.  Modules already using a forced version will be skipped.
         */
        incoming.afterResolve { // ResolvableDependencies
            it.resolutionResult.allDependencies { // DependencyResult
                if(it instanceof ResolvedDependencyResult
                        && it.requested instanceof ModuleComponentSelector) {
                    if(!it.selected.selectionReason.forced) {
                        checkVersion((ModuleComponentSelector) it.requested)
                    }
                }
            }
        }
    }
}

/**
 * Check the version of the requested module and throw and exception if it's
 * using a version range.
 *
 * @param requested The module component to check.
 */
void checkVersion(ModuleComponentSelector requested) {
    def version = requested.version

    if(version.endsWith(")")
            || version.equals("LATEST")
            || version.equals("RELEASE")) {
        throw new GradleException(
                "${requested} uses a version range.  Try force.")
    }
}

      

+3


source to share


1 answer


You can use the API configuration.getIncoming()

to compare the declared versions to the allowed versions (like in configuration.getIncoming().afterResolve()

) and fail if they don't match. To get a conflict resolution in Maven "declared version wins" (rather than Gradle "highest version wins"), you can use the API configuration.getResolutionStrategy().force()

. To force a conflict of any version (using force()

) use configuration.getResolutionStrategy().failOnVersionConflict()

. See the Gradle Build Language Reference for API details .



+2


source







All Articles