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:
committed by
Space
parent
6bfa710e34
commit
bc01676489
@@ -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`.
|
||||
Executable
+24
@@ -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.
|
||||
Reference in New Issue
Block a user