diff --git a/kotlin-native/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt b/kotlin-native/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt index b1078852683..f1466c63308 100644 --- a/kotlin-native/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt +++ b/kotlin-native/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.konan.util.KonanHomeProvider import java.io.File import java.nio.file.Files import java.nio.file.Paths +import java.security.MessageDigest private fun decodeFromUtf8(bytes: ByteArray) = String(bytes) internal fun encodeToUtf8(str: String) = str.toByteArray() @@ -102,32 +103,92 @@ inline fun Number.invalidNarrowing(): R { throw Error("unable to narrow ${this.javaClass.simpleName} \"${this}\" to ${R::class.java.simpleName}") } -fun loadKonanLibrary(name: String) { - try { - System.loadLibrary(name) - } catch (e: UnsatisfiedLinkError) { - val fullLibraryName = System.mapLibraryName(name) - val dir = "${KonanHomeProvider.determineKonanHome()}/konan/nativelib" - try { - System.load("$dir/$fullLibraryName") - } catch (e: UnsatisfiedLinkError) { - if (fullLibraryName.endsWith(".dylib") && e.message?.contains("library load disallowed by system policy") == true) { - throw UnsatisfiedLinkError(""" - |Library $dir/$fullLibraryName can't be loaded. - |${'\t'}This can happen because library file is marked as untrusted (e.g because it was downloaded from browser). - |${'\t'}You can trust libraries in distribution by running - |${'\t'}${'\t'}xattr -d com.apple.quarantine '$dir'/* - |${'\t'}command in terminal - |${'\t'}Original exception message: - |${'\t'}${e.message} - """.trimMargin()) - } - val tempDir = Files.createTempDirectory(Paths.get(dir), null).toAbsolutePath().toString() - Files.createLink(Paths.get(tempDir, fullLibraryName), Paths.get(dir, fullLibraryName)) - // TODO: Does not work on Windows. May be use FILE_FLAG_DELETE_ON_CLOSE? - File(tempDir).deleteOnExit() - File("$tempDir/$fullLibraryName").deleteOnExit() - System.load("$tempDir/$fullLibraryName") +// Ported from ClassLoader::initializePath. +private fun initializePath() = + System.getProperty("java.library.path", "") + .split(File.pathSeparatorChar) + .map { if (it == "") "." else it } + +private val sha256 = MessageDigest.getInstance("SHA-256") +private val systemTmpDir = System.getProperty("java.io.tmpdir") + +// TODO: File(..).deleteOnExit() does not work on Windows. May be use FILE_FLAG_DELETE_ON_CLOSE? +private fun tryLoadKonanLibrary(dir: String, fullLibraryName: String, runFromDaemon: Boolean): Boolean { + val fullLibraryPath = Paths.get(dir, fullLibraryName) + if (!Files.exists(fullLibraryPath)) return false + + fun createTempDirWithLibrary() = if (runFromDaemon) { + Files.createTempDirectory(null).toAbsolutePath().toString().also { + Files.copy(fullLibraryPath, Paths.get(it, fullLibraryName)) + } + } else { + Files.createTempDirectory(Paths.get(dir), null).toAbsolutePath().toString().also { + Files.createLink(Paths.get(it, fullLibraryName), fullLibraryPath) } } + + val defaultTempDir = if (!runFromDaemon) + dir + else { + // Sometimes loading library from its original place doesn't work (it gets 'half-loaded' + // with relocation table haven't been substituted by the system loader without reporting any error). + // We workaround this by copying the library to some temporary place. + // For now this behaviour have only been observed for compilations run from the Gradle daemon on Team City. + val hash = sha256.digest(Files.readAllBytes(fullLibraryPath)) + val defaultTempDirName = buildString { + append(fullLibraryName) + append('_') + hash.forEach { + val hex = it.toUByte().toString(16) + if (hex.length == 1) + append('0') + append(hex) + } + } + val defaultTempDir = Paths.get(systemTmpDir, defaultTempDirName).toAbsolutePath().toString() + val tempDir = File(createTempDirWithLibrary()) + if (tempDir.renameTo(File(defaultTempDir))) { + File(defaultTempDir).deleteOnExit() + File("$defaultTempDir/$fullLibraryName").deleteOnExit() + } else { + tempDir.deleteRecursively() + } + defaultTempDir + } + + try { + System.load("$defaultTempDir/$fullLibraryName") + } catch (e: UnsatisfiedLinkError) { + if (fullLibraryName.endsWith(".dylib") && e.message?.contains("library load disallowed by system policy") == true) { + throw UnsatisfiedLinkError(""" + |Library $dir/$fullLibraryName can't be loaded. + |${'\t'}This can happen because library file is marked as untrusted (e.g because it was downloaded from browser). + |${'\t'}You can trust libraries in distribution by running + |${'\t'}${'\t'}xattr -d com.apple.quarantine '$dir'/* + |${'\t'}command in terminal + |${'\t'}Original exception message: + |${'\t'}${e.message} + """.trimMargin()) + } + val tempDir = createTempDirWithLibrary() + + File(tempDir).deleteOnExit() + File("$tempDir/$fullLibraryName").deleteOnExit() + System.load("$tempDir/$fullLibraryName") + } + + return true +} + +fun loadKonanLibrary(name: String) { + val runFromDaemon = System.getProperty("kotlin.native.tool.runFromDaemon") == "true" + val fullLibraryName = System.mapLibraryName(name) + val paths = initializePath() + for (dir in paths) { + if (tryLoadKonanLibrary(dir, fullLibraryName, runFromDaemon)) return + } + val defaultNativeLibsDir = "${KonanHomeProvider.determineKonanHome()}/konan/nativelib" + if (tryLoadKonanLibrary(defaultNativeLibsDir, fullLibraryName, runFromDaemon)) + return + error("Lib $fullLibraryName is not found in $defaultNativeLibsDir and ${paths.joinToString { it }}") } \ 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/KonanToolRunner.kt b/kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/konan/KonanToolRunner.kt index 80c93c13000..76bb7ea44e3 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 @@ -37,6 +37,8 @@ internal interface KonanToolRunner { internal fun KonanToolRunner.run(vararg args: String) = run(args.toList()) +private const val runFromDaemonPropertyName = "kotlin.native.tool.runFromDaemon" + internal abstract class KonanCliRunner( protected val toolName: String, project: Project, @@ -48,6 +50,11 @@ internal abstract class KonanCliRunner( final override val mainClass get() = "org.jetbrains.kotlin.cli.utilities.MainKt" final override val daemonEntryPoint get() = "daemonMain" + final override val mustRunViaExec get() = false.also { System.setProperty(runFromDaemonPropertyName, "true") } + + final override val execSystemPropertiesBlacklist: Set + get() = super.execSystemPropertiesBlacklist + runFromDaemonPropertyName + // 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 -> @@ -92,8 +99,6 @@ internal class KonanCliCompilerRunner( val useArgFile: Boolean = true, konanHome: String = project.konanHome ) : KonanCliRunner("konanc", project, additionalJvmArgs, konanHome) { - override val mustRunViaExec get() = false - override fun transformArgs(args: List): List { if (!useArgFile) return super.transformArgs(args) @@ -130,8 +135,6 @@ internal class KonanCliInteropRunner( 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()) } @@ -171,6 +174,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", project, additionalJvmArgs, konanHome)