diff --git a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/KonanToolRunner.kt b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/KonanToolRunner.kt index 065125554d9..41f52389fa8 100644 --- a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/KonanToolRunner.kt +++ b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/KonanToolRunner.kt @@ -16,172 +16,129 @@ package org.jetbrains.kotlin.gradle.plugin.konan -import org.gradle.api.Named import org.gradle.api.Project -import org.gradle.api.file.FileCollection -import org.gradle.api.Action -import org.gradle.api.GradleException -import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.gradle.jvm.toolchain.JavaLauncher -import org.gradle.jvm.toolchain.JavaToolchainService -import org.gradle.jvm.toolchain.JavaToolchainSpec -import org.gradle.process.ExecSpec import org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin.ProjectProperty.KONAN_HOME -import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget -import org.jetbrains.kotlin.konan.util.DependencyProcessor import java.nio.file.Files import org.jetbrains.kotlin.* -import java.io.ByteArrayOutputStream - -internal interface KonanToolRunner : Named { - val mainClass: String - val classpath: FileCollection - val jvmArgs: List - val environment: Map +import org.jetbrains.kotlin.konan.properties.resolvablePropertyString +import org.jetbrains.kotlin.konan.util.DependencyDirectories +import java.io.File +import java.util.Properties +import org.jetbrains.kotlin.compilerRunner.KotlinToolRunner +internal interface KonanToolRunner { fun run(args: List) - fun run(vararg args: String) = run(args.toList()) } +internal fun KonanToolRunner.run(vararg args: String) = run(args.toList()) + internal abstract class KonanCliRunner( - val toolName: String, - val fullName: String, - val project: Project, - private val additionalJvmArgs: List, - private val konanHome: String -) : KonanToolRunner { - override val mainClass = "org.jetbrains.kotlin.cli.utilities.MainKt" + protected val toolName: String, + project: Project, + val additionalJvmArgs: List = emptyList(), + val konanHome: String = project.konanHome +) : KotlinToolRunner(project), KonanToolRunner { + final override val displayName get() = toolName - override fun getName() = toolName + final override val mainClass get() = "org.jetbrains.kotlin.cli.utilities.MainKt" + final override val daemonEntryPoint get() = "daemonMain" // We need to unset some environment variables which are set by XCode and may potentially affect the tool executed. - protected val blacklistEnvironment: List by lazy { - KonanPlugin::class.java.getResourceAsStream("/env_blacklist")?.let { stream -> - stream.reader().use { it.readLines() } - } ?: emptyList() + final override val execEnvironmentBlacklist: Set by lazy { + HashSet().also { collector -> + KonanPlugin::class.java.getResourceAsStream("/env_blacklist")?.let { stream -> + stream.reader().use { r -> r.forEachLine { collector.add(it) } } + } + } } - protected val blacklistProperties: Set = - setOf( - "java.endorsed.dirs", // Fix for KT-25887 - "user.dir" // Don't propagate the working dir of the current Gradle process - ) + final override val execSystemProperties by lazy { mapOf("konan.home" to konanHome) } - override val classpath: FileCollection = - project.fileTree("$konanHome/konan/lib/") - .apply { include("*.jar") } + final override val classpath by lazy { project.fileTree("$konanHome/konan/lib/").apply { include("*.jar") }.files } - override val jvmArgs = HostManager.defaultJvmArgs.toMutableList().apply { - if (additionalJvmArgs.none { it.startsWith("-Xmx") } && - project.jvmArgs.none { it.startsWith("-Xmx") }) { - add("-Xmx3G") - } - addAll(additionalJvmArgs) - addAll(project.jvmArgs) - } - - override val environment = mutableMapOf("LIBCLANG_DISABLE_CRASH_RECOVERY" to "1") - - private fun String.escapeQuotes() = replace("\"", "\\\"") - - private fun Sequence>.escapeQuotesForWindows() = - if (HostManager.hostIsMingw) { - map { (key, value) -> key.escapeQuotes() to value.escapeQuotes() } - } else { - this - } - - open protected fun transformArgs(args: List): List = args - - override fun run(args: List) { - project.logger.info("Run tool: $toolName with args: ${args.joinToString(separator = " ")}") - if (classpath.isEmpty) { - throw IllegalStateException("Classpath of the tool is empty: $toolName\n" + - "Probably the '${KONAN_HOME.propertyName}' project property contains an incorrect path.\n" + - "Please change it to the compiler root directory and rerun the build.") - } - - @Suppress("UNCHECKED_CAST") - val launcher = project.getProperty(KonanPlugin.ProjectProperty.KONAN_JVM_LAUNCHER) as? Provider - ?: throw IllegalStateException("Missing property: ${KonanPlugin.ProjectProperty.KONAN_JVM_LAUNCHER}") - - val out = ByteArrayOutputStream() - val err = ByteArrayOutputStream() - - val execResult = project.exec(object : Action { - override fun execute(exec: ExecSpec) { - exec.executable = launcher.get().executablePath.toString() - val properties = System.getProperties().asSequence() - .map { (k, v) -> k.toString() to v.toString() } - .filter { (k, _) -> k !in this@KonanCliRunner.blacklistProperties } - .filter { (k, _) -> !k.startsWith("sun") && !k.startsWith("java") } - .escapeQuotesForWindows() - .toMap() - .toMutableMap() - properties.put("konan.home", project.kotlinNativeDist.absolutePath) - - exec.args(mutableListOf().apply { - addAll(jvmArgs) - addAll(properties.entries.map { "-D${it.key}=${it.value}" }) - add("-cp") - add(classpath.joinToString(separator = System.getProperty("path.separator"))) - add(mainClass) - addAll(listOf(toolName) + transformArgs(args)) - }) - blacklistEnvironment.forEach { environment.remove(it) } - exec.environment(environment) - exec.errorOutput = err - exec.standardOutput = out - exec.isIgnoreExitValue = true - } - }) - - check(execResult.exitValue == 0) { - """ - stdout:$out - stderr:$err + final override fun checkClasspath() = + check(classpath.isNotEmpty()) { + """ + Classpath of the tool is empty: $toolName + Probably the '${KONAN_HOME.propertyName}' project property contains an incorrect path. + Please change it to the compiler root directory and rerun the build. """.trimIndent() - } - } + } + + data class IsolatedClassLoaderCacheKey(val classpath: Set) + + // TODO: can't we use this for other implementations too? + final override val isolatedClassLoaderCacheKey get() = IsolatedClassLoaderCacheKey(classpath) + + override fun transformArgs(args: List) = listOf(toolName) + args + + final override fun getCustomJvmArgs() = additionalJvmArgs } -internal class KonanInteropRunner( - project: Project, - additionalJvmArgs: List = emptyList(), - konanHome: String = project.konanHome -) : KonanCliRunner("cinterop", "Kotlin/Native cinterop tool", project, additionalJvmArgs, konanHome) { - init { - if (HostManager.host == KonanTarget.MINGW_X64) { - //TODO: Oh-ho-ho fix it in more convinient way. - environment.put("PATH", DependencyProcessor.defaultDependenciesRoot.absolutePath + - "\\llvm-11.1.0-windows-x64" + - "\\bin;${environment.get("PATH")}") - } - } -} - -internal class KonanCompilerRunner( +/** Kotlin/Native compiler runner */ +internal class KonanCliCompilerRunner( project: Project, additionalJvmArgs: List = emptyList(), val useArgFile: Boolean = true, konanHome: String = project.konanHome -) : KonanCliRunner("konanc", "Kotlin/Native compiler", project, additionalJvmArgs, konanHome) { +) : KonanCliRunner("konanc", project, additionalJvmArgs, konanHome) { + override val mustRunViaExec get() = false + override fun transformArgs(args: List): List { - if (!useArgFile) { - return args + if (!useArgFile) return super.transformArgs(args) + + val argFile = Files.createTempFile(/* prefix = */ "konancArgs", /* suffix = */ ".lst").toFile().apply { deleteOnExit() } + argFile.printWriter().use { w -> + for (arg in args) { + val escapedArg = arg + .replace("\\", "\\\\") + .replace("\"", "\\\"") + w.println("\"$escapedArg\"") + } } - val argFile = Files.createTempFile("konancArgs", ".lst").toAbsolutePath().apply { - toFile().deleteOnExit() - } - Files.write(argFile, args) + return listOf(toolName, "@${argFile.absolutePath}") + } +} - return listOf("@${argFile}") +/** Kotlin/Native C-interop tool runner */ +internal class KonanCliInteropRunner( + project: Project, + additionalJvmArgs: List = emptyList(), + konanHome: String = project.konanHome +) : KonanCliRunner("cinterop", project, additionalJvmArgs, konanHome) { + override val mustRunViaExec get() = false + + override fun transformArgs(args: List): List { + return super.transformArgs(args) + listOf("-Xproject-dir", project.projectDir.toString()) + } + + override val execEnvironment by lazy { + val result = mutableMapOf() + result.putAll(super.execEnvironment) + result["LIBCLANG_DISABLE_CRASH_RECOVERY"] = "1" + llvmExecutablesPath?.let { + result["PATH"] = "$it;${System.getenv("PATH")}" + } + result + } + + private val llvmExecutablesPath: String? by lazy { + if (HostManager.host == KonanTarget.MINGW_X64) { + // TODO: Read it from Platform properties when it is accessible. + val konanProperties = Properties().apply { + project.file("$konanHome/konan/konan.properties").inputStream().use(::load) + } + + konanProperties.resolvablePropertyString("llvmHome.mingw_x64")?.let { toolchainDir -> + DependencyDirectories.defaultDependenciesRoot + .resolve("$toolchainDir/bin") + .absolutePath + } + } else + null } } @@ -189,4 +146,6 @@ internal class KonanKlibRunner( project: Project, additionalJvmArgs: List = emptyList(), konanHome: String = project.konanHome -) : KonanCliRunner("klib", "Klib management tool", project, additionalJvmArgs, konanHome) +) : KonanCliRunner("klib", project, additionalJvmArgs, konanHome) { + override val mustRunViaExec get() = false +} diff --git a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCacheTask.kt b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCacheTask.kt index ca9096d36d9..435e6119b22 100644 --- a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCacheTask.kt +++ b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCacheTask.kt @@ -4,7 +4,7 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.Directory import org.gradle.api.provider.Property import org.gradle.api.tasks.* -import org.jetbrains.kotlin.gradle.plugin.konan.KonanCompilerRunner +import org.jetbrains.kotlin.gradle.plugin.konan.KonanCliCompilerRunner import org.jetbrains.kotlin.gradle.plugin.konan.konanHome import org.jetbrains.kotlin.konan.library.defaultResolver import org.jetbrains.kotlin.konan.target.CompilerOutputKind @@ -79,6 +79,6 @@ open class KonanCacheTask: DefaultTask() { "-Xadd-cache=${originalKlib?.absolutePath}", "-Xcache-directory=${cacheDirectory.absolutePath}" ) + additionalCacheFlags + cachedLibraries.map { "-Xcached-library=${it.key},${it.value}" } - KonanCompilerRunner(project, konanHome = konanHome).run(args) + KonanCliCompilerRunner(project, konanHome = konanHome).run(args) } } \ No newline at end of file diff --git a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCompileTask.kt b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCompileTask.kt index 4eaf5f2dd8f..8815f6e3aa1 100644 --- a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCompileTask.kt +++ b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCompileTask.kt @@ -36,7 +36,7 @@ import java.io.File abstract class KonanCompileTask: KonanBuildingTask(), KonanCompileSpec { @get:Internal - override val toolRunner = KonanCompilerRunner(project, project.konanExtension.jvmArgs) + override val toolRunner = KonanCliCompilerRunner(project, project.konanExtension.jvmArgs) abstract val produce: CompilerOutputKind @Internal get diff --git a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCompilerDownloadTask.kt b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCompilerDownloadTask.kt index 6333aa9ece9..7af9108e4af 100644 --- a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCompilerDownloadTask.kt +++ b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanCompilerDownloadTask.kt @@ -70,7 +70,7 @@ open class KonanCompilerDownloadTask : DefaultTask() { // Download dependencies if a user said so. if (downloadDependencies) { - val runner = KonanCompilerRunner(project, project.konanExtension.jvmArgs) + val runner = KonanCliCompilerRunner(project, project.konanExtension.jvmArgs) project.konanTargets.forEach { runner.run("-Xcheck_dependencies", "-target", it.visibleName) } diff --git a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanInteropTask.kt b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanInteropTask.kt index a9e3d09051a..30f39c917eb 100644 --- a/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanInteropTask.kt +++ b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/tasks/KonanInteropTask.kt @@ -37,7 +37,7 @@ import javax.inject.Inject open class KonanInteropTask @Inject constructor(@Internal val workerExecutor: WorkerExecutor) : KonanBuildingTask(), KonanInteropSpec { @get:Internal - override val toolRunner: KonanToolRunner = KonanInteropRunner(project, project.konanExtension.jvmArgs) + override val toolRunner: KonanToolRunner = KonanCliInteropRunner(project, project.konanExtension.jvmArgs) override fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) { super.init(config, destinationDir, artifactName, target)