Conditionally / Lazily configure extensions

Similar to configuring a task only if it is part of the execution graph

tasks.withType(Foo).configureEach {
   // executes only if Foo is going to execute
}

how can one configure an extension only if the task/plugin that needs the extension is in task graph

someExtension {
  expensiveConfig = "<data loaded from file, for example>"
}

Ideally I would want to configure this extension in afterEvaluate block so that any dependent variables are already set by then. But any other Lazy technique would suffice.

Hello,

I think that you should take a look at the Lazy Configuration API provided by Gradle.

Assuming a plugin registering an extension defined by under the name my:

import javax.inject.Inject

import org.gradle.api.Project
import org.gradle.api.provider.Property

class MyExtension {

    Property<String> expensiveString
    
    @Inject
    MyExtension(Project project) {
        expensiveString = project.objects.property(String)
    }
}

You can defer actual computation with:

plugins {
    id 'my-plugin'
}

my {
    expensiveString = project.provider {
        println 'computing expensiveString...'
        return 'result'
    }
}

tasks.register('consume') {
    doLast {
        println 'starting consume task'
        println my.expensiveString.get()
    }
}

Running gradle consume will print the following lines in that order:

  1. starting consume task
  2. computing expensiveString…
  3. result

Here is an example involving a file:

plugins {
    id 'my-plugin'
}

my {
    expensiveString = project.layout.projectDirectory.file(project.provider {
        println 'providing file to load...'
        'big.xml'
    }).map { f ->
        println "loading ${f.asFile.name} file..."
        return 'expensive content'
    }
}

tasks.register('consume') {
    doLast {
        println 'starting consume task'
        println my.expensiveString.get()
    }
}

Thank you so much for the elaborate response @Pierre1.

This would totally make sense if I could change the extension, however the Extension I am trying to update belongs another plugin (PluginBundleExtension from com.gradle.plugin-publish, to be specific) and as of now it does not use Property/Providers :neutral_face:

For example, I want to configure the extension only if I have publishPlugins in the taskGraph. I was actually thinking of leveraging taskGraph.whenReady for this but was unsure if that is the right way.

So the updated problem statement is “How to lazily configure an extension which I cannot modify”.

Sorry I missed your point.
I am afraid that I can only think of poor quality workarounds at this point.

whenReady combined with hasTask seems a good workaround at first glance, but the hasTask(Task) requires to configure the task even if it is not part of the graph, and the hasTask(String) use the path to the task, and not its name, so it may hit some edge cases with multi projects builds or included builds (untested):

plugins {
    id 'my-plugin'
}
println 'begin configuration'

tasks.register('consume') {
    println 'registering consume task'

    doLast {
        println 'starting consume task'
        println my.someString
    }
}

gradle.taskGraph.whenReady { g ->
    println 'task graph is ready'

    if (g.hasTask(':consume')) {
        println 'task consume is present in graph'
        my.someString = 'expensive'
    }
}
println 'end configuration'

The TaskExecutionListener is another option, but it move some configuration into the task execution lifecyle which is a bad practice.

gradle.taskGraph.addTaskExecutionListener(new TaskExecutionListener() {
    
    void beforeExecute(Task t) {
        if (t.name == 'consume') {
            println 'computing someString'
            my.someString = 'expensive'
        }
    }
    
    void afterExecute(Task t, TaskState s) {
    }
})