Cannot resolve recursive copy of configuration with cyclic dependencies within beforeResolve

A user of our plugin has reported a problem when their build contains projects with cyclic dependencies. I’ve reduced this down to a short example that removes the plugin from the picture.

When a build contains circular dependencies between two of its projects, a recursive copy of a configuration that itself resolves successfully will fail to resolve if the copy is made in beforeResolve or afterResolve. This is illustrated in the following build.gradle:

subprojects {
	apply plugin: 'java'
}

project (':alpha') {
	dependencies {
		testCompile project(':bravo')
	}

	configurations.testRuntime.incoming.beforeResolve {
		configurations.testRuntime.copyRecursive().resolve()
	}
}

project (':bravo') {
	dependencies {
		compile project(':alpha')
	}
}

It will fail when running alpha:dependencies:

Execution failed for task ':alpha:dependencies'.
> Could not resolve all dependencies for configuration ':alpha:testRuntimeCopy'.
   > Module version gh-46:bravo:unspecified, configuration 'default' declares a dependency on configuration 'default' which is not declared in the module descriptor for gh-46:alpha:unspecified

As mentioned above, the failure only occurs when the copy is made during resolution, i.e. within beforeResolve or afterResolve. The copy will resolve successfully if it is made either before or after the original configuration is resolved:

configurations.testRuntime.copyRecursive().resolve()
configurations.testRuntime.resolve()
configurations.testRuntime.copyRecursve().resolve()

The problem is basically that you’re performing a separate dependency resolution that attempts to resolve against project ‘:alpha’ while the configurations for ‘:alpha’ are in a partially-resolved state.

While the exception is not very helpful, a likely fix would be for us to catch this and fail in a more descriptive manner.
Is there a reason that you need to do resolution in this way?

Is there a reason that you need to do resolution in this way?

I believe so, although I would happily use an alternative approach should one exist.

Part of the plugin’s functionality is to make exclusions behave as they do in Maven. This is achieved by resolving a copy of a configuration, examining the pom files of the resolved dependencies, configuring the appropriate exclusions on the original configuration, and then resolving the configuration. I took this approach as there didn’t appear to be any way to hook into the resolution process such that exclusions could be applied on the fly.

OK I’ve raised a Jira issue for this. Unfortunately I don’t see an easy fix.