3.4-RC-1/3.3: Resolving configurations may be disallowed and throw IllegalStateException


(Stefan Neuhaus) #1

I would like to bring up the question if the following is to be considered a breaking change and should be mentioned so in the release notes:

Gradle 3.3 introduced the concept of whether a Configuration can be resolved or not: Configuration.isCanBeResolved()

Apparently the refactoring of the Java plugin in 3.4-rc-1 actually introduced a couple of configurations that are not resolvable: ["apiElements", "implementation", "runtimeElements", "runtimeOnly", "testImplementation", "testRuntimeOnly"]

While before it seemed to be perfectly fine to simply resolve all configurations, since Gradle 3.4-rc-1 the following code fails (when having the Java plugin applied):

project.getConfigurations().findAll { true }.each { Configuration configuration ->
    configuration.getResolvedConfiguration()
}

Thrown Exception:

java.lang.IllegalStateException: Resolving configuration 'apiElements' directly is not allowed
        at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.assertResolvingAllowed(DefaultConfiguration.java:818)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveToStateOrLater(DefaultConfiguration.java:419)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.getResolvedConfiguration(DefaultConfiguration.java:414)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration_Decorated.getResolvedConfiguration(Unknown Source)
        at org.gradle.api.artifacts.Configuration$getResolvedConfiguration.call(Unknown Source)

(Cédric Champeau) #2

That’s correct. There’s a good reason for having this flag, which is variant aware dependency resolution. Some configurations are not meant to be resolved, because they typically represent a bucket of dependencies. On the other hand, some configurations correspond to a certain usage and can be resolved. Let’s take an example: imagine that you use the new implementation configuration. This configuration cannot be resolved, and there’s a good reason for this: it just says “the implementation of this project depends on project B”. So you just declare a dependency. Now, there might be multiple variants of your project: one for debug, one for release. Or one for i386 and one for amd64. The concrete set of transitive dependencies that you will get depend only on the configuration that you resolve: the debug configuration or the release configuration, which provide information about the usage. Resolving a “bucket” doesn’t mean anything. You can only get wrong results. For that purpose, you must check if a configuration is meant to be resolved or not. If it’s not, then you can be 100% sure that if you’re trying to resolve them, you are doing something wrong.


(Cédric Champeau) #3

And for completeness, it is probably a good idea to mention this behavior in the release notes, even if, as you mentioned, it’s already the case in 3.3. But yes, 3.4 is the first release of Gradle that comes with un-resolvable configurations by default.


(idlsoft) #4

My pre-3.4 code is using JavaPlugin.TEST_RUNTIME_CONFIGURATION_NAME, which is now deprecated, and the suggested replacement is TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME.
However the latter “can’t be used as a project dependency because it isn’t intended for consumption by other components”.
So what’s the actual replacement?
I need one for resolving and another for attaching artifacts.


(Stefan Oehme) #5

There is no consumable equivalent for tests. What is the use case? If the use case is to share test fixtures, I’d model those explicitly and separate them from the tests in their own source set. That way you don’t trigger downstream test re-runs just because an upstream test has changed, but only when an upstream test fixture changes.


(idlsoft) #6

We have some utility classes under src/test/java used by other modules. Not very elegant, but not entirely unusual either.
Currently, the declaring project adds a test.jar artifact to 'testRuntime'. Dependents declare a project dependency with configuration: 'testRuntime'.
The setup works well enough, and I’d prefer not to mess with it unless I absolutely have to.

There is a second scenario, not test related. We assemble a binary distribution of the app, which is using 'runtime' (just pulls all the jars into a dir). That too is deprecated in favor of RUNTIME_ELEMENTS_CONFIGURATION_NAME, which gives “Resolving configuration ‘runtimeElements’ directly is not allowed”. I tried RUNTIME_CLASSPATH_CONFIGURATION_NAME, which worked.
Is the deprecation message incorrect or just incomplete?


(Stefan Oehme) #7

That’s the test fixtures use case I was referring to. It is much better solved by splitting the fixtures and tests. With your current setup, tests in downstream projects are re-run every time you change a test in an upstream project, even though you are really only reusing the fixtures, not the tests themselves. You could of course just create a testElements configuration to keep your current approach working, but I encourage you to go the extra mile and create a separate testFixtures SourceSet.

runtimeElements is for consumption by other projects, runtimeClasspath is for the projects internal consumption, e.g. building a distribution. This separation leaves the door open for custom Configurations that put stuff into the runtime of the current project without exposing it to downstream consumers for instance.


(idlsoft) #8

That makes sense, I think the deprecation comment should mention that, or at least say "use RUNTIME_ELEMENTS_CONFIGURATION_NAME or RUNTIME_CLASSPATH_CONFIGURATION_NAME.

The approach is not clean, that much is obvious, but it’s been there since the maven days. And, to be fair, there is no practical benefit from splitting the sourceSets, those tests don’t change very often, and the CI server builds everything anyway. I just wish testElements was predefined to streamline the 3.4 migration.


(Stefan Oehme) #9

We want users to make a conscious decision here. If you are fine with the performance impact of leaving them together, that’s okay. But generally we want to encourage clear separation of what is shared and what is internal.


(idlsoft) #10

After spending some time with this yesterday I realized that it wasn’t trivial before either.
I still had to do
ArchivePublishArtifact jarArtifact = new ArchivePublishArtifact(testJar); testRuntimeConfiguration.getArtifacts().add(jarArtifact);
Now I’ve added auto-creatiion of testRuntimeElements to the code, no biggie.

You don’t have convince me that this isn’t the best way to do things, but one of my priorities is to avoid as many questions as possible from people who’ve only used maven before.

You may not have to deal with this a lot, but anything that used to work out of the box before and no longer does turns into a long discussion. And, no matter how valid the reason, creates an impression that “this is complicated”, and hurts migrations.


(Stefan Oehme) #11

This is internal API, please use project.artifacts {} instead.


(Stefan Neuhaus) #12

@CedricChampeau I created a PR for updating the release notes: https://github.com/gradle/gradle/pull/1351


(spy99) #13

This is a breaking change for me. My build scripts alter the generated poms to accurately reflect project dependencies. Specifically for war, jar, aar, and apk projects. This change breaks jar/war projects. Why was this change introduced and why can’t i access the list of resolved dependencies?

Is there some kind of lifecycle change which implies that the resolved dependencies are only accessible later in the build and in a phase whereby i can still modify the generated pom?

Edit: for the jar/war projects, it throws when resolving compile and test compile configuration names.