gradle-versions-plugin icon indicating copy to clipboard operation
gradle-versions-plugin copied to clipboard

Checking dependency constraints not working

Open bratkartoffel opened this issue 3 years ago • 5 comments

Although there is an option to enable checking the dependency constrains for updates, it doesn't work.

Example:

plugins {
    id 'java'
    id 'com.github.ben-manes.versions' version "0.39.0"
}
group = 'com.example'
sourceCompatibility = '11'
repositories { mavenCentral() }
dependencyUpdates.checkConstraints = true

dependencies {
    implementation("ch.qos.logback:logback-classic:1.2.3")
    constraints {
        constraints {
            implementation('org.slf4j:slf4j-api') { version { strictly '1.7.24' } }
        }
    }
}

Running the dependencyUpdates task results in the following output:

The following dependencies are using the latest milestone version:
 - com.github.ben-manes.versions:com.github.ben-manes.versions.gradle.plugin:0.39.0
 - org.slf4j:slf4j-api:1.7.24

The following dependencies have later milestone versions:
 - ch.qos.logback:logback-classic [1.2.3 -> 1.3.0-alpha5]
     http://logback.qos.ch

Checking on maven central shows, that the newest version of slf4j-api is in fact currently 2.0.0-alpha1.

Looking at the Resolver.groovy, around Line 154:

    copy.dependencies.clear()
    copy.dependencies.addAll(latest)
    copy.dependencies.addAll(inherited)

This part cleans up the dependencies from the copied configuration prior running the resolver and doing the actual version checks.

Clearing the dependencyConstraints at that point makes it work like expected for me:

    copy.dependencies.clear()
    copy.dependencies.addAll(latest)
    copy.dependencies.addAll(inherited)
    copy.dependencyConstraints.clear()

I think the issue here might be the strictly constraint, as it effectively disables any further resolution. As we need it to be strictly in our project to downgrade some transitive dependencies, we may not change it to another value, like require or prefer.

Could you please consider removing all constraints prior resolving the dependencies?

Thanks, Simon

bratkartoffel avatar Jun 17 '21 11:06 bratkartoffel

@anuraaga contributed this feature and has some unit tests for it. I'd be happy to accept a PR with your changes and an additional test case. He'd be the best to review, as I haven't used constraints in my own projects.

ben-manes avatar Jun 17 '21 15:06 ben-manes

Can you please explain how dependency constraints differ from a componentSelection / resolutionStrategy? From afar they appear similar and I don't understand why both exist.

As we perform a copy, we honor the resolutionStrategy as less surprising (also, I don't think the api allows us to remove it). That is sometimes desired and sometimes not. The workaround we suggest, e.g. in #361, is to conditionalize adding the resolutionStrategy by checking if the dependencyUpdates task is going to be run. If so, then the rules like forcing versions can be skipped in the configuration. Perhaps there is a better way to do this, but no one has complained with that answer so far.

My naive impression is that constraints are similar and maybe we should recommend the same solution. I don't know if clearing it would be any more or less surprising, and a resolutionStrategy sets a precedence of honoring whatever the configuration is set as. If Gradle allowed for more manipulations, then we could have a callback to modify our copy to remove these rules to allow you to clear the constraints without us making that decision explicitly.

What do you guys think is the right approach here, @bratkartoffel, @anuraaga?

ben-manes avatar Jul 05 '21 04:07 ben-manes

I agree that resolutionStrategy and strict dependencies seem similar so the current default of respecting the build could be least surprising. A code callback instead to customize the configuration could fallback well.

anuraaga avatar Jul 05 '21 12:07 anuraaga

Sure, an option or callback to configure the behavior would be fine too. As this is beyond my groovy / gradle capabilities I cannot help with a PR in that case.

bratkartoffel avatar Jul 05 '21 15:07 bratkartoffel

For the time being the below works without plugin changes. It is not pretty, but neither is the resolutionStrategy fix.

dependencyUpdates.checkConstraints = true

def strictVersion(DependencyConstraint constraint, String version) {
  gradle.taskGraph.whenReady { taskGraph ->
    constraint.version {
      if (taskGraph.hasTask(dependencyUpdates)) {
        require version
      } else {
        strictly version
      }
    }
  }
}

dependencies {
  implementation("ch.qos.logback:logback-classic:1.2.3")
  constraints {
    constraints {
      implementation('org.slf4j:slf4j-api') {
        strictVersion(it, '1.7.24')
      }
    }
  }
}

For the plugin to make this all less hairy, I think a callback with the Configuration would be the most powerful. That means DependencyUpdates.groovy would allow one to set an Action<? super Configuration> and we'd dispatch to that in Resolver on our copy before resolving. Then a user could do any fix ups they think make sense, like clearing the dependencyConstraints. This was done for resolutionStrategy so you could use that as an example to follow.

ben-manes avatar Jul 05 '21 18:07 ben-manes