Task Configuration Avoidance and Connecting Task Outputs and Inputs

I am learning to use task configuration avoidance and am having difficulty connecting the outputs of a task to the inputs and avoiding the use of the get() method. I have tried several different methods including using a model that uses properties. Entities of the model are associated using the map() function of a Provider. However, I have not had much luck with this approach. Is there guidance on how to connect the properties of tasks?

I understand that this has also been asked in Accessing Task Output while Using Task Configuration Avoidance, but I am hoping that perhaps a response is matter of timing.

Thanks,
Brian

1 Like

It’s not timing. This place really is the ghost town it appears to be :slightly_smiling_face:

hi @bkeyser

The Property class accepts other Property(s) when being set. This can be shown here

I have a plugin that uses the new lazy api heavily and am happy to help but i don’t understand the problem you are experiencing. Could you clarify?

In short if you have two task types, one that has an output property and one that has an input property, and an instance of each, you can set one to the other. The Groovy syntax just looks different from Kotlin since the groovy syntax has operator overloading for the assignment(=) operator which i believe calls the set method on the property.

Pseudo Groovy Script:

// input is a Property type with a get/set api
taskConsumer.input =
taskProducer.output //(output is a Property type, with a get/set api)

Pseudo KotlinScript:

taskConsumer.input.set(taskProducer.output)

Psuedo class examples

class Producer extends DefaultTask {
  @Output
  val output: Property<String> = project.objects.property(String::class)

  @TaskAction
  fun execute() { output.set("foo") }
}

class Consumer extends DefaultTask {
   @Input
  val input: Property<String> = project.objects.property(String::class)

  @TaskAction
  fun execute() { println(input.get()) }
}

Glad to help more if you can provide what you are trying to do.

Hi Justin,

Thanks for getting back to me! I have used lazy configuration independently of the task configuration avoidance functionality and have found the “autowiring” of task dependencies to be a nice feature. I was hoping there might be an idiom for using them together.

When registering a task it provides a TaskProvider. However, in order to use the underlying task within the provider you would need to call get(). Gradle will then configure the task and as most tasks will be configured similarly one will loose the advantages within the task configuration avoidance functionality.

In order to solve this problem, I created a companion model that used similar properties to those needed by a task such as RegularFileProperty, DirectoryProperty, and Property. As I couldn’t anticipate the settings of the properties within the companion model I needed a way to dynamically configure the companion model. This was accomplished through the service-provider loading facility of the JDK (java.util.ServiceLoader). A provider would configure the properties of the task using the extension model and the providers for other tasks in order to set its values. For those considering this approach, there is one trick that is required as Gradle uses a custom class loader and this prevents ServiceLoader from finding the providers. The relevant code is (where ProjectProperties is the enclosing class)

    private static <T> List<T> providerDelegate(Class<T> providerClass) {
        ClassLoader gradleClassLoader = Thread.currentThread().getContextClassLoader()
        try {
            Thread.currentThread().setContextClassLoader(ProjectProperties.class.getClassLoader())
            ServiceLoader<T> providerLoaders = ServiceLoader.load(providerClass)

             providerLoaders.iterator().toList()
         } finally {
             Thread.currentThread().setContextClassLoader(gradleClassLoader)
         }
    }

I realize that this description may be a little difficult to understand and if there is interest I would be willing to spend some time to create an example of how this allowed me to use the task avoidance configuration functionality. I couldn’t figure out how to use the lazy configuration and the task “autowiring” so I used the “traditional” method of dependsOn to specify task dependencies.

Brian

Hi @bkeyser,

TaskContainer.register(java.lang.String) lazily creates the task when it is required. This returns the TaskProvider like you said.

You can use a TaskProvider instance just like you would if you had created the task eagerly to define dependencies.

Per docs:

Methods like Task.dependsOn(java.lang.Object…​) and ConfigurableFileCollection.builtBy(java.lang.Object…) work with TaskProvider in the same way as Task, so you do not need to unwrap a Provider for explicit dependencies to continue to work.

This means you can set depensOn with task providers and no eager configuration will occur.

def check = tasks.register("check")
def verificationTask = tasks.register("verificationTask") {
    // Configure verificationTask
}
// lazy configuration
check.configure {
    dependsOn verificationTask
}

Also, all tasks should be registered not created which allows for the older style idiom

def someTask = tasks.register("someTask")

task anEagerTask {
    dependsOn someTask
}

Is this what you wanted, lazy task dependency graph?

Hi Justin,

Yes and no :wink: … The examples you provided show how to specify dependencies between tasks, but I was unsure if there was an idiomatic method for “linking” the output of one task to the input of another task. The reply to your first post suggests a method for accomplishing that and adheres to the principles of task avoidance. While I don’t know for certain, I have a feeling that the Gradle team is working on providing a Gradle-specific way of linking the outputs of a task to the inputs of another …

Brian