Cyclic dependencies


(detelinyordanov) #1

Hi everyone,

We have identified an issue with cyclic dependencies that would prevent a project to use an older version of itself to do some task on itself. For example: 1. Lets say we have a module ‘com.google.code.findbugs:findbugs:2.0.1’ 2. We develop a Gradle Findbugs plugin which would resolve the latest released findbugs module (e.g. in a custom ‘findbugs’ configuration) and execute the findbugs checks on the current project 3. If we try to execute the above plugin on the ‘com.google.code.findbugs:findbugs:2.0.2-SNAPSHOT’ project we would get something like:

> Could not resolve all dependencies for configuration ':findbugs:findbugs'.
   > Module version group:com.google.code.findbugs, module:findbugs, version:2.0.1, configuration:default declares a dependency on configuration 'default' which is not declared in the module descriptor for group:com.google.code.findbugs, module:findbugs, version:2.0.2-SNAPSHOT

This is when using Gradle 1.3 and an Ivy repository to upload to.

The problem seems to be that Gradle always adds the current module as a root dependency node, to which all dependencies are attached and resolved. The workaround we are currently using is to resolve the findbugs (or whatever plugin libraries the plugin has) in a separate project (e.g. in the root project, which usually does not produce any artifacts), then reference this configuration’s files in each project’s custom configuration. E.g.:

rootProject {
    configurations.add('someConf')
    dependencies {
        someConf 'com.google.code.findbugs:findbugs:2.0.1'
    }
}

Then for each subproject:

dependencies {
    findbugs project.files(project.rootProject.configurations.someConf.files)
}

The above could probably be configured at execution time to avoid resolving the libraries during project evaluation.

If anyones knows a better workaround we could use, please let me know so that we can change our code. Otherwise, this could probably be of help to others that hit this issue.

Thanks,

Detelin


(Peter Walker) #2

Would you be able to share the problematic build script?


(detelinyordanov) #3

For example:

apply plugin: 'base'
group = 'org.foo'
  if (project.hasProperty('release')) {
    status
= 'release'
    version = '1.0.0'
}
else {
    status
= 'integration'
    version = '1.0.1-SNAPSHOT'
}
  repositories.ivy {
    name
'local'
    url
  new File(gradle.gradleUserHomeDir, 'local')
}
  uploadArchives.repositories.add(repositories.local)
artifacts.add('archives', tasks.add('dummyJar', Jar))
configurations.getByName('default').extendsFrom(configurations.archives)
  task resolveReleasedVersion << {
    description = "Attempts to resolve the previously released 1.0.0 version of the current module"
          Configuration someConf = buildscript.configurations.add('someConf')
    buildscript.dependencies.add(someConf.name, [group: project.group, name: project.name, version: '1.0.0'])
    println someConf.files
}

To reproduce run: 1. gradle clean uploadArchives -Prelease 2. gradle resolveReleasedVersion

It’s probably a good idea to run with separate Gradle user home as it creates a local repository there.

Thanks,

Detelin


#4

Thanks for the report. The problem you’re experiencing is due to the fact that the ‘project module’ is included in the set of candidate dependencies, and when it comes to version conflict resolution this one is chosen in preference to the older one referenced in your dependency declaration:

12:27:11.763 [DEBUG] [org.gradle.api.internal.artifacts.ivyservice.resolveengine.DependencyGraphBuilder] Selected org.foo#publish-resolve;1.0.1-SNAPSHOT from conflicting modules [org.foo#publish-resolve;1.0.1-SNAPSHOT, org.foo#publish-resolve;1.0.0].

I can’t think of a better workaround than yours: by resolving the dependency in a project different from the one being resolved, you avoid the issue.


(detelinyordanov) #5

Well, that’s what we are doing right now. Not in the plugin, but in project’s build script instead.