User customizable conflict resolution failure


(Mor) #1

Hi,

I’m Using gradle 3.5.
I want to be able to have a “customization” on top of ResolutionStrategy’s failOnVersionConflict() - i’ll explain:
We have internal packages versioned x.y.z where a change in x is not backward compatible.
I want to do a check and fail if any of my transitive dependencies are using 2 versions of the same artifact in which the value of x is different.
In example:
group:artifact:1.0.0
group:artifact:2.0.0 --> this should fail the build

Of course the test needs to take transitive dependencies into account.
I looked at resolutionStrategy and did not find anything which will allow me to do that.
I also tried doing something like this:

dependencies {
    compile 'aaa:bbb:0.0.0'
    compile 'aaa:bbb:1.0.0'

    project.afterEvaluate {
        def a = configurations.compile.resolvedConfiguration.getFirstLevelModuleDependencies().each { dep ->
            printDeps(dep,0)
        }
    }
}

def printDeps(def dep, def level) {
    println ">"*level + "module=" + dep.moduleName + "-" + dep.moduleVersion
    dep.children.each { dep2 ->
        printDeps(dep2, level+1)
    }
}

But it only shows the resolved version - 1.0.0 - so I’m looking for the “raw” transitive dependencies.
How can I do that kind of check?

Any help would be appreiciated


(Mark Vieira) #2

You could use a dependency resolution rule to error if someone is asking for a version other than what you expect.

configurations {
    compile.resolutionStrategy.eachDependency { details ->
        if (details.requested.group == 'group' && details.requested.name == 'artifact' && details.requested.version != 'x.y.z') {
            throw new RuntimeException("Someone is asking for a different version of my module than expected")
        }
    }
}

This would of course just notify you. If you want to simply avoid this situation altogether you could always just force the version you expect.


(Mor) #3

Hi,

First - thanks for the reply, but I think it’s not what I’m looking for:

  1. The code you posted does not run for me- I had to replace “eachDependency” with “dependencies.each” for it to run
  2. This seems to be going over direct dependencies - not transitive ones
  3. I have tens of artifacts and they are updated rapidly. For the dependency list I maintain - I wish to maintain it once and not in multiple places (as in not mention specific versions in more than one place).
  4. Even if I could maintain the list once (pretty sure I can) and even if it was transitive - I still do not understand how that solves my issue - let’s imagine the following scenario:
    I’m working on module A. A’s compile depends on artifacts B and C.
    B depends on D [x.y.z] and C depends D [k.l.m] where k!=x. I as the developer of A - do not want to know I need D, let alone specify it’s specific version - because then it beats the whole purpose of transitive dependencies.

Like I mentioned - I’m looking to access the raw transitive dependencies before (or while) the resolution strategy takes place - something similar to the information available to task of type DependencyReportTask - it shows all version conflicts and how they were resolved (i.e lower version -> higher version).

I hope it’s clear and thanks again.


(Mark Vieira) #4

Sorry, I had a typo above. It should be configurations.compile.resolutionStrategy.eachDependency.


(Mor) #5

Hi,

I have tried using this but it either does nothing or I’m using it wrong.
I need to be able to compare 2 versions of the same artifact in the raw transitive dependencies before they are resolved. I DON’T want to restrict a specific version.

Thanks,


(Mark Vieira) #6

You can’t get this before resolution unfortunately. The report you referenced above is produced by resolving the dependency graph. You can get at that same information via the API though. If you want to know where all the conflicts are you can simply say “for all my resolved dependencies, where is the thing requested not the thing actually selected”. This basically tells you anywhere conflict resolution kicks in.

The source of this info is going to be a ResolvedDependencyResult. You can get at this by doing something like:

configurations.all {
    incoming.afterResolve { resolveableDependencies ->
        resolveableDependencies.resolutionResult.dependencies.each { // compare requested to selected }
    }
}

(Mor) #7

Hi,

Thanks - the last code you provided helped me get what I wanted.
p.s it needs to be allDependencies and not just dependencies (your original code did not compile)

Here is my code (working in gradle 3.5):

configurations.all {
    incoming.afterResolve { resolveableDependencies ->
        resolveableDependencies.resolutionResult.allDependencies.findAll { dep ->
            (dep.requested instanceof ModuleComponentSelector ||
             dep.requested instanceof ResolvedComponentResult) &&
            dep.requested.group.equalsIgnoreCase("our_group_name")
        }.each { /*DefaultResolvedDependencyResult*/ dep ->
            def requestedMajor
            def selectedMajor

            if (dep.requested instanceof ResolvedComponentResult) {
                requestedMajor = dep.requested.moduleVersion.version.tokenize('.')[0]
            } else { //ModuleComponentSelector
                requestedMajor = dep.requested.version.tokenize('.')[0]
            }

            if (dep.selected instanceof ResolvedComponentResult) {
                selectedMajor = dep.selected.moduleVersion.version.tokenize('.')[0]
            } else { //ModuleComponentSelector
                selectedMajor = dep.selected.version.tokenize('.')[0]
            }

            if (requestedMajor != selectedMajor)
                throw new Exception("requested=${dep.requested} and selected=${dep.selected} have different major versions")
        }
    }
}

Is there a simpler way (especially regarding ResolvedComponentResult vs ModuleComponentSelector)?
Thanks again,
Mor