Un-nesting Provider<Provider<>>

I’m trying to write a new Task that consumes the result of an ArchiveTask.

This was my first pass:

open class ValidateZipDistribution @Inject constructor(objects: ObjectFactory) : DefaultTask() {
    @InputFile
    val zipFile : RegularFileProperty = objects.fileProperty()

    fun fromTask(taskProvider: Provider<Zip>) {
        zipFile.set(taskProvider.map { it.archiveFile })
    }

but because Zip.archiveFile is a Provider itself, this results in Provider<Provider<RegularFile>, and the set method of my property doesn’t like that as an input.

Clearly I need to unwrap one of those Providers, but the docs on lazy configuration are full of warning against evaluating anything too early, recommend always using map, etc.

Do I

  • taskProvider.get().archiveFile
  • taskProvider.map { it.archiveFile.get() }
  • taskProvider.map { it.archiveFile }.get()
  • add a forUseAtConfigurationTime somewhere in here?

There’s mention of Providers carrying some additional meta-data about Task dependencies, information that’s outside the provided value itself. That’s a big part of what’s inducing anxiety about Doing It Wrong here. It seems to be completely invisible to Provider’s public interface. How am I, as a Task author, supposed to know if that dependency information is intact?

You can use flatMap to avoid having nested providers.

zipFile.set(taskProvider.flatMap { it.archiveFile })

Oh, that does explain why flatMap is named as it is, thank you!

I did not realize that from its docstring. Looking at it now with a microscope I see that map is

a new Provider whose value is the value of this provider transformed using the given function.

and flatMap is

a new Provider from the value of this provider transformed using the given function.

but the distinction between “value is the value of” and “from the value of” was way too subtle for me to pick up on.

It also says

Any task details associated with this provider are ignored. The new provider will use whatever task details are associated with the return value of the function.

I think that’s appropriate in this case? I remain concerned about how to ensure the integrity of this invisible information.

Gradle does attach task information to certain properties, and yours is probably the “by the book” example (one task’s output file is another task’s input file), so it should be fine. You should probably test that you in fact get an automatic task dependency from your task to the archive task.

It’s a nice concept, but personally I wouldn’t rely on this behavior because
a) it seems a bit like black magic and is not at all obvious,
b) Gradle doesn’t let you attach the build task information to a Property or Provider yourself, and
c) this can make your code brittle because as soon as you introduce another link in the chain (say, a Property in some custom DSL extension) it might break.

So, since you already have a reference to the task, my recommendation would be to make the task dependency explicit in your fromTask method by calling dependsOn(taskProvider).

Yeah, those concerns make sense to me. It’s just that some of the documentation on Properties (e.g. Working with Task Inputs and Outputs) makes it sound like the lack of explicit dependsOn is a feature.

It doesn’t go as far as saying “don’t use dependsOn,” but when we’re introduced to things like this in Gradle there’s this question of “is this the new best practice?”

but I guess some of this stuff is still Incubating and dependsOn isn’t going to be deprecated any time soon.

Is there someplace else we should be giving feedback on incubating features like this, or is this forum the place to be?