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