[K/N][gradle][interop] Workaround for loading readonly native libs
This commit is contained in:
@@ -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 <reified R : Number> 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 }}")
|
||||
}
|
||||
+8
-7
@@ -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<String>
|
||||
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<String> by lazy {
|
||||
HashSet<String>().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<String>): List<String> {
|
||||
if (!useArgFile) return super.transformArgs(args)
|
||||
|
||||
@@ -130,8 +135,6 @@ internal class KonanCliInteropRunner(
|
||||
additionalJvmArgs: List<String> = emptyList(),
|
||||
konanHome: String = project.konanHome
|
||||
) : KonanCliRunner("cinterop", project, additionalJvmArgs, konanHome) {
|
||||
override val mustRunViaExec get() = false
|
||||
|
||||
override fun transformArgs(args: List<String>): List<String> {
|
||||
return super.transformArgs(args) + listOf("-Xproject-dir", project.projectDir.toString())
|
||||
}
|
||||
@@ -171,6 +174,4 @@ internal class KonanKlibRunner(
|
||||
project: Project,
|
||||
additionalJvmArgs: List<String> = emptyList(),
|
||||
konanHome: String = project.konanHome
|
||||
) : KonanCliRunner("klib", project, additionalJvmArgs, konanHome) {
|
||||
override val mustRunViaExec get() = false
|
||||
}
|
||||
) : KonanCliRunner("klib", project, additionalJvmArgs, konanHome)
|
||||
|
||||
Reference in New Issue
Block a user