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 5ed1a198419..065125554d9 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,125 +16,172 @@ 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 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 +import java.io.ByteArrayOutputStream + +internal interface KonanToolRunner : Named { + val mainClass: String + val classpath: FileCollection + val jvmArgs: List + val environment: Map -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( - protected val toolName: String, - project: Project, - val additionalJvmArgs: List = emptyList(), - val konanHome: String = project.konanHome -) : KotlinToolRunner(project), KonanToolRunner { - final override val displayName get() = toolName + 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" - final override val mainClass get() = "org.jetbrains.kotlin.cli.utilities.MainKt" - final override val daemonEntryPoint get() = "daemonMain" + override fun getName() = toolName // We need to unset some environment variables which are set by XCode and may potentially affect the tool executed. - 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 blacklistEnvironment: List by lazy { + KonanPlugin::class.java.getResourceAsStream("/env_blacklist")?.let { stream -> + stream.reader().use { it.readLines() } + } ?: emptyList() } - final override val execSystemProperties by lazy { mapOf("konan.home" to konanHome) } + 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 classpath by lazy { project.fileTree("$konanHome/konan/lib/").apply { include("*.jar") }.files } + override val classpath: FileCollection = + project.fileTree("$konanHome/konan/lib/") + .apply { include("*.jar") } - 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() + 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 } - data class IsolatedClassLoaderCacheKey(val classpath: Set) + open protected fun transformArgs(args: List): List = args - // TODO: can't we use this for other implementations too? - final override val isolatedClassLoaderCacheKey get() = IsolatedClassLoaderCacheKey(classpath) + 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.") + } - override fun transformArgs(args: List) = listOf(toolName) + args + @Suppress("UNCHECKED_CAST") + val launcher = project.getProperty(KonanPlugin.ProjectProperty.KONAN_JVM_LAUNCHER) as? Provider + ?: throw IllegalStateException("Missing property: ${KonanPlugin.ProjectProperty.KONAN_JVM_LAUNCHER}") - final override fun getCustomJvmArgs() = additionalJvmArgs + 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 + """.trimIndent() + } + } } -/** Kotlin/Native compiler runner */ -internal class KonanCliCompilerRunner( +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( project: Project, additionalJvmArgs: List = emptyList(), val useArgFile: Boolean = true, konanHome: String = project.konanHome -) : KonanCliRunner("konanc", project, additionalJvmArgs, konanHome) { - override val mustRunViaExec get() = false - +) : KonanCliRunner("konanc", "Kotlin/Native compiler", project, additionalJvmArgs, konanHome) { override fun transformArgs(args: List): List { - 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\"") - } + if (!useArgFile) { + return args } - return listOf(toolName, "@${argFile.absolutePath}") - } -} - -/** 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 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")}" + val argFile = Files.createTempFile("konancArgs", ".lst").toAbsolutePath().apply { + toFile().deleteOnExit() } - result - } + Files.write(argFile, args) - 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 + return listOf("@${argFile}") } } @@ -142,6 +189,4 @@ internal class KonanKlibRunner( project: Project, additionalJvmArgs: List = emptyList(), konanHome: String = project.konanHome -) : KonanCliRunner("klib", project, additionalJvmArgs, konanHome) { - override val mustRunViaExec get() = false -} +) : KonanCliRunner("klib", "Klib management tool", project, additionalJvmArgs, konanHome) 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 435e6119b22..ca9096d36d9 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.KonanCliCompilerRunner +import org.jetbrains.kotlin.gradle.plugin.konan.KonanCompilerRunner 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}" } - KonanCliCompilerRunner(project, konanHome = konanHome).run(args) + KonanCompilerRunner(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 8815f6e3aa1..4eaf5f2dd8f 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 = KonanCliCompilerRunner(project, project.konanExtension.jvmArgs) + override val toolRunner = KonanCompilerRunner(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 7af9108e4af..6333aa9ece9 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 = KonanCliCompilerRunner(project, project.konanExtension.jvmArgs) + val runner = KonanCompilerRunner(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 30f39c917eb..a9e3d09051a 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 = KonanCliInteropRunner(project, project.konanExtension.jvmArgs) + override val toolRunner: KonanToolRunner = KonanInteropRunner(project, project.konanExtension.jvmArgs) override fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) { super.init(config, destinationDir, artifactName, target)