运行 仅当从依赖于另一个任务的命令行调用的任务,即使它失败

Run task only if invoked from command line with dependency on another task even if it fails

我会先解释我的用例,然后再概括描述我想做什么。

用例:打开测试结果

默认 $gradle test 将 运行 test 任务和 运行 所有单元测试。我想添加一个 browseTest 任务,如果在命令行中指定,它将在默认浏览器中打开测试报告。我已经有了打开测试结果的代码,但我需要弄清楚如何执行它。以下是我认为它应该如何工作:

Command Tests up-to-date Run tests? Test result? Open test report?
test no yes success/failure no
test yes no success/failure no
browseTest no yes success/failure yes
browseTest yes no success/failure yes

一般情况

我希望能够让一项任务(即 browseTest)将另一项任务(例如 task)添加到任务图中,而不依赖于其他任务的成功或失败。如果我使用 dependsOn 那么第一个 take 失败会阻止第二个任务执行。使用 mustRunAfter 指定顺序但不会将任务添加到任务图中(因此不会执行)。

如果有类似下面的东西,我相信它会得到我想要的:

task("browseTest") {
    addsToTaskGraph("test")
    mustRunAfter("test")
    doLast {
        // Open test results in browser
    }
}

更新答案

我又回答了我自己的问题。我能够成功创建 runsAfter 扩展函数。我希望能够清理触发 callback/hook 的逻辑,但总的来说,这对用户非常友好,而且效率不高。

val browseTest = task("browseTest") {
    runsAfter(tasks.test.name) {
        // perform "callback"
        val file = project.file("build/reports/tests/test/index.html")
        browse(file.absolutePath)
    }
}

fun Task.runsAfter(vararg paths: String, action: () -> Unit) {
    val parent = this
    val helper = task("${name}__runsAfter") {
        doLast {
            if (gradle.taskGraph.hasTask(parent)) {
                action()
            }
        }
    }
    paths.forEach {
        dependsOn(it)
        tasks[it].finalizedBy(helper)
    }
}

原答案

虽然我不太喜欢这个结果,但我能够让它工作。如果大家有其他想法,请告诉我。

val browseTest = task("browseTest") {
    dependsOn("test")
    val parent = this
    val helper = task("browseTestHelper") {
        doLast {
            if (gradle.taskGraph.hasTask(parent)) {
                // Open test results in browser
                val file = project.file("build/reports/tests/test/index.html")
                browse(file.absolutePath)
            }
        }
    }
    tasks.test {
        finalizedBy(helper)
    }
}

这里有几张:

  1. browseTest任务依赖于test,也就是说如果指定它会强制test到运行。
  2. browseTest 任务实际上没有做任何事情。
  3. 已创建 browseTestHelper 个任务。
  4. test 任务配置为由 browseTestHelper“完成”。
  5. browseTestHelper 运行 时,它检查原始 browseTest 任务是否在任务图中。如果没有,它什么都不做。
    • 这是必要的,因为 browseTestHelper 任务已创建并完成 test,即使未调用 browseTest 任务也是如此。
  6. 如果 browseTest 在任务图中,它将执行其操作。

有几种方法可以改进:

  1. 有没有比 val parent = this 行更好的访问“父”任务的方法?
  2. 您能否有条件地向任务添加终结器,同时查看任务图中是否存在任务?
    • 我猜不会,因为添加终结器的行为会影响任务图。
  3. 有没有办法将这一切包装在一个函数中,您只需调用一个函数即可调用?

做这样的事情会很棒:

val browseTest = task("browseTest") {
    runsAfter("test") {
        val file = project.file("build/reports/tests/test/index.html")
        browse(file.absolutePath)
    }
}

您自己的答案似乎太复杂了。在我看来,你的问题的本质是当 browseTest 将成为构建的一部分时,不要让任务 test 失败:

If I use dependsOn then the first task failing prevents the second task from executing.

嗯,您可以简单地通过使用其 ignoreFailures 属性 来防止任务 test 构建失败。将此 属性 与您的情况相结合,您就可以开始了。

我将在这里使用 Groovy 代码,因为我对 Gradle Kotlin DSL 不是很熟悉:

test {
    doFirst {
        ignoreFailures = gradle.taskGraph.hasTask('browseTest')
    }
}

task browseTest {
    dependsOn 'test'
    doLast {
        // open in browser
    }
}