Use system-specific user cache directory in main-kts

#KT-38222 fixed
This commit is contained in:
Henrik Tunedal
2020-05-17 21:07:03 +02:00
committed by Ilya Chernikov
parent 8f80cf5664
commit 71da941c8b
3 changed files with 160 additions and 2 deletions
@@ -5,11 +5,13 @@
package org.jetbrains.kotlin.mainKts.test
import org.jetbrains.kotlin.mainKts.COMPILED_SCRIPTS_CACHE_DIR_PROPERTY
import org.jetbrains.kotlin.mainKts.impl.Directories
import org.jetbrains.kotlin.mainKts.MainKtsScript
import org.junit.Assert
import org.junit.Test
import java.io.*
import java.net.URLClassLoader
import java.util.*
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.baseClassLoader
@@ -173,6 +175,92 @@ class MainKtsTest {
}.lines()
}
class CacheDirectoryDetectorTest {
private val temp = "/test-temp-dir"
private val home = "/test-home-dir"
private val localAppData = "C:\\test-local-app-data"
private val xdgCache = "/test-xdg-cache-dir"
@Test
fun `Windows uses local app data dir`() {
setOSName("Windows 10")
assertCacheDir(localAppData)
}
@Test
fun `Windows falls back to temp dir when no app data dir`() {
setOSName("Windows 10")
environment.remove("LOCALAPPDATA")
assertCacheDir(temp)
}
@Test
fun `OS X uses user cache dir`() {
setOSName("Mac OS X")
assertCacheDir("$home/Library/Caches")
}
@Test
fun `Linux uses XDG cache dir`() {
setOSName("Linux")
assertCacheDir(xdgCache)
}
@Test
fun `Linux falls back to dot cache when no XDG dir`() {
setOSName("Linux")
environment.remove("XDG_CACHE_HOME")
assertCacheDir("$home/.cache")
}
@Test
fun `FreeBSD uses XDG cache dir`() {
setOSName("FreeBSD")
assertCacheDir(xdgCache)
}
@Test
fun `FreeBSD falls back to dot cache when no XDG dir`() {
setOSName("FreeBSD")
environment.remove("XDG_CACHE_HOME")
assertCacheDir("$home/.cache")
}
@Test
fun `Unknown OS uses dot cache`() {
setOSName("")
assertCacheDir("$home/.cache")
}
@Test
fun `Unknown OS and unknown home directory gives null`() {
setOSName("")
systemProperties.setProperty("user.home", "")
assertCacheDir(null)
}
private fun setOSName(name: String?) {
systemProperties.setProperty("os.name", name)
}
private fun assertCacheDir(path: String?) {
val file = path?.let(::File)
Assert.assertEquals(file, directories.cache)
}
private val systemProperties = Properties().apply {
setProperty("java.io.tmpdir", temp)
setProperty("user.home", home)
}
private val environment = mutableMapOf(
"LOCALAPPDATA" to localAppData,
"XDG_CACHE_HOME" to xdgCache
)
private val directories = Directories(systemProperties, environment)
}
internal fun captureOut(body: () -> Unit): String {
val outStream = ByteArrayOutputStream()
val prevOut = System.out
@@ -0,0 +1,68 @@
package org.jetbrains.kotlin.mainKts.impl
import java.io.File
import java.util.*
class Directories(
private val systemProperties: Properties,
private val environment: Map<String, String>)
{
// Links to recommendations for storing various kinds of files on different platforms:
// Windows: http://www.microsoft.com/security/portal/mmpc/shared/variables.aspx
// unix (freedesktop): http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
// OS X: https://developer.apple.com/library/mac/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html
//
// Note that the temp directory must not be used on Unix, as it is shared between users.
val cache: File?
get() = when (os) {
OSKind.Windows -> getEnv("LOCALAPPDATA")?.toFile() ?: temp
OSKind.OSX -> userHome?.resolve("Library/Caches")
OSKind.Unix -> getEnv("XDG_CACHE_HOME")?.toFile() ?: userHome?.resolve(".cache")
OSKind.Unknown -> userHome?.resolve(".cache")
}
private val userHome: File?
get() = getProperty("user.home")?.toFile()
private val temp: File?
get() = getProperty("java.io.tmpdir")?.toFile()
private enum class OSKind {
Windows,
OSX,
Unix,
Unknown
}
// OS detection based on
// compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/FileSystemUtils.kt
// which in turn is based on: http://www.code4copy.com/java/post/detecting-os-type-in-java
private val os: OSKind
get() = getProperty("os.name")?.toLowerCase().let { name ->
when {
name == null -> OSKind.Unknown
name.startsWith("windows") -> OSKind.Windows
name.startsWith("mac os") -> OSKind.OSX
name.contains("unix") -> OSKind.Unix
name.startsWith("linux") -> OSKind.Unix
name.contains("bsd") -> OSKind.Unix
name.startsWith("irix") -> OSKind.Unix
name.startsWith("mpe/ix") -> OSKind.Unix
name.startsWith("aix") -> OSKind.Unix
name.startsWith("hp-ux") -> OSKind.Unix
name.startsWith("sunos") -> OSKind.Unix
name.startsWith("sun os") -> OSKind.Unix
name.startsWith("solaris") -> OSKind.Unix
else -> OSKind.Unknown
}
}
private fun getProperty(name: String) = systemProperties.getProperty(name).nullIfBlank()
private fun getEnv(name: String) = environment[name].nullIfBlank()
private fun String.toFile() = File(this)
private fun String?.nullIfBlank() = if (this == null || this.isBlank()) null else this
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.mainKts
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.mainKts.impl.Directories
import org.jetbrains.kotlin.mainKts.impl.IvyResolver
import java.io.File
import java.nio.ByteBuffer
@@ -72,8 +73,8 @@ class MainKtsHostConfiguration : ScriptingHostConfiguration(
val cacheExtSetting = System.getProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY)
?: System.getenv(COMPILED_SCRIPTS_CACHE_DIR_ENV_VAR)
val cacheBaseDir = when {
cacheExtSetting == null -> System.getProperty("java.io.tmpdir")
?.let(::File)?.takeIf { it.exists() && it.isDirectory }
cacheExtSetting == null -> Directories(System.getProperties(), System.getenv()).cache
?.takeIf { it.exists() && it.isDirectory }
?.let { File(it, "main.kts.compiled.cache").apply { mkdir() } }
cacheExtSetting.isBlank() -> null
else -> File(cacheExtSetting)
@@ -173,3 +174,4 @@ private fun compiledScriptUniqueName(script: SourceCode, scriptCompilationConfig
private fun ByteArray.toHexString(): String = joinToString("", transform = { "%02x".format(it) })
private fun Int.toByteArray() = ByteBuffer.allocate(Int.SIZE_BYTES).also { it.putInt(this) }.array()