[K/N][gradle][interop] Workaround for loading readonly native libs

This commit is contained in:
Igor Chevdar
2021-12-20 12:56:21 +05:00
parent 7a4ef76527
commit f724b5c29d
2 changed files with 95 additions and 33 deletions
@@ -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 }}")
}