Skip to content

Commit

Permalink
System property to extract binaries to a different folder (#891)
Browse files Browse the repository at this point in the history
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 #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
}
```
  • Loading branch information
igordmn authored Mar 15, 2024
1 parent 4721587 commit a776361
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 5 additions & 7 deletions skiko/src/jvmMain/kotlin/org/jetbrains/skiko/Library.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -107,17 +105,17 @@ 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) {
// We made a duplicate to resolve classloader conflicts.
unpackIfNeeded(copyDir!!, icu, true)
} else {
// Normal path where Skiko is loaded only once.
unpackIfNeeded(cacheDir, icu, false)
unpackIfNeeded(dataDir, icu, false)
}
}
}
Expand Down
24 changes: 24 additions & 0 deletions skiko/src/jvmMain/kotlin/org/jetbrains/skiko/SkikoProperties.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down

0 comments on commit a776361

Please sign in to comment.