NPE in DependencyGraphBuilder$DependencyEdge.getFailure

I have a scenario which resembles a dependency cycle that causes Gradle to fail when resolving the configuration. Given two modules A and B, which look sorta like this (psuedo Gradle):

name = 'A'
dependencies {
    compile 'B'
    compile ['C','D','E','F', etc]
}
name = 'B'
dependencies {
    testCompile 'A'
}

Technically I don’t consider that cycle, but when resolving B, it will revisit itself in a different config. When resolving B, I can see it visiting the InternalDependencyResult that which representes A(compile)->B(default) in ResolutionResultBuilder.resolvedConfiguration. InternalDependencyResult.selector.module.selected.resolver is null, the selected is for an older B.

I only see this because I’m in the debugger, the stacktrace is below when it goes on to fail. I have no idea is resolver is allowed to be null or not, but getFailure() seems like it assumes that resolver can never be null. I’m guessing the DefaultModuleRevisionResolveState is in a bad state.

I know that cycles are bad, but does Gradle not even support traversing a graph with a cycle? Are there reports of cycle breaking resolution?

java.lang.NullPointerException
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DependencyGraphBuilder$DependencyEdge.getFailure(DependencyGraphBuilder.java:415)
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder.resolvedConfiguration(ResolutionResultBuilder.java:59)
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DependencyGraphBuilder.assembleResult(DependencyGraphBuilder.java:167)
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DependencyGraphBuilder.resolve(DependencyGraphBuilder.java:72)
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultDependencyResolver.resolve(DefaultDependencyResolver.java:78)
 at org.gradle.api.internal.artifacts.ivyservice.CacheLockingArtifactDependencyResolver$1.create(CacheLockingArtifactDependencyResolver.java:39)
 at org.gradle.api.internal.artifacts.ivyservice.CacheLockingArtifactDependencyResolver$1.create(CacheLockingArtifactDependencyResolver.java:37)
 at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:124)
 at org.gradle.cache.internal.DefaultPersistentDirectoryStore.useCache(DefaultPersistentDirectoryStore.java:130)
 at org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager.useCache(DefaultCacheLockingManager.java:53)
 at org.gradle.api.internal.artifacts.ivyservice.CacheLockingArtifactDependencyResolver.resolve(CacheLockingArtifactDependencyResolver.java:37)
 at org.gradle.api.internal.artifacts.ivyservice.SelfResolvingDependencyResolver.resolve(SelfResolvingDependencyResolver.java:41)
 at org.gradle.api.internal.artifacts.ivyservice.ShortcircuitEmptyConfigsArtifactDependencyResolver.resolve(ShortcircuitEmptyConfigsArtifactDependencyResolver.java:46)
 at org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingArtifactDependencyResolver.resolve(ErrorHandlingArtifactDependencyResolver.java:39)
 at org.gradle.api.internal.artifacts.ivyservice.DefaultConfigurationResolver.resolve(DefaultConfigurationResolver.java:42)
 at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveNow(DefaultConfiguration.java:240)
 at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$900(DefaultConfiguration.java:45)
 at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationResolvableDependencies.getResolutionResult(DefaultConfiguration.java:564)
 at org.gradle.api.tasks.diagnostics.internal.dependencies.AsciiDependencyReportRenderer.render(AsciiDependencyReportRenderer.java:84)
 at org.gradle.api.tasks.diagnostics.DependencyReportTask.generate(DependencyReportTask.java:60)
 at org.gradle.api.tasks.diagnostics.AbstractReportTask.generate(AbstractReportTask.java:67)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
 at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
 at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1047)
 at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:877)

I’m almost certain you’re hitting GRADLE-2752. It’s a relatively recent regression, but we haven’t locked it down to exactly when it happened yet.

It’s a candidate for being fixed in 1.7 at this stage.

The stack is slightly different, but it definitely sounds similar enough. Thanks for pointing it out. I guess I’ll have to go back to 1.4.

Are ‘a’ and ‘b’ projects in the same build? Modules in a repository? Both?

‘A’ and ‘B’ are separate projects. In this example, I’m building ‘B’ and referencing a previous published ‘A’ sitting in an Ivy repository. ‘A’ then references an older ‘B’ which is in the same Ivy repository.

So you’d like the jar from ‘A’ plus the production classes from the current ‘B’ in the resulting test compile classpath?

No, I’d expect ‘A’ in the repository and the B currently being built. I see your point that in one definition that is what the graph is saying, i.e. “I have dependency on old B, use it”. But I’m expect conflict resolution to pick the B being built over the older one found in the resolve.

I was in the debugger and saw one of my resolvers in the list of resolves twice, both pointing to Artifactory. One had a name which I never set as the name though. I also had one other resolver, for local filesystem repository, bringing the total to 3. I’ll have to track down where the duplicate came from, but I wonder if that’s a vector into this bug, multiple resolvers that can lookup “B”.

That’s exactly what I meant. Apologies for the confusion.

I find that DependencyGraphBuilder$ConfigurationNode.getArtifacts gets NPE in 1.4, and DependencyEdge.getFailure gets NPE in 1.5. Meaning, that I can’t go back to 1.4 either. In my case I need the descriptor.withXml feature introduced in 1.4.

It also appears that DependencyGraphBuilder.traverseGraph calls the conflictResolver.select for the current module and comes up with the version being built instead of the older version of “B”. Which is why the current module is being found during the resolve.

There’s an initial fix for GRADLE-2752 in master now. You can try it out using the latest nightly build.

I haven’t marked the issue as fixed yet, as there’s still some more work required to finish it up. However, it should deal with the case you’ve described here.

BTW, the fix you put in works.