Situation: Assign default values for task paramet...
# plugin-development
f
Situation: Assign default values for task parameters I am looking for advice on which approach to take. Obviously the task definition should either require the parameter be provided or there should be reasonable default values hard coded. There are cases where the dsl introduced by the plugin is the proper level at which to set the default values. My question is what is the best way to propagate the defaults set for the plugin into the tasks? If the generation of the task it incorporated into the plugin dsl the approach is pretty clear. However, if the tasks associated with the plugin are created/registered outside of the dsl how best to propagate the default values? • Give the plugin a public property which the task may use. • Give the task a class property (shared by all instances) which is set by the plugin. • Something else.
t
Copy code
project.getTasks().withType(MyTask.class).configureEach(task -> {
  task.getProperty().convention(extension.getProperty())
})
f
Thanks, I presume that goes in the
apply
method of the plugin?
v
It could, but actually it is questionable whether you should. The usually recommended approach to maximize potential reusability is to have tasks and extensions as opinionless as possible. And then a plugin might register tasks and extensions and add opinion like default values and / or wiring extension properties to task properties, but not to all of that type, but only to the ones where the plugin actually has an opinion about.
If you want that opinion reusable, so that a consumer of the plugin and task can decide to employ the same opinion onto tasks he creates, the plugin could for example provide a method to add the opinion to a given task instance
t
Well, "it depends". It is expected that all
Pmd
task inherit their defaults from the project-level
pmd
extension, and default to use the project-level java toolchain (I was looking at this just this weekend as I was coding a replacement plugin for de.thetaphi.forbiddenapis (before realizing that I should rather code that DefaultLocale check for Error Prone)). IMO, if a plugin provides task types (in its JAR) and registers a project-level extension to configure defaults for those tasks, then those defaults should apply to all tasks, whether registered by the plugin itself or manually.
v
Well, it differs what "is expected" and where defaults "should apply to". What I learned long time ago from the Gradle folks and docs is what I said. Extensions and tasks should be as opinonless as possible to maximize potential reuse and the plugin adds some extension and task instances and employs opinon on them. I don't say the whole Gradle codebase is following these principles. Yes, all built-in code quality plugins follow that pattern to wire the extension properties to the task properties. And of course someone could also do that in some 3rd party plugin the same. It just reduces the easy reuse potential slightly. šŸ™‚
But this is of course a highly subjective topic and might also be perceived differently from case to case.
ā˜ļø 1
f
So, each of the two approaches are reasonable: Here they are combined (Kotlin) • Equip the Plugin with an opinion which the task can opt to use
Copy code
class ExamplePlugin : Plugin<Project> {

    companion object {
        private lateinit var extension: ExampleExtension

        fun getExtension(): ExampleExtension {
            if (this::extension.isInitialized) return extension
            return extension
        }
    }
• Provide a default (convention) to all of the tasks of a certain type
Copy code
override fun apply(project: Project): Unit = project.run {
    extension = project.extensions.create<ExampleExtension>("example-dsl")

    tasks {
        withType<ExampleTask>().configureEach {
            // repeat the following for each of the properties
            this.firstProperty.convention(extension.firstPropertyDefault)
        }
    }
}
There are some details that need to be worked out but...
I wonder if combining them in this way would be a good or bad idea.
The opinion (first) approach would then have something like the following in the `@TaskAction of the ExampleTask.
Copy code
val ext = ExamplePlugin.getExtension()
v
That's not at all a combination of the two approaches, that is solely the approach that the plugin employs the opinion on all task of that type unasked.
Also this
getExtension
does not make too much sense. If the task is used without the plugin, the extension will not be set. And if the plugin is applied, then the opinion is already employed. The task should definitely not pull the opinion from the extension. A task should usually never be aware of any extensions.
šŸ‘† 2
Especially not at execution time, or you do not get proper up-to-date and caching behavior
f
Then I guess I do not understand the approach you presented. Could you provide a code fragment?
t
Your first snippet (companion object) would likely fail in multi-project builds. Your second snippet is the way to go (if that's what you want to do, that Vampire objects to šŸ˜‰)
v
Copy code
class ExamplePlugin ... {
    fun apply(...) {
        val foo by tasks.registering(ExampleTask::class) {
            employOpinion(this)
        }
    }
    companion object {
        fun employOpinon(task: ExampleTask) {
            task.propertyA = extension.propertyA
            task.propertyB = extension.propertyB
        }
    }
}
then if someone creates a task of that type and wants to inherit the opinion, he can just do the same
Copy code
val bar by tasks.registering(ExampleTask::class) {
    ExamplePlugin.employOpinion(this)
}
and if he does not want the opinion inherited he can just use the plain opinionless task.
f
Ah, I see. You want to keep the application of defaults away from the task definition. Assigning defaults (convention) in the plugin apply or task register (or creation) is fine.
v
Away from the task definition in any case. My point was to not apply it conditionless to all tasks, but just to tasks actually registered by the plugin and readily available for others registering tasks if that type if they want to employ the same opinion
šŸ™Œ 1
p
IMHO it really depends on the task and its use-case, some tasks need plugins/extensions, and some can be reused without any plugins/extensions. For the latter I configure the conventions in the task.
v
Sure, you can do that, it's just not the idiomatic recommended way. :-)