Gradle:任务的标准输出同时到文件和终端

时间:2014-07-08 13:50:53

标签: gradle exec output

我想将一个构建任务的standardOutput更改为file,因为稍后将由另一个任务对其进行解析。

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

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

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

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

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

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

谢谢!

3 个答案:

答案 0 :(得分:17)

阐述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);
    }
}

答案 1 :(得分:1)

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

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

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")
    }
}

在项目构建脚本中,可以捕获输出并对其执行操作,甚至可以实施“警告失败”模式。

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()
            )
        }
    }
}

答案 2 :(得分:0)

如果您想从插件或groovy / kotlin / java脚本中获取完整日志,A可能对您有所帮助。
我正在使用该类来通过我自己的插件将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)
            }
        }
    }
}