All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.internal.compilerRunner.native.KotlinNativeToolRunner.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.internal.compilerRunner.native
import com.intellij.openapi.util.text.StringUtil.escapeStringCharacters
import org.gradle.api.file.FileCollection
import org.gradle.api.logging.Logging
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.process.ExecOperations
import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter
import org.jetbrains.kotlin.build.report.metrics.GradleBuildPerformanceMetric
import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime
import org.jetbrains.kotlin.build.report.metrics.measure
import org.jetbrains.kotlin.buildtools.internal.KotlinBuildToolsInternalJdkUtils
import org.jetbrains.kotlin.buildtools.internal.getJdkClassesClassLoader
import org.jetbrains.kotlin.compilerRunner.KotlinCompilerArgumentsLogLevel
import org.jetbrains.kotlin.gradle.internal.ClassLoadersCachingBuildService
import org.jetbrains.kotlin.gradle.internal.ParentClassLoaderProvider
import org.jetbrains.kotlin.gradle.logging.gradleLogLevel
import org.jetbrains.kotlin.gradle.plugin.statistics.BuildFusService
import org.jetbrains.kotlin.gradle.plugin.statistics.NativeArgumentMetrics
import org.jetbrains.kotlin.konan.target.HostManager
import java.io.IOException
import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.nio.file.Path
import java.util.*
import javax.inject.Inject
internal abstract class KotlinNativeToolRunner @Inject constructor(
private val metricsReporterProvider: Provider>,
private val classLoadersCachingBuildServiceProvider: Provider,
private val toolSpec: ToolSpec,
private val fusMetricsConsumer: Provider,
private val execOperations: ExecOperations,
) {
private val logger = Logging.getLogger(toolSpec.displayName.get())
private val classLoadersCachingBuildService: ClassLoadersCachingBuildService
get() = classLoadersCachingBuildServiceProvider.get()
private val metricsReporter get() = metricsReporterProvider.get()
fun runTool(args: ToolArguments) {
metricsReporter.measure(GradleBuildTime.RUN_COMPILATION_IN_WORKER) {
fusMetricsConsumer.orNull?.let { metricsConsumer ->
NativeArgumentMetrics.collectMetrics(args.arguments, metricsConsumer.getFusMetricsConsumer())
}
if (args.shouldRunInProcessMode) {
runInProcess(args)
} else {
runViaExec(args)
}
}
}
private fun runViaExec(args: ToolArguments) {
metricsReporter.measure(GradleBuildTime.NATIVE_IN_EXECUTOR) {
val systemProperties = System.getProperties()
/* Capture 'System.getProperties()' current state to avoid potential 'ConcurrentModificationException' */
.snapshot()
.asSequence()
.map { (k, v) -> k.toString() to v.toString() }
.filter { (k, _) -> k !in toolSpec.systemPropertiesBlacklist }
.escapeQuotesForWindows()
.toMap() + toolSpec.systemProperties
val toolArgsPair = if (toolSpec.shouldPassArgumentsViaArgFile.get()) {
val argFile = args.toArgFile()
argFile to listOfNotNull(
toolSpec.optionalToolName.orNull,
"@${argFile.toFile().absolutePath}"
)
} else {
null to listOfNotNull(toolSpec.optionalToolName.orNull) + args.arguments
}
try {
logger.log(
args.compilerArgumentsLogLevel.gradleLogLevel,
"""
|Run "${toolSpec.displayName.get()}" tool in a separate JVM process
|Main class = ${toolSpec.mainClass.get()}
|Arguments = ${args.arguments.toPrettyString()}
|Transformed arguments = ${toolArgsPair.second.toPrettyString()}
|Classpath = ${toolSpec.classpath.files.map { it.absolutePath }.toPrettyString()}
|JVM options = ${toolSpec.jvmArgs.get().toPrettyString()}
|Java system properties = ${systemProperties.toPrettyString()}
|Suppressed ENV variables = ${toolSpec.environmentBlacklist.toPrettyString()}
|Custom ENV variables = ${toolSpec.environment.toPrettyString()}
""".trimMargin()
)
execOperations.javaexec { spec ->
spec.mainClass.set(toolSpec.mainClass)
spec.classpath = toolSpec.classpath
spec.jvmArgs(toolSpec.jvmArgs.get())
spec.systemProperties(systemProperties)
spec.environment(toolSpec.environment)
toolSpec.environmentBlacklist.forEach { spec.environment.remove(it) }
spec.args(toolArgsPair.second)
}
} finally {
toolArgsPair.first?.let {
try {
Files.deleteIfExists(it)
} catch (_: IOException) {}
}
}
}
}
private fun runInProcess(args: ToolArguments) {
metricsReporter.measure(GradleBuildTime.NATIVE_IN_PROCESS) {
val isolatedClassLoader = classLoadersCachingBuildService.getClassLoader(
toolSpec.classpath.files.toList(),
// Required for KotlinNativePaths to properly detect konan home directory
object : ParentClassLoaderProvider {
@OptIn(KotlinBuildToolsInternalJdkUtils::class)
private val jdkClassesClassLoader = getJdkClassesClassLoader()
override fun getClassLoader(): ClassLoader? = jdkClassesClassLoader
override fun hashCode(): Int = jdkClassesClassLoader.hashCode()
override fun equals(other: Any?): Boolean =
other is ParentClassLoaderProvider && other.getClassLoader() == jdkClassesClassLoader
}
)
if (toolSpec.jvmArgs.get().contains("-ea")) isolatedClassLoader.setDefaultAssertionStatus(true)
logger.log(
args.compilerArgumentsLogLevel.gradleLogLevel,
"""
|Run in-process tool "${toolSpec.displayName.get()}"
|Entry point method = ${toolSpec.mainClass.get()}.${toolSpec.daemonEntryPoint.get()}
|Classpath = ${toolSpec.classpath.files.map { it.absolutePath }.toPrettyString()}
|Arguments = ${args.arguments.toPrettyString()}
""".trimMargin()
)
val toolArgs = listOf(toolSpec.displayName.get()) + args.arguments
try {
val mainClass = isolatedClassLoader.loadClass(toolSpec.mainClass.get())
val entryPoint = mainClass
.methods
.singleOrNull { it.name == toolSpec.daemonEntryPoint.get() }
?: error("Couldn't find daemon entry point '${toolSpec.daemonEntryPoint.get()}'")
metricsReporter.measure(GradleBuildTime.RUN_ENTRY_POINT) {
entryPoint.invoke(null, toolArgs.toTypedArray())
}
} catch (t: InvocationTargetException) {
throw t.targetException
}
}
}
private fun Properties.snapshot(): Properties = clone() as Properties
private fun Sequence>.escapeQuotesForWindows() =
if (HostManager.hostIsMingw) map { (key, value) -> key.escapeQuotes() to value.escapeQuotes() } else this
private fun String.escapeQuotes() = replace("\"", "\\\"")
private fun Map.toPrettyString(): String = buildString {
append('[')
if ([email protected] ()) append('\n')
[email protected] { (key, value) ->
append('\t').append(key).append(" = ").append(value.toPrettyString()).append('\n')
}
append(']')
}
private fun Collection.toPrettyString(): String = buildString {
append('[')
if ([email protected] ()) append('\n')
[email protected] { append('\t').append(it.toPrettyString()).append('\n') }
append(']')
}
private fun String.toPrettyString(): String =
when {
isEmpty() -> "\"\""
any { it == '"' || it.isWhitespace() } -> '"' + escapeStringCharacters(this) + '"'
else -> this
}
private fun ToolArguments.toArgFile(): Path {
val argFile = Files.createTempFile(
"kotlinc-native-args",
".lst"
)
argFile.toFile().printWriter().use { w ->
arguments.forEach { arg ->
val escapedArg = arg
.replace("\\", "\\\\")
.replace("\"", "\\\"")
w.println("\"$escapedArg\"")
}
}
return argFile
}
class ToolSpec(
val displayName: Provider,
val optionalToolName: Provider,
val mainClass: Provider,
val daemonEntryPoint: Provider,
val classpath: FileCollection,
val jvmArgs: ListProperty,
val shouldPassArgumentsViaArgFile: Provider,
val systemProperties: Map = emptyMap(),
val environment: Map = emptyMap(),
val environmentBlacklist: Set = emptySet(),
) {
val systemPropertiesBlacklist: Set = setOf(
"java.endorsed.dirs", // Fix for KT-25887
"user.dir", // Don't propagate the working dir of the current Gradle process
"java.system.class.loader" // Don't use custom class loaders
)
/**
* Disable C2 compiler for HotSpot VM to improve compilation speed.
*/
fun disableC2(): ToolSpec {
System.getProperty("java.vm.name")?.let { vmName ->
if (vmName.contains("HotSpot", true)) jvmArgs.add("-XX:TieredStopAtLevel=1")
}
return this
}
fun enableAssertions(): ToolSpec {
jvmArgs.add("-ea")
return this
}
fun configureDefaultMaxHeapSize(): ToolSpec {
if (jvmArgs.get().none { it.startsWith("-Xmx") }) {
jvmArgs.add("-Xmx3g")
}
return this
}
}
data class ToolArguments(
val shouldRunInProcessMode: Boolean,
val compilerArgumentsLogLevel: KotlinCompilerArgumentsLogLevel,
val arguments: List,
)
}