From bc01676489b69ee534af01758c3444bd6d6a0050 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Wed, 6 Oct 2021 09:48:16 +0000 Subject: [PATCH] Add kotlin-native/tools/degrade -- a helper for K/N Gradle builds Intended to be helpful in minimizing reproducers or investigating Kotlin/Native-related problems. It analyses kotlin-multiplatform Gradle plugin events, and creates shell scripts that run executed Kotlin/Native tools (compiler, cinterop) without Gradle. --- kotlin-native/tools/degrade/README.md | 27 +++ kotlin-native/tools/degrade/degrade | 24 +++ .../tools/degrade/degrade.init.gradle.kts | 154 ++++++++++++++++++ .../tools/degrade/settings.gradle.kts | 1 + 4 files changed, 206 insertions(+) create mode 100644 kotlin-native/tools/degrade/README.md create mode 100755 kotlin-native/tools/degrade/degrade create mode 100644 kotlin-native/tools/degrade/degrade.init.gradle.kts create mode 100644 kotlin-native/tools/degrade/settings.gradle.kts diff --git a/kotlin-native/tools/degrade/README.md b/kotlin-native/tools/degrade/README.md new file mode 100644 index 00000000000..3709a291028 --- /dev/null +++ b/kotlin-native/tools/degrade/README.md @@ -0,0 +1,27 @@ +# degrade + +`degrade` is a tool that analyses kotlin-multiplatform Gradle plugin events, +and creates shell scripts that run executed Kotlin/Native tools (compiler, cinterop) +without Gradle. + +Intended to be helpful in minimizing reproducers or investigating +Kotlin/Native-related problems. + +Confirmed to work with Kotlin versions from 1.5.10 to 1.6.0-M1. +Probably doesn't work on Windows. + +## Usage + +``` +degrade +``` + +for example, in a Gradle project directory +``` +degrade ./gradlew build +``` + +The tool runs Gradle build, analyses certain events during the run, +and generates shell scripts in the `degrade` directory at the project root. +It emits a script per Kotlin/Native task, and also `rerun-all.sh` and +`rerun-failed.sh`. diff --git a/kotlin-native/tools/degrade/degrade b/kotlin-native/tools/degrade/degrade new file mode 100755 index 00000000000..3ff3326335c --- /dev/null +++ b/kotlin-native/tools/degrade/degrade @@ -0,0 +1,24 @@ +#!/bin/sh + +# Copied from gradlew: +# ------ +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null +# ------ + +exec "$@" -i -I "$APP_HOME/degrade.init.gradle.kts" diff --git a/kotlin-native/tools/degrade/degrade.init.gradle.kts b/kotlin-native/tools/degrade/degrade.init.gradle.kts new file mode 100644 index 00000000000..abf9610d53c --- /dev/null +++ b/kotlin-native/tools/degrade/degrade.init.gradle.kts @@ -0,0 +1,154 @@ +rootProject { + apply() +} + +class DegradePlugin : Plugin { + 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() + } + } + + fun register() { + val taskToLog = mutableMapOf() + val allScripts = mutableListOf() + val failedScripts = mutableListOf() + + rootProject.allprojects { + tasks.all { + val task = this@all + val state = taskToLog.getOrPut(task, ::TaskLog) + task.logging.addStandardOutputListener { state.stdout.append(it) } + } + } + + rootProject.gradle.addListener(object : BuildAdapter(), TaskExecutionListener { + override fun beforeExecute(task: Task) { + taskToLog.getOrPut(task, ::TaskLog).clear() + } + + 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 + } + } + + 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) = 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"] + + 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 { + val result = mutableListOf() + + 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 != "]" } + .map { it.trimStart() } + .toList() + + result += KotlinNativeCommand(transformedArguments) + } + + return result + } + + private class KotlinNativeCommand(val transformedArguments: List) + + 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) + } +} diff --git a/kotlin-native/tools/degrade/settings.gradle.kts b/kotlin-native/tools/degrade/settings.gradle.kts new file mode 100644 index 00000000000..26dcfb7dffb --- /dev/null +++ b/kotlin-native/tools/degrade/settings.gradle.kts @@ -0,0 +1 @@ +// Defines an empty Gradle project, helping to open degrade.init.gradle.kts in IntelliJ IDEA. \ No newline at end of file