Add build reports for diagnosing build problems in Gradle
#KT-12700 Fixed To turn build reports, add 'kotlin.build.report.enable=true' to gradle.properties file (or pass it in CLI via`-Pkotlin.build.report.enable=true`). By default build reports are created in `rootProject/build/reports/kotlin-build`. Build report dir can be customized via `kotlin.build.report.dir` property.
This commit is contained in:
+2
-1
@@ -26,5 +26,6 @@ interface CompilationResults : Remote {
|
||||
}
|
||||
|
||||
enum class CompilationResultCategory(val code: Int) {
|
||||
IC_COMPILE_ITERATION(0)
|
||||
IC_COMPILE_ITERATION(0),
|
||||
IC_LOG(1)
|
||||
}
|
||||
@@ -511,13 +511,18 @@ class CompileServiceImpl(
|
||||
|
||||
val workingDir = incrementalCompilationOptions.workingDir
|
||||
val modulesApiHistory = ModulesApiHistoryJs(incrementalCompilationOptions.modulesInfo)
|
||||
|
||||
val compiler = IncrementalJsCompilerRunner(
|
||||
workingDir = workingDir,
|
||||
reporter = reporter,
|
||||
buildHistoryFile = incrementalCompilationOptions.multiModuleICSettings.buildHistoryFile,
|
||||
modulesApiHistory = modulesApiHistory
|
||||
)
|
||||
return compiler.compile(allKotlinFiles, args, compilerMessageCollector, changedFiles)
|
||||
return try {
|
||||
compiler.compile(allKotlinFiles, args, compilerMessageCollector, changedFiles)
|
||||
} finally {
|
||||
reporter.flush()
|
||||
}
|
||||
}
|
||||
|
||||
private fun execIncrementalCompiler(
|
||||
@@ -570,6 +575,8 @@ class CompileServiceImpl(
|
||||
val workingDir = incrementalCompilationOptions.workingDir
|
||||
|
||||
val modulesApiHistory = incrementalCompilationOptions.run {
|
||||
reporter.report { "Use module detection: ${multiModuleICSettings.useModuleDetection}" }
|
||||
|
||||
if (!multiModuleICSettings.useModuleDetection) {
|
||||
ModulesApiHistoryJvm(modulesInfo)
|
||||
} else {
|
||||
@@ -587,7 +594,11 @@ class CompileServiceImpl(
|
||||
modulesApiHistory = modulesApiHistory,
|
||||
kotlinSourceFilesExtensions = allKotlinExtensions
|
||||
)
|
||||
return compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles)
|
||||
return try {
|
||||
compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles)
|
||||
} finally {
|
||||
reporter.flush()
|
||||
}
|
||||
}
|
||||
|
||||
override fun leaseReplSession(
|
||||
|
||||
@@ -22,26 +22,54 @@ import org.jetbrains.kotlin.incremental.ICReporter
|
||||
import java.io.File
|
||||
|
||||
internal class RemoteICReporter(
|
||||
private val servicesFacade: CompilerServicesFacadeBase,
|
||||
private val compilationResults: CompilationResults,
|
||||
compilationOptions: CompilationOptions
|
||||
private val servicesFacade: CompilerServicesFacadeBase,
|
||||
private val compilationResults: CompilationResults,
|
||||
compilationOptions: IncrementalCompilationOptions
|
||||
) : ICReporter {
|
||||
|
||||
private val rootDir = compilationOptions.modulesInfo.projectRoot
|
||||
private val shouldReportMessages = ReportCategory.IC_MESSAGE.code in compilationOptions.reportCategories
|
||||
private val isVerbose = compilationOptions.reportSeverity == ReportSeverity.DEBUG.code
|
||||
private val shouldReportCompileIteration = CompilationResultCategory.IC_COMPILE_ITERATION.code in compilationOptions.requestedCompilationResults
|
||||
private val shouldReportCompileIteration =
|
||||
CompilationResultCategory.IC_COMPILE_ITERATION.code in compilationOptions.requestedCompilationResults
|
||||
private val shouldReportICLog = CompilationResultCategory.IC_LOG.code in compilationOptions.requestedCompilationResults
|
||||
private val icLogLines = arrayListOf<String>()
|
||||
|
||||
override fun report(message: () -> String) {
|
||||
val lazyMessage = lazy { message() }
|
||||
if (shouldReportMessages && isVerbose) {
|
||||
servicesFacade.report(ReportCategory.IC_MESSAGE, ReportSeverity.DEBUG, message())
|
||||
servicesFacade.report(ReportCategory.IC_MESSAGE, ReportSeverity.DEBUG, lazyMessage.value)
|
||||
}
|
||||
if (shouldReportICLog) {
|
||||
icLogLines.add(lazyMessage.value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reportCompileIteration(sourceFiles: Collection<File>, exitCode: ExitCode) {
|
||||
if (shouldReportCompileIteration) {
|
||||
compilationResults.add(CompilationResultCategory.IC_COMPILE_ITERATION.code,
|
||||
CompileIterationResult(sourceFiles, exitCode.toString())
|
||||
compilationResults.add(
|
||||
CompilationResultCategory.IC_COMPILE_ITERATION.code,
|
||||
CompileIterationResult(sourceFiles, exitCode.toString())
|
||||
)
|
||||
}
|
||||
if (shouldReportICLog) {
|
||||
icLogLines.add("compile iteration:")
|
||||
sourceFiles.relativePaths(rootDir).forEach {
|
||||
icLogLines.add(" $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun flush() {
|
||||
if (shouldReportICLog) {
|
||||
compilationResults.add(CompilationResultCategory.IC_LOG.code, icLogLines)
|
||||
}
|
||||
}
|
||||
|
||||
private fun File.relativeOrCanonical(base: File): String =
|
||||
relativeToOrNull(base)?.path ?: canonicalPath
|
||||
|
||||
private fun Iterable<File>.relativePaths(base: File): List<String> =
|
||||
map { it.relativeOrCanonical(base) }.sorted()
|
||||
}
|
||||
|
||||
|
||||
+18
-9
@@ -21,18 +21,27 @@ internal class GradleCompilationResults(
|
||||
LoopbackNetworkInterface.clientLoopbackSocketFactory,
|
||||
LoopbackNetworkInterface.serverLoopbackSocketFactory
|
||||
) {
|
||||
|
||||
var icLogLines: List<String>? = null
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun add(compilationResultCategory: Int, value: Serializable) {
|
||||
if (compilationResultCategory == CompilationResultCategory.IC_COMPILE_ITERATION.code) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val compileIterationResult = value as? CompileIterationResult
|
||||
if (compileIterationResult != null) {
|
||||
val sourceFiles = compileIterationResult.sourceFiles
|
||||
if (sourceFiles.any()) {
|
||||
log.kotlinDebug { "compile iteration: ${sourceFiles.pathsAsStringRelativeTo(projectRootFile)}" }
|
||||
when (compilationResultCategory) {
|
||||
CompilationResultCategory.IC_COMPILE_ITERATION.code -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val compileIterationResult = value as? CompileIterationResult
|
||||
if (compileIterationResult != null) {
|
||||
val sourceFiles = compileIterationResult.sourceFiles
|
||||
if (sourceFiles.any()) {
|
||||
log.kotlinDebug { "compile iteration: ${sourceFiles.pathsAsStringRelativeTo(projectRootFile)}" }
|
||||
}
|
||||
val exitCode = compileIterationResult.exitCode
|
||||
log.kotlinDebug { "compiler exit code: $exitCode" }
|
||||
}
|
||||
val exitCode = compileIterationResult.exitCode
|
||||
log.kotlinDebug { "compiler exit code: $exitCode" }
|
||||
}
|
||||
CompilationResultCategory.IC_LOG.code -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
icLogLines = value as? List<String>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ internal class GradleCompilerEnvironment(
|
||||
messageCollector: GradlePrintingMessageCollector,
|
||||
outputItemsCollector: OutputItemsCollector,
|
||||
val outputFiles: FileCollection,
|
||||
val reportExecutionResult: Boolean,
|
||||
val incrementalCompilationEnvironment: IncrementalCompilationEnvironment? = null
|
||||
) : CompilerEnvironment(Services.EMPTY, messageCollector, outputItemsCollector) {
|
||||
val toolsJar: File? by lazy { findToolsJar() }
|
||||
|
||||
+2
-1
@@ -141,7 +141,8 @@ internal open class GradleCompilerRunner(protected val task: Task) {
|
||||
incrementalModuleInfo = modulesInfo,
|
||||
buildFile = buildFile,
|
||||
outputFiles = environment.outputFiles.toList(),
|
||||
taskPath = task.path
|
||||
taskPath = task.path,
|
||||
reportExecutionResult = environment.reportExecutionResult
|
||||
)
|
||||
TaskLoggers.put(task.path, task.logger)
|
||||
runCompilerAsync(workArgs)
|
||||
|
||||
+59
-5
@@ -11,7 +11,9 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.config.Services
|
||||
import org.jetbrains.kotlin.daemon.common.*
|
||||
import org.jetbrains.kotlin.gradle.logging.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
|
||||
import org.jetbrains.kotlin.gradle.report.TaskExecutionResult
|
||||
import org.jetbrains.kotlin.gradle.tasks.throwGradleExceptionIfError
|
||||
import org.jetbrains.kotlin.gradle.utils.stackTraceAsString
|
||||
import org.jetbrains.kotlin.incremental.ChangedFiles
|
||||
@@ -20,6 +22,7 @@ import org.slf4j.LoggerFactory
|
||||
import java.io.*
|
||||
import java.net.URLClassLoader
|
||||
import java.rmi.RemoteException
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Inject
|
||||
@@ -50,7 +53,8 @@ internal class GradleKotlinCompilerWorkArguments(
|
||||
val incrementalModuleInfo: IncrementalModuleInfo?,
|
||||
val buildFile: File?,
|
||||
val outputFiles: List<File>,
|
||||
val taskPath: String
|
||||
val taskPath: String,
|
||||
val reportExecutionResult: Boolean
|
||||
) : Serializable {
|
||||
companion object {
|
||||
const val serialVersionUID: Long = 0
|
||||
@@ -88,6 +92,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
|
||||
private val buildFile = config.buildFile
|
||||
private val outputFiles = config.outputFiles
|
||||
private val taskPath = config.taskPath
|
||||
private val reportExecutionResult = config.reportExecutionResult
|
||||
|
||||
private val log: KotlinLogger =
|
||||
TaskLoggers.get(taskPath)?.let { GradleKotlinLogger(it).apply { debug("Using '$taskPath' logger") } }
|
||||
@@ -226,7 +231,16 @@ internal class GradleKotlinCompilerWork @Inject constructor(
|
||||
requestedCompilationResults = emptyArray()
|
||||
)
|
||||
val servicesFacade = GradleCompilerServicesFacadeImpl(log, bufferingMessageCollector)
|
||||
return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null)
|
||||
return try {
|
||||
daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null)
|
||||
} finally {
|
||||
reportExecutionResultIfNeeded {
|
||||
TaskExecutionResult(
|
||||
executionStrategy = DAEMON_EXECUTION_STRATEGY,
|
||||
icLogLines = nonIcBuildLog("incremental compilation is not enabled for '$taskPath'")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun incrementalCompilationWithDaemon(
|
||||
@@ -238,6 +252,11 @@ internal class GradleKotlinCompilerWork @Inject constructor(
|
||||
val icEnv = incrementalCompilationEnvironment ?: error("incrementalCompilationEnvironment is null!")
|
||||
val knownChangedFiles = icEnv.changedFiles as? ChangedFiles.Known
|
||||
|
||||
val requestedCompilationResults = EnumSet.of(CompilationResultCategory.IC_COMPILE_ITERATION)
|
||||
if (reportExecutionResult) {
|
||||
requestedCompilationResults.add(CompilationResultCategory.IC_LOG)
|
||||
}
|
||||
|
||||
val compilationOptions = IncrementalCompilationOptions(
|
||||
areFileChangesKnown = knownChangedFiles != null,
|
||||
modifiedFiles = knownChangedFiles?.modified,
|
||||
@@ -245,7 +264,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
|
||||
workingDir = icEnv.workingDir,
|
||||
reportCategories = reportCategories(isVerbose),
|
||||
reportSeverity = reportSeverity(isVerbose),
|
||||
requestedCompilationResults = arrayOf(CompilationResultCategory.IC_COMPILE_ITERATION.code),
|
||||
requestedCompilationResults = requestedCompilationResults.map { it.code }.toTypedArray(),
|
||||
compilerMode = CompilerMode.INCREMENTAL_COMPILER,
|
||||
targetPlatform = targetPlatform,
|
||||
usePreciseJavaTracking = icEnv.usePreciseJavaTracking,
|
||||
@@ -257,11 +276,29 @@ internal class GradleKotlinCompilerWork @Inject constructor(
|
||||
log.info("Options for KOTLIN DAEMON: $compilationOptions")
|
||||
val servicesFacade = GradleIncrementalCompilerServicesFacadeImpl(log, bufferingMessageCollector)
|
||||
val compilationResults = GradleCompilationResults(log, projectRootFile)
|
||||
return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults)
|
||||
val result = daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults)
|
||||
|
||||
reportExecutionResultIfNeeded {
|
||||
TaskExecutionResult(
|
||||
executionStrategy = DAEMON_EXECUTION_STRATEGY,
|
||||
icLogLines = compilationResults.icLogLines
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun compileOutOfProcess(): ExitCode =
|
||||
runToolInSeparateProcess(compilerArgs, compilerClassName, compilerFullClasspath, log)
|
||||
try {
|
||||
runToolInSeparateProcess(compilerArgs, compilerClassName, compilerFullClasspath, log)
|
||||
} finally {
|
||||
reportExecutionResultIfNeeded {
|
||||
TaskExecutionResult(
|
||||
executionStrategy = OUT_OF_PROCESS_EXECUTION_STRATEGY,
|
||||
icLogLines = nonIcBuildLog("$OUT_OF_PROCESS_EXECUTION_STRATEGY execution strategy does not support incremental compilation")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileInProcess(messageCollector: MessageCollector): ExitCode {
|
||||
// in-process compiler should always be run in a different thread
|
||||
@@ -276,6 +313,13 @@ internal class GradleKotlinCompilerWork @Inject constructor(
|
||||
} finally {
|
||||
bufferingMessageCollector.flush(messageCollector)
|
||||
threadPool.shutdown()
|
||||
|
||||
reportExecutionResultIfNeeded {
|
||||
TaskExecutionResult(
|
||||
executionStrategy = IN_PROCESS_EXECUTION_STRATEGY,
|
||||
icLogLines = nonIcBuildLog("$IN_PROCESS_EXECUTION_STRATEGY execution strategy does not support incremental compilation")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,4 +364,14 @@ internal class GradleKotlinCompilerWork @Inject constructor(
|
||||
} else {
|
||||
ReportSeverity.DEBUG.code
|
||||
}
|
||||
|
||||
private inline fun reportExecutionResultIfNeeded(fn: () -> TaskExecutionResult) {
|
||||
if (reportExecutionResult) {
|
||||
val result = fn()
|
||||
TaskExecutionResults[taskPath] = result
|
||||
}
|
||||
}
|
||||
|
||||
private fun nonIcBuildLog(reason: String): List<String> =
|
||||
listOf("Performing non-incremental build: $reason")
|
||||
}
|
||||
+1
@@ -64,6 +64,7 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JV
|
||||
val outputItemCollector = OutputItemsCollectorImpl()
|
||||
val environment = GradleCompilerEnvironment(
|
||||
compilerClasspath, messageCollector, outputItemCollector,
|
||||
reportExecutionResult = kotlinCompileTask.reportExecutionResult,
|
||||
outputFiles = allOutputFiles()
|
||||
)
|
||||
if (environment.toolsJar == null && !isAtLeastJava9) {
|
||||
|
||||
+19
-2
@@ -19,16 +19,26 @@ package org.jetbrains.kotlin.gradle.plugin
|
||||
import org.gradle.BuildAdapter
|
||||
import org.gradle.BuildResult
|
||||
import org.gradle.api.invocation.Gradle
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.gradle.api.logging.Logging
|
||||
import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX
|
||||
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
|
||||
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
|
||||
import org.jetbrains.kotlin.gradle.report.KotlinBuildReporter
|
||||
import org.jetbrains.kotlin.gradle.report.configureBuildReporter
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.utils.relativeToRoot
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
|
||||
import java.io.File
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : BuildAdapter() {
|
||||
internal class KotlinGradleBuildServices private constructor(
|
||||
private val gradle: Gradle
|
||||
) : BuildAdapter() {
|
||||
companion object {
|
||||
private val CLASS_NAME = KotlinGradleBuildServices::class.java.simpleName
|
||||
const val FORCE_SYSTEM_GC_MESSAGE = "Forcing System.gc()"
|
||||
@@ -68,10 +78,17 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : B
|
||||
// but it is called before any plugin can attach build listener
|
||||
fun buildStarted() {
|
||||
startMemory = getUsedMemoryKb()
|
||||
|
||||
TaskLoggers.clear()
|
||||
TaskExecutionResults.clear()
|
||||
|
||||
configureBuildReporter(gradle, log)
|
||||
}
|
||||
|
||||
override fun buildFinished(result: BuildResult) {
|
||||
TaskLoggers.clear()
|
||||
TaskExecutionResults.clear()
|
||||
|
||||
val gradle = result.gradle!!
|
||||
GradleCompilerRunner.clearBuildModulesInfo()
|
||||
|
||||
@@ -100,7 +117,6 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : B
|
||||
log.lifecycle("[KOTLIN][PERF] Used memory after build: $endMem kb (difference since build start: ${"%+d".format(endMem - startMem)} kb)")
|
||||
}
|
||||
|
||||
TaskLoggers.clear()
|
||||
gradle.removeListener(this)
|
||||
instance = null
|
||||
log.kotlinDebug(DISPOSE_MESSAGE)
|
||||
@@ -122,3 +138,4 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : B
|
||||
private fun getGcCount(): Long =
|
||||
ManagementFactory.getGarbageCollectorMXBeans().sumByLong { Math.max(0, it.collectionCount) }
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -18,9 +18,11 @@ package org.jetbrains.kotlin.gradle.plugin
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.dsl.Coroutines
|
||||
import org.jetbrains.kotlin.gradle.report.BuildReportMode
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
internal fun PropertiesProvider.mapKotlinTaskProperties(task: AbstractKotlinCompile<*>) {
|
||||
@@ -54,6 +56,12 @@ internal class PropertiesProvider(private val project: Project) {
|
||||
val coroutines: Coroutines?
|
||||
get() = property("kotlin.coroutines")?.let { Coroutines.byCompilerArgument(it) }
|
||||
|
||||
val buildReportEnabled: Boolean
|
||||
get() = booleanProperty("kotlin.build.report.enable") ?: false
|
||||
|
||||
val buildReportDir: File?
|
||||
get() = property("kotlin.build.report.dir")?.let { File(it) }
|
||||
|
||||
val incrementalJvm: Boolean?
|
||||
get() = booleanProperty("kotlin.incremental")
|
||||
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.plugin.internal.state
|
||||
|
||||
import org.jetbrains.kotlin.gradle.report.TaskExecutionResult
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal object TaskExecutionResults {
|
||||
private val results = ConcurrentHashMap<String, TaskExecutionResult>()
|
||||
|
||||
operator fun get(taskPath: String): TaskExecutionResult? =
|
||||
results[taskPath]
|
||||
|
||||
operator fun set(taskPath: String, result: TaskExecutionResult) {
|
||||
results[taskPath] = result
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
results.clear()
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.report
|
||||
|
||||
import org.gradle.BuildAdapter
|
||||
import org.gradle.BuildResult
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.execution.TaskExecutionListener
|
||||
import org.gradle.api.invocation.Gradle
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.gradle.api.tasks.TaskState
|
||||
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||
import java.io.File
|
||||
import java.lang.StringBuilder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
internal fun configureBuildReporter(gradle: Gradle, log: Logger) {
|
||||
val rootProject = gradle.rootProject
|
||||
val properties = PropertiesProvider(rootProject)
|
||||
|
||||
if (!properties.buildReportEnabled) return
|
||||
|
||||
val perfLogDir = properties.buildReportDir
|
||||
?: rootProject.buildDir.resolve("reports/kotlin-build").apply { mkdirs() }
|
||||
|
||||
if (perfLogDir.isFile) {
|
||||
log.error("Kotlin build report cannot be created: '$perfLogDir' is a file")
|
||||
return
|
||||
}
|
||||
|
||||
perfLogDir.mkdirs()
|
||||
val ts = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().time)
|
||||
|
||||
val perfReportFile = perfLogDir.resolve("${gradle.rootProject.name}-build-$ts.txt")
|
||||
val reporter = KotlinBuildReporter(perfReportFile)
|
||||
gradle.addBuildListener(reporter)
|
||||
|
||||
gradle.taskGraph.whenReady { graph ->
|
||||
graph.allTasks.asSequence()
|
||||
.filterIsInstance<AbstractKotlinCompile<*>>()
|
||||
.forEach { it.reportExecutionResult = true }
|
||||
}
|
||||
|
||||
log.kotlinDebug { "Configured Kotlin build reporter" }
|
||||
}
|
||||
|
||||
internal class KotlinBuildReporter(private val perfReportFile: File) : BuildAdapter(), TaskExecutionListener {
|
||||
init {
|
||||
val dir = perfReportFile.parentFile
|
||||
check(dir.isDirectory) { "$dir does not exist or is a file" }
|
||||
check(!perfReportFile.isFile) { "Build report log file $perfReportFile exists already" }
|
||||
}
|
||||
|
||||
private val taskStartNs = HashMap<Task, Long>()
|
||||
private val kotlinTaskTimeNs = HashMap<Task, Long>()
|
||||
private val tasksSb = StringBuilder()
|
||||
|
||||
@Volatile
|
||||
private var allTasksTimeNs: Long = 0L
|
||||
|
||||
@Synchronized
|
||||
override fun beforeExecute(task: Task) {
|
||||
taskStartNs[task] = System.nanoTime()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun afterExecute(task: Task, state: TaskState) {
|
||||
val startNs = taskStartNs[task] ?: return
|
||||
|
||||
val endNs = System.nanoTime()
|
||||
val timeNs = endNs - startNs
|
||||
allTasksTimeNs += timeNs
|
||||
|
||||
if (!task.javaClass.name.startsWith("org.jetbrains.kotlin")) {
|
||||
return
|
||||
}
|
||||
|
||||
kotlinTaskTimeNs[task] = timeNs
|
||||
tasksSb.appendln()
|
||||
|
||||
val skipMessage = state.skipMessage
|
||||
if (skipMessage != null) {
|
||||
tasksSb.appendln("$task was skipped: $skipMessage")
|
||||
} else {
|
||||
tasksSb.appendln("$task finished in ${formatTime(timeNs)}")
|
||||
}
|
||||
|
||||
val path = task.path
|
||||
val executionResult = TaskExecutionResults[path]
|
||||
if (executionResult != null) {
|
||||
tasksSb.appendln("Execution strategy: ${executionResult.executionStrategy}")
|
||||
|
||||
executionResult.icLogLines?.let { lines ->
|
||||
tasksSb.appendln("Compilation log for $task:")
|
||||
lines.forEach { tasksSb.appendln(" $it") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun buildFinished(result: BuildResult) {
|
||||
val logger = result.gradle?.rootProject?.logger
|
||||
try {
|
||||
perfReportFile.writeText(taskOverview() + tasksSb.toString())
|
||||
logger?.lifecycle("Kotlin build report is written to ${perfReportFile.canonicalPath}")
|
||||
} catch (e: Throwable) {
|
||||
logger?.error("Could not write Kotlin build report to ${perfReportFile.canonicalPath}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun taskOverview(): String {
|
||||
val sb = StringBuilder()
|
||||
val kotlinTotalTimeNs = kotlinTaskTimeNs.values.sum()
|
||||
val ktTaskPercent = (kotlinTotalTimeNs.toDouble() / allTasksTimeNs * 100).asString(1)
|
||||
|
||||
sb.appendln("Total time for Kotlin tasks: ${formatTime(kotlinTotalTimeNs)} ($ktTaskPercent % of all tasks time)")
|
||||
|
||||
val table = TextTable("Time", "% of Kotlin time", "Task")
|
||||
kotlinTaskTimeNs.entries
|
||||
.sortedByDescending { (_, timeNs) -> timeNs }
|
||||
.forEach { (task, timeNs) ->
|
||||
val percent = (timeNs.toDouble() / kotlinTotalTimeNs * 100).asString(1)
|
||||
table.addRow(formatTime(timeNs), "$percent %", task.path)
|
||||
}
|
||||
table.printTo(sb)
|
||||
sb.appendln()
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun formatTime(ns: Long): String {
|
||||
val seconds = ns.toDouble() / 1_000_000_000
|
||||
return seconds.asString(2) + " s"
|
||||
}
|
||||
|
||||
private fun Double.asString(decPoints: Int): String =
|
||||
String.format("%.${decPoints}f", this)
|
||||
|
||||
private class TextTable(vararg columnNames: String) {
|
||||
private val rows = ArrayList<List<String>>()
|
||||
private val columnsCount = columnNames.size
|
||||
private val maxLengths = IntArray(columnsCount) { columnNames[it].length }
|
||||
|
||||
init {
|
||||
rows.add(columnNames.toList())
|
||||
}
|
||||
|
||||
fun addRow(vararg row: String) {
|
||||
check(row.size == columnsCount) { "Row size ${row.size} differs from columns count $columnsCount" }
|
||||
rows.add(row.toList())
|
||||
|
||||
for ((i, col) in row.withIndex()) {
|
||||
maxLengths[i] = max(maxLengths[i], col.length)
|
||||
}
|
||||
}
|
||||
|
||||
fun printTo(sb: StringBuilder) {
|
||||
for (row in rows) {
|
||||
sb.appendln()
|
||||
for ((i, col) in row.withIndex()) {
|
||||
if (i > 0) sb.append("|")
|
||||
|
||||
sb.append(col.padEnd(maxLengths[i], ' '))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.report
|
||||
|
||||
internal class TaskExecutionResult(
|
||||
val executionStrategy: String,
|
||||
val icLogLines: List<String>?
|
||||
)
|
||||
+1
@@ -71,6 +71,7 @@ open class KotlinCompileCommon : AbstractKotlinCompile<K2MetadataCompilerArgumen
|
||||
val compilerRunner = compilerRunner()
|
||||
val environment = GradleCompilerEnvironment(
|
||||
computedCompilerClasspath, messageCollector, outputItemCollector,
|
||||
reportExecutionResult = reportExecutionResult,
|
||||
outputFiles = allOutputFiles()
|
||||
)
|
||||
compilerRunner.runMetadataCompilerAsync(sourceRoots.kotlinSourceFiles, args, environment)
|
||||
|
||||
+5
@@ -132,6 +132,9 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
|
||||
logger.kotlinDebug { "Set $this.incremental=$value" }
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
var reportExecutionResult: Boolean = false
|
||||
|
||||
@get:Internal
|
||||
internal val buildHistoryFile: File
|
||||
get() = File(taskBuildDirectory, "build-history.bin")
|
||||
@@ -436,6 +439,7 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
|
||||
val environment = GradleCompilerEnvironment(
|
||||
computedCompilerClasspath, messageCollector, outputItemCollector,
|
||||
outputFiles = allOutputFiles(),
|
||||
reportExecutionResult = reportExecutionResult,
|
||||
incrementalCompilationEnvironment = icEnv
|
||||
)
|
||||
compilerRunner.runJvmCompilerAsync(
|
||||
@@ -593,6 +597,7 @@ open class Kotlin2JsCompile : AbstractKotlinCompile<K2JSCompilerArguments>(), Ko
|
||||
val environment = GradleCompilerEnvironment(
|
||||
computedCompilerClasspath, messageCollector, outputItemCollector,
|
||||
outputFiles = allOutputFiles(),
|
||||
reportExecutionResult = reportExecutionResult,
|
||||
incrementalCompilationEnvironment = icEnv
|
||||
)
|
||||
compilerRunner.runJsCompilerAsync(sourceRoots.kotlinSourceFiles, commonSourceSet.toList(), args, environment)
|
||||
|
||||
Reference in New Issue
Block a user