Gradle 2.5: Changed behaviour of Dependency Resolve Rules when replacing a project dependency with a dependency on the same module version

Upon the upgrade to Gradle 2.5 some of our plugins have stopped working when getResolutionResult is getting called.

Here are two examples that are causing the issue

project.configurations.each { conf ->
  conf.incoming.resolutionResult.allDependencies { dependencyResult ->
      println dependencyResult
  }
}

Example 2

    project.configurations.all { Configuration configuration ->
      configuration.incoming.afterResolve {
          configuration.resolvedConfiguration.resolvedArtifacts.each { a ->
            def moduleGroup = a.moduleVersion.id.group
            def moduleName = a.moduleVersion.id.name
            println "$moduleGroup:$moduleName"
          }
          project.logger.debug("$project finished validating artifacts")
      }
   }

There is a dependency listed like

dependencies {
    testCompile(project(":project$scalaSuffix"))
}

This call generates

Caused by: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find com.example:project_2.10:0.2.1.
Searched in the following locations:
    http://artifactory.com:8081/artifactory/release/com/example/photon/project_2.10/0.2.1/project_2.10-0.2.1.ivy
Required by:
    com.example:project_2.10:0.2.1
        at org.gradle.internal.resolve.result.DefaultBuildableComponentResolveResult.notFound(DefaultBuildableComponentResolveResult.java:38)
        at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.resolveModule(RepositoryChainComponentMetaDataResolver.java:88)
        at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.resolve(RepositoryChainComponentMetaDataResolver.java:59)
        at org.gradle.api.internal.artifacts.ivyservice.clientmodule.ClientModuleResolver.resolve(ClientModuleResolver.java:44)
        at org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyResolver.resolve(ProjectDependencyResolver.java:72)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder$ModuleVersionResolveState.resolve(DependencyGraphBuilder.java:575)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder$ModuleVersionResolveState.getMetaData(DependencyGraphBuilder.java:585)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder$DependencyEdge.calculateTargetConfigurations(DependencyGraphBuilder.java:270)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder$DependencyEdge.attachToTargetConfigurations(DependencyGraphBuilder.java:244)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder.traverseGraph(DependencyGraphBuilder.java:156)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder.resolveDependencyGraph(DependencyGraphBuilder.java:94)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder.resolve(DependencyGraphBuilder.java:84)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultDependencyResolver$1.execute(DefaultDependencyResolver.java:142)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultDependencyResolver$1.execute(DefaultDependencyResolver.java:96)
        at org.gradle.internal.Transformers$4.transform(Transformers.java:137)
        at org.gradle.api.internal.artifacts.ivyservice.DefaultIvyContextManager.withIvy(DefaultIvyContextManager.java:61)
        at org.gradle.api.internal.artifacts.ivyservice.DefaultIvyContextManager.withIvy(DefaultIvyContextManager.java:39)
        at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultDependencyResolver.resolve(DefaultDependencyResolver.java:96)
        at org.gradle.api.internal.artifacts.ivyservice.CacheLockingArtifactDependencyResolver$1.run(CacheLockingArtifactDependencyResolver.java:42)
        at org.gradle.internal.Factories$1.create(Factories.java:22)
        at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:192)
        at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:175)
        at org.gradle.cache.internal.DefaultPersistentDirectoryStore.useCache(DefaultPersistentDirectoryStore.java:106)
        at org.gradle.cache.internal.DefaultCacheFactory$ReferenceTrackingCache.useCache(DefaultCacheFactory.java:187)
        at org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager.useCache(DefaultCacheLockingManager.java:64)
        at org.gradle.api.internal.artifacts.ivyservice.CacheLockingArtifactDependencyResolver.resolve(CacheLockingArtifactDependencyResolver.java:40)
        at org.gradle.api.internal.artifacts.ivyservice.SelfResolvingDependencyResolver.resolve(SelfResolvingDependencyResolver.java:41)
        at org.gradle.api.internal.artifacts.ivyservice.ShortcircuitEmptyConfigsArtifactDependencyResolver.resolve(ShortcircuitEmptyConfigsArtifactDependencyResolver.java:56)
        at org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingArtifactDependencyResolver.resolve(ErrorHandlingArtifactDependencyResolver.java:46)
        at org.gradle.api.internal.artifacts.ivyservice.DefaultConfigurationResolver.resolve(DefaultConfigurationResolver.java:45)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveGraphIfRequired(DefaultConfiguration.java:363)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveNow(DefaultConfiguration.java:338)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$900(DefaultConfiguration.java:55)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationResolvableDependencies.getResolutionResult(DefaultConfiguration.java:781)

I changed the stack trace slightly to have non internal URL’s / projects. I can only reproduce this with scala based project. Can you tell me what would have changed between 2.4 and 2.5 that would cause the exception to be thrown?

Upon some more investigation it looks like the project dependency is being substituted with an Ivy dependency, but I can’t find anything to that effect in the debug logs.

I was able to recreate this on a very simple project.

https://github.com/ethankhall/gradle-substitution-error

If you change the wrapper to use 2.4 it will work as expected:

> ./gradlew :projectA:dependencies     
testCompile - Compile classpath for source set 'test'.
\--- project :projectB
     \--- commons-io:commons-io:2.4

In 2.5 it will error out saying:

> ./gradlew :projectA:dependencies     
testCompile - Compile classpath for source set 'test'.
\--- project :projectB -> com.example:projectB:1.2.3 FAILED

Thanks for the analysis. This does indeed appear to be a regression in Dependency Resolve Rules: in previous versions of Gradle you could call DependencyResolveDetails.useTarget() on a project dependency and Gradle would ignore the change, simply using the value from the original project dependency. In Gradle 2.5 we added the ability to substitute project and external dependencies, which means that we now try to honour the result of applying the rule.

In your example you have added a rule that modifies all dependencies, including project dependencies. Since details.requested is always a ModuleVersionSelector, the rule is attempting to replace a project dependency with an external module dependency.

I think we should address this by once again ignore the effect of a Dependency Resolve Rule on a Project dependency.

Digging a bit deeper, it seems like this only applies when you have a rule that attempts to replace a project dependency with exactly the same dependency coordinates, using DependencyResolveDetails.useTarget().

In your example you are doing this with details.useTarget(details.requested):

configurations.all { Configuration conf ->
  conf.resolutionStrategy { ResolutionStrategy rs ->
    rs.eachDependency { DependencyResolveDetails details ->
      details.useTarget(details.requested)
    }
  }
}

So I think we just need to add back a check so that if you specify the exact project coordinates in details.useTarget(), we retain the existing project dependency.

1 Like

Is this something that I will have to wait for 2.6 and start working on a workaround? If so could I create a ProjectComponentSelector and call useTarget on that instead of having to wait for 2.6

Yes, you’ll need to wait for Gradle 2.6 for a fix. The behaviour is only different when you try to replace a project dependency with a “group:name:version” that exactly matches the declared group/name/version values for the project. In this case, Gradle <= 2.4 will keep the project dependency. Gradle 2.5 will replace it with an external dependency for “group:name:version”. (If the coordinates don’t match, Gradle will always replace the project dependency with module dependency.

In some respects, the new behaviour is better, because we assume that whenever you call ‘useTarget’ on a dependency, you are specifying a new external dependency to use.

The workaround would be to avoid calling ‘useTarget’ or ‘useVersion’ for a project dependency, unless you want to replace the project dependency with an external dependency.

Okay. Thanks! Now to find all the (many) places we do this.

Hi ,

I am trying to upgrade from Gradle 1.12 to 2.7. However I find myself stuck in almost the same problem.

The used code is similar to the example that is provided by @daz :

//At this point there are many different IF statements, but the problem persist only with the xmlgraphics

The result of the build --stacktrace:

    Cause 7: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find batik:batik-ext:1.7.
search in the following locations: ...
Required by:
    com.lhs:func_frwmwk_clt:cbio-3.0.1.x-SNAPSHOT > org.apache.xmlgraphics:fop:0.93
        at org.gradle.internal.resolve.result.DefaultBuildableComponentResolveResult.notFound(DefaultBuildableComponentResolveResult.java:38)
        at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.resolveModule(RepositoryChainCompon
tMetaDataResolver.java:88)
        at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.resolve(RepositoryChainComponentMet
ataResolver.java:59)

The Build executes successfully with Gradle 2.2 but no further version success the build.

Is there any help or hints?

Thanks.