| /* |
| * Copyright 2018 The Bazel Authors. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package io.bazel.kotlin.builder.utils |
| |
| import com.google.protobuf.MessageOrBuilder |
| import com.google.protobuf.TextFormat |
| import io.bazel.kotlin.builder.toolchain.CompilationStatusException |
| import io.bazel.kotlin.model.CompilationTaskInfo |
| import java.io.ByteArrayInputStream |
| import java.io.ByteArrayOutputStream |
| import java.io.File |
| import java.io.PrintStream |
| import java.nio.file.Paths |
| |
| class CompilationTaskContext(val info: CompilationTaskInfo, private val out: PrintStream) { |
| private val start = System.currentTimeMillis() |
| private val executionRoot: String = Paths.get("").toAbsolutePath().toString() + File.separator |
| private var timings: MutableList<String>? |
| private var level = -1 |
| @PublishedApi |
| internal val isTracing: Boolean |
| |
| init { |
| val debugging = info.debugList.toSet() |
| timings = if (debugging.contains("timings")) mutableListOf() else null |
| isTracing = debugging.contains("trace") |
| } |
| |
| fun reportUnhandledException(throwable: Throwable) { |
| throwable.printStackTrace(out) |
| } |
| |
| @Suppress("unused") |
| fun print(msg: String) { |
| out.println(msg) |
| } |
| |
| /** |
| * Print a list of debugging lines. |
| * |
| * @param header a header string |
| * @param lines a list of lines to print out |
| * @param prefix a prefix to add to each line |
| * @param filterEmpty if empty lines should be discarded or not |
| */ |
| fun printLines(header: String, lines: List<String>, prefix: String = "| ", filterEmpty: Boolean = false) { |
| check(header.isNotEmpty()) |
| out.println(if (header.endsWith(":")) header else "$header:") |
| lines.forEach { |
| if (it.isNotEmpty() || !filterEmpty) { |
| out.println("$prefix$it") |
| } |
| } |
| out.println() |
| } |
| |
| inline fun <T> whenTracing(block: CompilationTaskContext.() -> T): T? { |
| return if (isTracing) { |
| block() |
| } else null |
| } |
| |
| /** |
| * Print a proto message if debugging is enabled for the task. |
| */ |
| fun printProto(header: String, msg: MessageOrBuilder) { |
| printLines(header, TextFormat.printToString(msg).split("\n"), filterEmpty = true) |
| } |
| |
| /** |
| * This method normalizes and reports the output from the Kotlin compiler. |
| */ |
| fun printCompilerOutput(lines: List<String>) { |
| lines.map(::trimExecutionRootPrefix).forEach(out::println) |
| } |
| |
| private fun trimExecutionRootPrefix(toPrint: String): String { |
| // trim off the workspace component |
| return if (toPrint.startsWith(executionRoot)) { |
| toPrint.replaceFirst(executionRoot, "") |
| } else toPrint |
| } |
| |
| /** |
| * Execute a compilation task. |
| * |
| * @throws CompilationStatusException if the compiler returns a status of anything but zero. |
| * @param args the compiler command line switches |
| * @param printOnFail if this is true the output will be printed if the task fails else the caller is responsible |
| * for logging it by catching the [CompilationStatusException] excepotion. |
| * @param compile the compilation method. |
| */ |
| inline fun executeCompilerTask( |
| args: List<String>, |
| compile: (Array<String>, PrintStream) -> Int, |
| printOnFail: Boolean = true, |
| printOnSuccess: Boolean = true |
| ): List<String> { |
| val outputStream = ByteArrayOutputStream() |
| val ps = PrintStream(outputStream) |
| val result = compile(args.toTypedArray(), ps) |
| val output = ByteArrayInputStream(outputStream.toByteArray()).bufferedReader().readLines() |
| if (result != 0) { |
| if (printOnFail) { |
| printCompilerOutput(output) |
| } |
| throw CompilationStatusException("compile phase failed", result, output) |
| } else if (printOnSuccess) { |
| printCompilerOutput(output) |
| } |
| return output |
| } |
| |
| /** |
| * Runs a task and records the timings. |
| */ |
| fun <T> execute(name: String, task: () -> T): T = execute({ name }, task) |
| |
| /** |
| * Runs a task and records the timings. |
| */ |
| @Suppress("MemberVisibilityCanBePrivate") |
| fun <T> execute(name: () -> String, task: () -> T): T { |
| return if (timings == null) { |
| task() |
| } else pushTimedTask(name(), task) |
| } |
| |
| private inline fun <T> pushTimedTask(name: String, task: () -> T): T { |
| level += 1 |
| val previousTimings = timings |
| timings = mutableListOf() |
| return try { |
| System.currentTimeMillis().let { start -> |
| task().also { |
| val stop = System.currentTimeMillis() |
| previousTimings!! += "${" ".repeat(level)} * $name: ${stop - start} ms" |
| previousTimings.addAll(timings!!) |
| } |
| } |
| } finally { |
| level -= 1 |
| timings = previousTimings |
| } |
| } |
| |
| /** |
| * This method should be called at the end of builder invocation. |
| * |
| * @param successful true if the task finished successfully. |
| */ |
| fun finalize(successful: Boolean) { |
| if (successful) { |
| timings?.also { |
| printLines("Task timings for ${info.label} (total: ${System.currentTimeMillis() - start} ms)", it) |
| } |
| } |
| } |
| } |