From a77636166813a8149c98d44943487e7346646aed Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Fri, 15 Mar 2024 12:33:21 +0100 Subject: [PATCH] System property to extract binaries to a different folder (#891) Skiko extracts binaries to `~/.skiko` by default, but it is not always possible. This PR adds a way to override the folder where to extract binaries: ``` System.setProperty("skiko.data.path", File(System.getProperty("java.io.tmpdir")).resolve(".skiko").toString()) ``` Fixes https://github.com/JetBrains/skiko/issues/885 The case seems rare - we only have a crash in tests reported. If we have reports from real users, we have to change the default `~/.skiko` to something else. ## Testing 1 (manual) 1. Run ``` import org.jetbrains.skia.Bitmap import java.io.File fun main() { val path = File(System.getProperty("java.io.tmpdir")).resolve(".skiko").toString() println(path) System.setProperty("skiko.data.path", path) Bitmap() // loads the library } ``` 2. See that `path` is created ## Testing 2 (manual) The default way works: ``` import org.jetbrains.skia.Bitmap fun main() { Bitmap() // loads the library } ``` --- .../skiko/RenderExceptionsHandler.kt | 2 +- .../skiko/SeveralClassloadersTest.kt | 18 ++++---------- .../kotlin/org/jetbrains/skiko/Library.kt | 12 ++++------ .../org/jetbrains/skiko/SkikoProperties.kt | 24 +++++++++++++++++++ 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/RenderExceptionsHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/RenderExceptionsHandler.kt index 3fefdfb9c..7d36c2d54 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/RenderExceptionsHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/RenderExceptionsHandler.kt @@ -12,7 +12,7 @@ internal class RenderExceptionsHandler { fun throwException(message: String) { if (output == null) { output = File( - "${Library.cacheRoot}/skiko-render-exception-${ProcessHandle.current().pid()}.log" + "${SkikoProperties.dataPath}/skiko-render-exception-${ProcessHandle.current().pid()}.log" ) } val exception = RenderException(message) diff --git a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SeveralClassloadersTest.kt b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SeveralClassloadersTest.kt index ce4cfc693..bee4b49f2 100644 --- a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SeveralClassloadersTest.kt +++ b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SeveralClassloadersTest.kt @@ -71,31 +71,21 @@ private fun newInstance(loader: ClassLoader, fqName: String, vararg args: Any): class SeveralClassloadersTest { @Test fun `load skiko in several classloaders (with skiko path)`() { - check(skikoLibraryPath != null) + check(SkikoProperties.libraryPath != null) doTest() } @Test fun `load skiko in several classloaders (without skiko path)`() { - val oldValue = skikoLibraryPath!! - skikoLibraryPath = null + val oldValue = SkikoProperties.libraryPath!! + SkikoProperties.libraryPath = null try { doTest() } finally { - skikoLibraryPath = oldValue + SkikoProperties.libraryPath = oldValue } } - private var skikoLibraryPath: String? - get() = System.getProperty(Library.SKIKO_LIBRARY_PATH_PROPERTY) - set(value) { - if (value != null) { - System.setProperty(Library.SKIKO_LIBRARY_PATH_PROPERTY, value) - } else { - System.clearProperty(Library.SKIKO_LIBRARY_PATH_PROPERTY) - } - } - private fun doTest() { val threaded = false val jar = System.getProperty("skiko.jar.path") diff --git a/skiko/src/jvmMain/kotlin/org/jetbrains/skiko/Library.kt b/skiko/src/jvmMain/kotlin/org/jetbrains/skiko/Library.kt index bfa920116..435d58929 100644 --- a/skiko/src/jvmMain/kotlin/org/jetbrains/skiko/Library.kt +++ b/skiko/src/jvmMain/kotlin/org/jetbrains/skiko/Library.kt @@ -7,9 +7,6 @@ import java.nio.file.StandardCopyOption import java.util.concurrent.atomic.AtomicBoolean object Library { - internal const val SKIKO_LIBRARY_PATH_PROPERTY = "skiko.library.path" - internal val cacheRoot = "${System.getProperty("user.home")}/.skiko/" - private val skikoLibraryPath = System.getProperty(SKIKO_LIBRARY_PATH_PROPERTY) private var copyDir: File? = null // A native library cannot be loaded in several classloaders, so we have to clone @@ -80,6 +77,7 @@ object Library { } // First try: system property is set. + val skikoLibraryPath = SkikoProperties.libraryPath if (skikoLibraryPath != null) { val library = File(File(skikoLibraryPath), platformName) loadLibraryOrCopy(library) @@ -107,9 +105,9 @@ object Library { ) val hash = hashResourceStream.use { it.bufferedReader().readLine() } - val cacheDir = File(File(cacheRoot), hash) - cacheDir.mkdirs() - val library = unpackIfNeeded(cacheDir, platformName, false) + val dataDir = File(File(SkikoProperties.dataPath), hash) + dataDir.mkdirs() + val library = unpackIfNeeded(dataDir, platformName, false) loadLibraryOrCopy(library) if (icu != null) { if (copyDir != null) { @@ -117,7 +115,7 @@ object Library { unpackIfNeeded(copyDir!!, icu, true) } else { // Normal path where Skiko is loaded only once. - unpackIfNeeded(cacheDir, icu, false) + unpackIfNeeded(dataDir, icu, false) } } } diff --git a/skiko/src/jvmMain/kotlin/org/jetbrains/skiko/SkikoProperties.kt b/skiko/src/jvmMain/kotlin/org/jetbrains/skiko/SkikoProperties.kt index 010f8b48c..09c51d16a 100644 --- a/skiko/src/jvmMain/kotlin/org/jetbrains/skiko/SkikoProperties.kt +++ b/skiko/src/jvmMain/kotlin/org/jetbrains/skiko/SkikoProperties.kt @@ -8,6 +8,30 @@ import java.lang.System.getProperty * Global Skiko properties, which are read from system JDK variables orr from environment variables */ object SkikoProperties { + /** + * Path where the Skiko binaries (dll/so/dylib, depending on OS) are placed. + * + * If defined, SKiko doesn't extract binaries from `jar` files to external folder. + * + * If null (default), it extracts them to `libraryCachePath` + */ + var libraryPath: String? + get() = getProperty("skiko.library.path") + internal set(value) { + if (value != null) { + System.setProperty("skiko.library.path", value) + } else { + System.clearProperty("skiko.library.path") + } + } + + /** + * The path where to store data files. + * + * It is used for extracting the Skiko binaries (if `libraryPath` isn't null) and logging. + */ + val dataPath: String get() = getProperty("skiko.data.path") ?: "${getProperty("user.home")}/.skiko/" + val vsyncEnabled: Boolean get() = getProperty("skiko.vsync.enabled")?.toBoolean() ?: true /**