From 71da941c8b4567f00324839f3ff7dbcda24424bf Mon Sep 17 00:00:00 2001 From: Henrik Tunedal Date: Sun, 17 May 2020 21:07:03 +0200 Subject: [PATCH] Use system-specific user cache directory in main-kts #KT-38222 fixed --- .../kotlin/mainKts/test/mainKtsTest.kt | 88 +++++++++++++++++++ .../kotlin/mainKts/impl/directories.kt | 68 ++++++++++++++ .../org/jetbrains/kotlin/mainKts/scriptDef.kt | 6 +- 3 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/directories.kt diff --git a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt index 05142c10ae1..c4b6487fe10 100644 --- a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt +++ b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt @@ -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 diff --git a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/directories.kt b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/directories.kt new file mode 100644 index 00000000000..bacc11d1858 --- /dev/null +++ b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/impl/directories.kt @@ -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) +{ + // 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 +} diff --git a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt index fdbdc368349..0fd47f5fab9 100644 --- a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt +++ b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt @@ -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() +