【问题标题】:Gradle: task's standardOutput to file and terminal simultaneouslyGradle:任务的标准输出到文件和终端同时
【发布时间】:2014-08-29 07:03:29
【问题描述】:

我想将一个构建任务的standardOutput 更改为文件,因为它稍后会被另一个任务解析。

而且,我还想在终端中同时输出以查看构建中发生了什么。

这就是我将任务的输出更改为文件的方式:

task sampleTaskWithOutputToFile(type: Exec) {
    commandLine 'someCommand', 'param1'

    doFirst {
        standardOutput = new FileOutputStream('someFolder/someFile.out')
    }
} 

据我所知,我可以编写自己的 OutputStream 实现,同时输出到文件和标准 System.out 但我想使用现有的解决方案

另外,我不能使用像 tee 这样的 unix 工具,因为可以从任何操作系统(Mac OS、某些 Linux 甚至 Windows...)启动任务

谢谢!

【问题讨论】:

  • 一些第三方 Java 库(例如 Apache Commons)有一个TeeOutputStream
  • 好的,谢谢,我试试这个!

标签: gradle exec output


【解决方案1】:

阐述 Peter N 关于 TeeOutputStream 的评论:

task sampleTaskWithOutputToFile(type: Exec) {
    commandLine 'someCommand', 'param1'

    doFirst {
        standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
            new FileOutputStream("someFolder/someFile.out"), System.out);
    }
}

【讨论】:

    【解决方案2】:

    这是一个适用于任何类型的 Gradle Task 的解决方案,不仅适用于 Exec
    注意:我提供的所有示例都是 build.gradle.kts 格式和 Gradle 惰性配置语法。

    在您的特定情况下,您希望使用内存缓存:

    tasks.register<Exec>("sampleTaskWithOutputToFile") {
        commandLine ("someCommand", "param1")
    
        val taskOutput = StringBuilder()
        logging.addStandardOutputListener { taskOutput.append(it) }
        doLast {
            project.file("foo.output.txt").writeText(taskOutput.toString())
        }
    }
    

    或直接写入文件:

    tasks.register<Exec>("sampleTaskWithOutputToFile") {
        commandLine ("someCommand", "param1")
    
        lateinit var taskOutput: java.io.Writer
        doFirst {
            taskOutput = project.file("someFolder/someFile.out").writer()
        }
        logging.addStandardOutputListener { taskOutput.append(it) }
        doLast {
            // WARNING: if the task fails, this won't be executed and the file remains open.
            // The memory cache version doesn't have this problem.
            taskOutput.close()
        }
    }
    

    作为更一般的答案,我们可以认为外部插件注册了 foo 任务。

    tasks.register("foo") {
        doLast {
            println("warning: some message")
        }
    }
    

    在项目构建脚本中,可以捕获并处理输出,甚至实现“fail-on-warning”模式。

    tasks.named("foo") {
        val taskOutput = StringBuilder()
        logging.addStandardOutputListener { taskOutput.append(it) }
        doLast {
            // Usage of taskOutput must be in doLast, after all other task actions have been done.
            // Be careful: if there's another `doLast {}` added after this, the output from that won't be considered.
            if ("warning:" in taskOutput) {
                throw Exception(
                    """
                    There was a problem executing foo, please fix.
                    """.trimIndent()
                )
            }
        }
    }
    

    【讨论】:

    • 没有“build.gradle.kts 格式”;)它被称为“Kotlin DSL”
    • @Xerus 嗯其实?,Kotlin DSL 是一组主要由 Kotlin DSL 脚本使用的 API,其文件名以 .gradle.kts 结尾。 Kotlin DSL 构建脚本 (build.gradle.kts) 是其中的一个特殊脚本(其他包括初始化脚本、设置、包含的文件),它具有一些可供我们使用的隐式变量/属性:参见最后一句 in this section。这就是示例中使用tasks 的原因,因为build.gradle.kts 文件由Project 支持。
    【解决方案3】:

    如果您想要插件或 Groovy/Kotlin/Java 脚本的完整日志,这可能对您有所帮助。

    我正在使用这个我的类从我自己的插件将 Gradle 记录到一个文件中。 你可以使用这个类(它可以从 Kotlin 翻译成另一种语言,因为它并不难):

    示例:

    gradle.useLogger(FileLogger(File(project.rootDir, "log.log")))
    
    import org.gradle.BuildListener
    import org.gradle.BuildResult
    import org.gradle.api.Project
    import org.gradle.api.ProjectEvaluationListener
    import org.gradle.api.ProjectState
    import org.gradle.api.Task
    import org.gradle.api.execution.TaskActionListener
    import org.gradle.api.execution.TaskExecutionGraph
    import org.gradle.api.execution.TaskExecutionGraphListener
    import org.gradle.api.execution.TaskExecutionListener
    import org.gradle.api.initialization.Settings
    import org.gradle.api.invocation.Gradle
    import org.gradle.api.logging.StandardOutputListener
    import org.gradle.api.tasks.TaskState
    import org.gradle.internal.logging.events.OutputEvent
    import org.gradle.internal.logging.events.OutputEventListener
    import sk.kimbinogreen.gradle.extensions.deleteIfExists
    import java.io.ByteArrayOutputStream
    import java.io.File
    import java.io.PrintStream
    
    @Suppress("UnstableApiUsage")
    class FileLogger(val file: File) :
            OutputEventListener,
            StandardOutputListener,
            BuildListener,
            ProjectEvaluationListener,
            TaskExecutionGraphListener,
            TaskExecutionListener,
            TaskActionListener {
        init {
            file.parentFile.mkdirs()
            file.deleteIfExists()
            file.createNewFile()
            appendText("Build Started.")
        }
    
        override fun buildStarted(gradle: Gradle?) {
            gradle?.useLogger(this)
        }
    
        override fun beforeSettings(settings: Settings?) {
        }
    
        override fun settingsEvaluated(settings: Settings?) {
        }
    
        override fun projectsLoaded(gradle: Gradle?) {
            gradle?.useLogger(this)
        }
    
        override fun projectsEvaluated(gradle: Gradle?) {
            gradle?.useLogger(this)
        }
    
        override fun buildFinished(result: BuildResult?) {
            appendText("Build finished.")
            appendError(result?.failure)
        }
    
        override fun onOutput(p0: OutputEvent?) {
            p0?.let { appendText(it.toString()) }
        }
    
        override fun onOutput(p0: CharSequence?) {
            p0?.let {
                appendText(it.toString())
            }
        }
    
        override fun beforeExecute(task: Task) {
            appendText("Task [${task.project.name}:${task.name}] started.")
            task.logging.addStandardErrorListener(this)
            task.logging.addStandardOutputListener(this)
        }
    
        override fun afterExecute(task: Task, state: TaskState) {
            appendText("Task [${task.project.name}:${task.name}] finished.")
            appendError(state.failure)
            task.logging.removeStandardErrorListener(this)
            task.logging.removeStandardOutputListener(this)
        }
    
        override fun afterEvaluate(project: Project, state: ProjectState) {
            project.logging.removeStandardOutputListener(this)
            project.logging.removeStandardErrorListener(this)
        }
    
        override fun beforeEvaluate(project: Project) {
            project.logging.addStandardOutputListener(this)
            project.logging.addStandardErrorListener(this)
        }
    
        override fun graphPopulated(taskGraph: TaskExecutionGraph) {
        }
    
        override fun beforeActions(task: Task) {
            task.logging.addStandardErrorListener(this)
            task.logging.addStandardOutputListener(this)
        }
    
        override fun afterActions(task: Task) {
            task.logging.removeStandardErrorListener(this)
            task.logging.removeStandardOutputListener(this)
        }
    
        @Synchronized
        fun appendText(s: String?) {
            s?.split('\n')?.forEach {
                if(it.replace("\n", "").trim().isNotEmpty())
                    file.appendText("$it\n")
            }
        }
    
        @Synchronized
        fun appendError(t: Throwable?) {
            t?.let { error ->
                ByteArrayOutputStream().let { os ->
                    error.printStackTrace(PrintStream(os))
                    String(os.toByteArray())
                }.let {
                    appendText(it)
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-01
      • 1970-01-01
      相关资源
      最近更新 更多