blob: b94e2aebbcd66ed6e509c9daa3488aef94757a91 [file] [log] [blame]
rootProject {
apply<DegradePlugin>()
}
class DegradePlugin : Plugin<Project> {
override fun apply(target: Project) {
if (target != target.rootProject) return
Degrade(target).register()
}
}
private class Degrade(val rootProject: Project) {
private val scriptDir = rootProject.file("degrade")
private class TaskLog {
val stdout = StringBuilder()
fun clear() {
stdout.clear()
}
}
private fun setupTaskLog(task: Task): TaskLog {
val log = TaskLog()
task.logging.addStandardOutputListener { log.stdout.append(it) }
return log
}
fun register() {
rootProject.gradle.addListener(object : BuildAdapter(), TaskExecutionListener {
val taskToLog = mutableMapOf<Task, TaskLog>()
val allScripts = mutableListOf<String>()
val failedScripts = mutableListOf<String>()
@Synchronized
override fun beforeExecute(task: Task) {
taskToLog.getOrPut(task) { setupTaskLog(task) }.clear()
}
@Synchronized
override fun afterExecute(task: Task, state: TaskState) {
val log = taskToLog[task] ?: return
val script = generateScriptForTask(task, log) ?: return
allScripts += script
if (state.failure != null) {
failedScripts += script
}
}
@Synchronized
override fun buildFinished(result: BuildResult) {
try {
generateAggregateScript("rerun-all.sh", allScripts)
generateAggregateScript("rerun-failed.sh", failedScripts)
} finally {
failedScripts.clear()
allScripts.clear()
}
}
})
}
private fun generateAggregateScript(name: String, scripts: List<String>) = generateScript(name) {
appendLine("""cd "$(dirname "$0")"""")
appendLine()
scripts.forEach {
appendLine("./$it")
}
}
private fun generateScriptForTask(task: Task, taskLog: TaskLog): String? {
val project = task.project
val stdoutLinesIterator = taskLog.stdout.split('\n').iterator()
val commands = parseKotlinNativeCommands { stdoutLinesIterator.takeIf { it.hasNext() }?.next() }
if (commands.isEmpty()) return null
val konanHome = project.properties["konanHome"] ?: project.properties["kotlinNativeDist"]
val scriptName = task.path.substring(1).replace(':', '_') + ".sh"
generateScript(scriptName) {
appendLine("""kotlinNativeDist="$konanHome"""")
appendLine()
commands.forEach { command ->
appendLine(""""${"$"}kotlinNativeDist/bin/run_konan" \""")
command.transformedArguments.forEachIndexed { index, argument ->
append(" ")
append(argument)
if (index != command.transformedArguments.lastIndex) {
appendLine(" \\")
}
}
appendLine()
appendLine()
}
}
return scriptName
}
private fun parseKotlinNativeCommands(nextLine: () -> String?): List<KotlinNativeCommand> {
val result = mutableListOf<KotlinNativeCommand>()
while (true) {
val line = nextLine() ?: break
if (line != "Main class = $kotlinNativeEntryPointClass"
&& !line.startsWith("Entry point method = $kotlinNativeEntryPointClass.")) continue
generateSequence(nextLine)
.firstOrNull { it.startsWith("Transformed arguments = ") }
.takeIf { it == "Transformed arguments = [" }
?: continue
val transformedArguments = generateSequence(nextLine)
.takeWhile { it != "]" }
.flatMap {
val line = it.trimStart()
if (line.startsWith("@")) { // argument with filename containing list of arguments
File(line.substringAfter("@"))
.readText()
.split("\n")
.map { it.substringAfter('"').substringBeforeLast('"') }
} else
listOf(line)
}
.toList()
result += KotlinNativeCommand(transformedArguments)
}
return result
}
private class KotlinNativeCommand(val transformedArguments: List<String>)
private companion object {
const val kotlinNativeEntryPointClass = "org.jetbrains.kotlin.cli.utilities.MainKt"
// appendLine is not available in Kotlin stdlib shipped with older Gradle versions;
// Copied here:
/** Appends a line feed character (`\n`) to this Appendable. */
private fun Appendable.appendLine(): Appendable = append('\n')
/** Appends value to the given Appendable and a line feed character (`\n`) after it. */
private fun Appendable.appendLine(value: CharSequence?): Appendable = append(value).appendLine()
}
private fun generateScript(name: String, generateBody: Appendable.() -> Unit) {
scriptDir.mkdirs()
val file = File(scriptDir, name)
file.bufferedWriter().use { writer ->
writer.appendLine("#!/bin/sh")
writer.appendLine("set -e")
writer.appendLine()
writer.generateBody()
}
file.setExecutable(true)
}
}