org.gradle.configurationcache.problems.ConfigurationCacheReport.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2020 the original author or authors.
*
* 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 org.gradle.configurationcache.problems
import org.apache.groovy.json.internal.CharBuf
import org.gradle.api.internal.file.temp.TemporaryFileProvider
import org.gradle.configurationcache.logger
import org.gradle.internal.concurrent.ExecutorFactory
import org.gradle.internal.concurrent.ManagedExecutor
import org.gradle.internal.hash.Hashing
import org.gradle.internal.hash.HashingOutputStream
import org.gradle.internal.service.scopes.Scopes
import org.gradle.internal.service.scopes.ServiceScope
import java.io.Closeable
import java.io.File
import java.nio.file.Files
import java.util.concurrent.TimeUnit
import kotlin.contracts.contract
@ServiceScope(Scopes.BuildTree::class)
class ConfigurationCacheReport(
executorFactory: ExecutorFactory,
temporaryFileProvider: TemporaryFileProvider
) : Closeable {
private
sealed class State {
open fun onDiagnostic(kind: DiagnosticKind, problem: PropertyProblem): State =
illegalState()
/**
* Writes the report file to the given [outputDirectory] if and only if
* there are diagnostics to report.
*
* @return a pair with the new [State] and the written [File], which will be `null` when there are no diagnostics.
*/
open fun commitReportTo(
outputDirectory: File,
cacheAction: String,
requestedTasks: String,
totalProblemCount: Int
): Pair =
illegalState()
open fun close(): State =
illegalState()
private
fun illegalState(): Nothing =
throw IllegalStateException("Operation is not valid in ${javaClass.simpleName} state.")
class Idle(
val executorFactory: ExecutorFactory,
val temporaryFileProvider: TemporaryFileProvider
) : State() {
/**
* There's nothing to write, return null.
*/
override fun commitReportTo(
outputDirectory: File,
cacheAction: String,
requestedTasks: String,
totalProblemCount: Int
): Pair =
this to null
override fun onDiagnostic(kind: DiagnosticKind, problem: PropertyProblem): State =
Spooling(
htmlReportSpoolFile(),
singleThreadExecutor(),
CharBuf::class.java.classLoader
).onDiagnostic(kind, problem)
private
fun htmlReportSpoolFile() =
temporaryFileProvider.createTemporaryFile("configuration-cache-report", "html")
private
fun singleThreadExecutor() =
executorFactory.create("Configuration cache report writer", 1)
override fun close(): State =
this
}
class Spooling(
val spoolFile: File,
val executor: ManagedExecutor,
/**
* [JsonModelWriter] uses Groovy's [CharBuf] for fast json encoding.
*/
val groovyJsonClassLoader: ClassLoader
) : State() {
private
val hashingStream = HashingOutputStream(Hashing.md5(), spoolFile.outputStream().buffered())
private
val writer = HtmlReportWriter(hashingStream.writer())
init {
executor.submit {
Thread.currentThread().contextClassLoader = groovyJsonClassLoader
writer.beginHtmlReport()
}
}
override fun onDiagnostic(kind: DiagnosticKind, problem: PropertyProblem): State {
executor.submit {
writer.writeDiagnostic(
kind,
problem
)
}
return this
}
override fun commitReportTo(outputDirectory: File, cacheAction: String, requestedTasks: String, totalProblemCount: Int): Pair {
lateinit var reportFile: File
executor.submit {
closeHtmlReport(cacheAction, requestedTasks, totalProblemCount)
reportFile = moveSpoolFileTo(outputDirectory)
}
executor.shutdownAndAwaitTermination()
return Closed to reportFile
}
override fun close(): State {
if (executor.isShutdown) {
writer.close()
} else {
executor.submit {
writer.close()
}
executor.shutdown()
}
return Closed
}
private
fun closeHtmlReport(cacheAction: String, requestedTasks: String, totalProblemCount: Int) {
writer.endHtmlReport(cacheAction, requestedTasks, totalProblemCount)
writer.close()
}
private
fun ManagedExecutor.shutdownAndAwaitTermination() {
shutdown()
if (!awaitTermination(30, TimeUnit.SECONDS)) {
logger.warn(
"Configuration cache report is taking too long to write... "
+ "The build might finish before the report has been completely written."
)
}
}
private
fun moveSpoolFileTo(outputDirectory: File): File {
val reportDir = outputDirectory.resolve(reportHash())
val reportFile = reportDir.resolve(HtmlReportTemplate.reportHtmlFileName)
if (!reportFile.exists()) {
require(reportDir.mkdirs()) {
"Could not create configuration cache report directory '$reportDir'"
}
Files.move(spoolFile.toPath(), reportFile.toPath())
}
return reportFile
}
private
fun reportHash() =
hashingStream.hash().toCompactString()
}
object Closed : State() {
override fun close(): State = this
}
}
private
var state: State = State.Idle(
executorFactory,
temporaryFileProvider
)
private
val stateLock = Object()
override fun close() {
modifyState {
close()
}
}
fun onProblem(problem: PropertyProblem) {
modifyState {
onDiagnostic(DiagnosticKind.PROBLEM, problem)
}
}
fun onInput(input: PropertyProblem) {
modifyState {
onDiagnostic(DiagnosticKind.INPUT, input)
}
}
/**
* Writes the report file to [outputDirectory].
*
* The file is laid out in such a way as to allow extracting the pure JSON model,
* see [HtmlReportWriter].
*/
internal
fun writeReportFileTo(outputDirectory: File, cacheAction: String, requestedTasks: String, totalProblemCount: Int): File? {
var reportFile: File?
modifyState {
val (newState, outputFile) = commitReportTo(outputDirectory, cacheAction, requestedTasks, totalProblemCount)
reportFile = outputFile
newState
}
return reportFile
}
private
inline fun modifyState(f: State.() -> State) {
contract {
callsInPlace(f, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
synchronized(stateLock) {
state = state.f()
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy