【问题标题】:Run task only if invoked from command line with dependency on another task even if it fails仅当从依赖于另一个任务的命令行调用时才运行任务,即使它失败了
【发布时间】:2021-04-29 18:06:36
【问题描述】:

我将首先解释我的用例,然后概括说明我想要做什么。

用例:打开测试结果

默认$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,那么第一次拍摄失败会阻止第二个任务执行。使用mustRunAfter 指定排序但不会将任务添加到任务图中(因此不会被执行)。

如果有类似以下的内容,我相信它会得到我想要的:

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

【问题讨论】:

    标签: gradle build.gradle gradle-kotlin-dsl


    【解决方案1】:

    更新答案

    我再次回答了我自己的问题。我能够成功创建runsAfter 扩展功能。我希望能够清理触发回调/挂钩的逻辑,但总体而言,这是非常用户友好的并且不会过于低效。

    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 任务是否在任务图中。如果没有,它什么也做不了。
      • 这是必要的,因为即使未调用 browseTest 任务,也会创建 browseTestHelper 任务并最终确定 test
    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)
        }
    }
    

    【讨论】:

      【解决方案2】:

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

      如果我使用dependsOn,那么第一个任务失败会阻止第二个任务执行。

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

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

      test {
          doFirst {
              ignoreFailures = gradle.taskGraph.hasTask('browseTest')
          }
      }
      
      task browseTest {
          dependsOn 'test'
          doLast {
              // open in browser
          }
      }
      

      【讨论】:

      • 我真的,真的不想阻止任务失败。如果有其他任务仅在成功的测试中执行,那么如果测试失败,我不会想要执行它们,即使我碰巧打开了测试报告。我真的希望能够依赖可能会或可能不会失败的测试,但它看起来不像 Gradle 支持的那样。不过,这绝对是一种更短更甜的快速修复方法:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多