gradle / kotlin-dsl-samples

Samples builds using the Gradle Kotlin DSL
https://gradle.org/kotlin/
Other
3.71k stars 434 forks source link

Best practice to throws exception when creating a custom gradle task #1411

Closed mochadwi closed 4 years ago

mochadwi commented 4 years ago

Below code, I'm trying to eagerly creates custom gradle task:

val taskB by tasks.registering {
    val moduleName: String? by project

    if (moduleName == null) throw org.gradle.api.tasks.StopExecutionException(
        "Please add parameter -PmoduleName=lorem when running this command")
   else {

       val taskA = when (moduleName) {
            "domain", "data" -> ":${moduleName}:test"
            else -> ":features:${moduleName}:test"
       }

       dependsOn(taskA)
    }
}

Expected Behavior

The throws/exception only occurs when specifically runs the task, e.g:

  1. ./gradlew taskB (without parameter throw error)
  2. ./gradlew taskB -PmoduleName=lorem (will runs taskA)

Current Behavior

Unfortunately the above code will fails when trying to Sync project (the taskB is triggered)

Context

Does the above code is a bad implementation to throws error/creating a custom task? please give me references/best practices to creating custom task (without creating a custom file task, e.g: class TaskB : DefaultTask) if possible

I'm using a multi-module gradle (partially groovy & kotlin).

Steps to Reproduce (for bugs)

  1. Sync project
  2. Rebuild
  3. Running any unit/android test

The above 3 steps will always throws error, but I never trying to run the taskB intentionally.

I'm worried about my custom task implementation, it shouldn't triggered/runs the taskB when sync project or the above steps, right?

Am I missing something or is this intended behaviours that should be implement using other approach?

Your Environment

StefMa commented 4 years ago

I guess this is because the synchronisation will (in the background) run the tasks task to populate the tasks view in the Gradle pannel in the IDE. And if you can the tasks task it will of course evaluate the tasks because it has to know "something" about it. Since the throw call part of the task configuration "section" it will throw...

So if you goal is to throw only if the task really run, than I guess you need something like this:

val taskB by tasks.registering {
    doLast {
        val moduleName: String? by project
        if (moduleName == null) throw StopExecutionException("Please add parameter -Px=true when running this command")
    }
}

Please note that the StopExecutionException will not "stop" the build to run in this case. As the documentation say:

Note that throwing this exception does not fail the execution of the task or the build.

If you really want to fail or stop the build you have throw another exception.

mochadwi commented 4 years ago

noted, will using InvalidDataUserException.

Please note that the StopExecutionException will not "stop" the build to run in this case. As the documentation say:

I see will wrapping it with doLast first, and update the result here

mochadwi commented 4 years ago

Unfortunately, this still doesn't works & throws this error instead:

Cannot call Task.dependsOn(Object...) on task ':taskB' after task has started execution.

@StefMa

StefMa commented 4 years ago

The dependsOn should be still outside of the doLast block 🙃

mochadwi commented 4 years ago

I see. But in this case. Lemme clarify my custom task

I need moduleName property to run task e.g: :moduleName:test.

When moving it dependsOn outside the doLast & if moduleName == null, it will execute the :[blank]:test instead @StefMa

StefMa commented 4 years ago

Sry, but I don't get exactly what do you mean. But this is what I ment:

val taskA = tasks.register("taskA") {
    doLast{ println("DoLast TaskA") }
}

val taskB by tasks.registering {
    doLast {
       val moduleName: String? by project

        if (moduleName == null) throw IllegalArgumentException("Stop")

        println("DoLast TaskB")
    }
    dependsOn(taskA)
}

I'm not sure if this is really what you want. Maybe tell me what do you want to achieve?

mochadwi commented 4 years ago

Sry, but I don't get exactly what do you mean. But this is what I ment:

val taskA = tasks.register("taskA") {
    doLast{ println("DoLast TaskA") }
}

val taskB by tasks.registering {
    doLast {
       val moduleName: String? by project

        if (moduleName == null) throw IllegalArgumentException("Stop")

        println("DoLast TaskB")
    }
    dependsOn(taskA)
}
  • Running taskA will execute taskA
  • Running taskB without the property will throw an exception after taskA was run
  • Running taskB with the property will run taskA and taskB

I'm not sure if this is really what you want. Maybe tell me what do you want to achieve?

I've updated my original code in the questions.

The property of moduleName is needed to execute a different task, that's why I'm using dependsOn.

Or maybe is there any other alternative to enable this custom task but will "runs the other task" (not dependsOn)?

mochadwi commented 4 years ago
val unitTestSpecificModule by tasks.registering {
    group = "reporting"
    description = "Runs the specific module/feature unit tests with coverage."

    val buildVariant = Environments.BUILD_VARIANT
    val testVariant = "test${buildVariant}UnitTest"
    val moduleName: String? by project

    doLast {
        if (moduleName == null) throw InvalidUserDataException(
            """
                Please adjust the moduleName with your current :features:[moduleName] e.g: 
                root-project$: ./gradlew -PmoduleName=auth unitTestSpecificModule
            """.trimIndent()
        )
    }

    if (moduleName != null) {
        val unitTestTask = when (moduleName) {
            "domain" -> ":domain:test"
            "data" -> ":data:test"
            "legacy" -> ":legacy:$testVariant"
            "notask" -> "tasks"
            else -> ":features:${moduleName}:$testVariant"
        }

        finalizedBy(unitTestTask)
    }
}

This is works for me. As long moduleName != null will runs the task unitTestTask. But is this the correct implementation? cmiiw @StefMa

StefMa commented 4 years ago

Thanks for the code update. I see want to try to achieve.

I guess your proposed solution can be used :) I don't see any issues with using it like this 👍