Alternatives to mutating state (configuration) during task execution

I have a break in my build after migration to gradle 9, i.e. the current setup worked ok in gradle 8.14.3

The setup is

  • a configurable object is exposed to the build, it has various Propertyfields
  • it is used as an @Input to multiple tasks (which are marked as @Nested)
  • one property is @Optionaland the typical value is a value that is fetched from an external service
  • since it’s some work that requires external service calls, this is done via a task so we have a task that takes a @Nested as input and tries to mutate it

as mentioned, in gradle 8.14.3, this is fine but in gradle 9 it breaks because it appears that all inputs are finalised ahead of task execution

The alternatives I can think of are:

  1. serialise that object to a file and then read that back in every other task that needs it (or perhaps create a different object to write to which is only referenced by tasks that I know run later)

  2. shift this “task” to project.gradle.taskGraph.whenReady which does still allow the value to be mutated

Is there any other alternative way to mutate shared state (configuration) at task execution time?

I’d say your option one are the two options to use.
If the task result is to be reused across multiple build runs, write it to a file.
If it is only for the current build execution, use a shared build service as data store.

OK thanks, hadn’t thought of a build service for this.

Unfortunately it seems that neither option works in this specific because of another related problem

namely one such value is a version string so Provider<String> gets transformed to group:artifact:version which gets added to a configuration as a dependency. This configuration is used as an @InputFiles to another task and it seems that this configuration is also resolved early, before the taskGraph is even ready which means there’s no apparent way to obtain such a value beyond the configuration phase.

Any suggested workarounds to this other than having to do the lookup during configuration?

Maybe not really a clean solution, but what you observed is just partly correct.
If you enable configuration cache, then yes, dependency resolution and Provider evaluation is done at the end of the configuration phase and the result stored to the CC entry so that the work does not need to be done when the entry is reused.

But there is one exception, if the file collection or provider has a task dependency, then that means that the value depends on the task being executed first, so those are evaluated at execution time instead after the dependency task was executed.

So you can trick the mechanism by having a provider that is derived from a task provider. That task does not need to do anything, it is just important that the task dependency is there to delay the evaluation to the execution phase.

Whether there is maybe a cleaner solution I’m not sure without analyzing the situation in more detail.

1 Like

thanks for the idea, it’s unclear how to make that chain though

i.e. the input to a task is a configuration so how to make a configuration depend on a task provider such that gradle is aware of that dependency?

The input surely is not a configuration as that would not be CC compatible. It most probably is a configurable file collection.

But anyway, you can add a provider-derived dependency to a configuration using addLater.

Yes to the task it’s a ConfigurableFileCollection

I see what you mean now, I was using addLater anyway (had been surprised that was a separate API to add) with the provider being property based. This approach is still not configuration cache friendly though?

Why do you think it is not?

because of this earlier comment

Did you read the sentence right after that one? o_O

Yes but it’s unclear what goes into the cache in that case and what that means for subsequent behaviour, a serialised closure? Nothing?

I think it should be the serialized lambda iirc, yes