NPE in DependencyGraphBuilder$DependencyEdge.getFailure

(Justin Ryan) #1

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?

 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DependencyGraphBuilder$DependencyEdge.getFailure(
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder.resolvedConfiguration(
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DependencyGraphBuilder.assembleResult(
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DependencyGraphBuilder.resolve(
 at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultDependencyResolver.resolve(
 at org.gradle.api.internal.artifacts.ivyservice.CacheLockingArtifactDependencyResolver$1.create(
 at org.gradle.api.internal.artifacts.ivyservice.CacheLockingArtifactDependencyResolver$1.create(
 at org.gradle.cache.internal.DefaultCacheAccess.useCache(
 at org.gradle.cache.internal.DefaultPersistentDirectoryStore.useCache(
 at org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager.useCache(
 at org.gradle.api.internal.artifacts.ivyservice.CacheLockingArtifactDependencyResolver.resolve(
 at org.gradle.api.internal.artifacts.ivyservice.SelfResolvingDependencyResolver.resolve(
 at org.gradle.api.internal.artifacts.ivyservice.ShortcircuitEmptyConfigsArtifactDependencyResolver.resolve(
 at org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingArtifactDependencyResolver.resolve(
 at org.gradle.api.internal.artifacts.ivyservice.DefaultConfigurationResolver.resolve(
 at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveNow(
 at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$900(
 at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationResolvableDependencies.getResolutionResult(
 at org.gradle.api.tasks.diagnostics.internal.dependencies.AsciiDependencyReportRenderer.render(
 at org.gradle.api.tasks.diagnostics.DependencyReportTask.generate(
 at org.gradle.api.tasks.diagnostics.AbstractReportTask.generate(
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(
 at java.lang.reflect.Method.invoke(
 at org.codehaus.groovy.reflection.CachedMethod.invoke(
 at groovy.lang.MetaMethod.doMethodInvoke(
 at groovy.lang.MetaClassImpl.invokeMethod(
 at groovy.lang.MetaClassImpl.invokeMethod(

(Luke Daley) #2

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.

(Justin Ryan) #3

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.

(Adam Murdoch) #4

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

(Justin Ryan) #5

‘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.

(Adam Murdoch) #6

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

(Justin Ryan) #7

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.

(Justin Ryan) #8

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”.

(Adam Murdoch) #9

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

(Justin Ryan) #10

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 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.

(Adam Murdoch) #11

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.

(Justin Ryan) #12

BTW, the fix you put in works.