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.
This commit is contained in:
Svyatoslav Scherbina
2021-10-06 09:48:16 +00:00
committed by Space
parent 6bfa710e34
commit bc01676489
4 changed files with 206 additions and 0 deletions
+27
View File
@@ -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 <your usual Gradle invocation>
```
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`.
+24
View File
@@ -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"
@@ -0,0 +1,154 @@
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()
}
}
fun register() {
val taskToLog = mutableMapOf<Task, TaskLog>()
val allScripts = mutableListOf<String>()
val failedScripts = mutableListOf<String>()
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<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"]
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 != "]" }
.map { it.trimStart() }
.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)
}
}
@@ -0,0 +1 @@
// Defines an empty Gradle project, helping to open degrade.init.gradle.kts in IntelliJ IDEA.