Recommended way to setup tasks based on "user" input

As a plugin author I asked myself what is the recommended way to setup a task if the task based on some “user” input.

Imagine the following Plugin:

open class AExtension {
    var from: String = ""
}

class APlugin : Plugin<Project> {

  override fun apply(project: Project) {
    val extension = project.extensions.create("aPluginEx", AExtension::class.java)

    project.tasks.create("invalidZipSub", Zip::class.java) {
        archiveName = "invalidSub.zip"
        from(extension.from)
    }
  }
}

The task can’t be executed (or leads to a build failure) because the extension wasn’t “read” yet by Gradle. Means the from String inside the AExtension is empty.

Task :invalidZipSub FAILED
FAILURE: Build failed with an exception.
* What went wrong:
path may not be null or empty string. path=‘’

There are two ways to fix this. Either by wrapping the tasks creation into the afterEvaluation-listener or let the user access the task directly in the build script and set the property:

// Solution 1 - Inside the Plugin:
project.afterEvaluate {
  project.tasks.create("validZipSubWithAfterEvaluate", Zip::class.java) {
  archiveName = "validZipWithAfterEvaluate.zip"
  from(extension.from)
}

// Solution 2 - Inside the Plugin:
project.tasks.create("validZipSubWithSetupInScript", Zip::class.java) {
  archiveName = "validSubWithSetupInScript.zip"
}

// Solution 2 - Inside a build.gradle.kts:
tasks.getByName("validZipSubWithSetupInScript") {
    val zipTask = this as Zip
    zipTask.from("sub")
}

// Solution 2 - Inside a build.gradle:
validZipSubWithSetupInScript {
  from("sub")
}

Solution 1 looks a little bit strange IMO. I don’t know why but I don’t like this for whatever reasons.
Solution 2 in the Kotlin DSL looks bad. Especially because you have to cast the task to the “correct” task. Which is not obvious for “users” which applies the plugin.
Solution 2 in the Groovy DSL looks the best.

I don’t like Solution 2 as well because the “user” has to setup multiple tasks. Imagine a plugin which provides 3 tasks (which needed to setup) and a extension. The build script for only one plugin would look horrible.

Anyway. What is Gradle way to solve that?
Or is the freedom of the plugin author to decided how to define a “API”.
From my point of view having a single extension where you can setup everything in one place is the nicest way. But then we have to create the tasks (or the setup of the task) into the afterEvalulate-listener which looks wrong too…

1 Like