From bd7a2e05bfa1f6ac9ad07e8ae76758dabacd27fa Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 22 Nov 2020 14:35:20 -0500 Subject: [PATCH] Use `Global / localCacheDirectory` for remote caching Fixes https://github.com/sbt/sbt/issues/6102 https://github.com/sbt/sbt/pull/6026 changed the implementation of remote cache to NOT use dependency resolution (Coursier), and directly use Ivy resolver for efficiency. This was good, but when I made the change, I've changed the cache directory to be `crossTarget.value / "remote-cache"`. This was ok for local testing purpose, but not great for real usage since we don't want the cache to be wiped out either in the CI machines or on a local laptop. This adds a new Global key called `localCacheDirectory`. Similar to Coursier cache, this is meant to be shared across all builds running on a machine. Also similar to Coursier cache this will try to follow the operating system specifc caching directory. ### localCacheDirectory location - Environment variable: `SBT_LOCAL_CACHE` - System property: `sbt.global.localcache` - Windows: %LOCALAPPDATA%\sbt\v1 - macOS: $HOME/Library/Caches/sbt/v1 - Linux: $HOME/.cache/sbt/v1 --- .../main/scala/sbt/internal/util/Util.scala | 3 ++ main/src/main/scala/sbt/Keys.scala | 1 + .../main/scala/sbt/internal/RemoteCache.scala | 13 +++++- .../src/main/scala/sbt/internal/SysProp.scala | 40 ++++++++++++++++++- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala index 5274b6083..06afd24a7 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala @@ -47,6 +47,9 @@ object Util { def ignoreResult[T](f: => T): Unit = macro Macro.ignore + lazy val isMac: Boolean = + System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac") + lazy val isWindows: Boolean = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows") diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 14b3d54dc..3e45c51b3 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -388,6 +388,7 @@ object Keys { val pushRemoteCacheTo = settingKey[Option[Resolver]]("The resolver to publish remote cache to.") val remoteCacheResolvers = settingKey[Seq[Resolver]]("Resolvers for remote cache.") val remoteCachePom = taskKey[File]("Generates a pom for publishing when publishing Maven-style.") + val localCacheDirectory = settingKey[File]("Directory to pull the remote cache to.") val usePipelining = settingKey[Boolean]("Use subproject pipelining for compilation.").withRank(BSetting) val exportPipelining = settingKey[Boolean]("Product early output so downstream subprojects can do pipelining.").withRank(BSetting) diff --git a/main/src/main/scala/sbt/internal/RemoteCache.scala b/main/src/main/scala/sbt/internal/RemoteCache.scala index be3b29b82..a6d017b37 100644 --- a/main/src/main/scala/sbt/internal/RemoteCache.scala +++ b/main/src/main/scala/sbt/internal/RemoteCache.scala @@ -50,10 +50,19 @@ object RemoteCache { .toList .map(_.take(commitLength)) + lazy val defaultCacheLocation: File = SysProp.globalLocalCache + lazy val globalSettings: Seq[Def.Setting[_]] = Seq( remoteCacheId := "", remoteCacheIdCandidates := Nil, - pushRemoteCacheTo :== None + pushRemoteCacheTo :== None, + localCacheDirectory :== defaultCacheLocation, + pushRemoteCache / ivyPaths := { + val app = appConfiguration.value + val base = app.baseDirectory.getCanonicalFile + // base is used only to resolve relative paths, which should never happen + IvyPaths(base, localCacheDirectory.value), + }, ) lazy val projectSettings: Seq[Def.Setting[_]] = (Seq( @@ -114,7 +123,7 @@ object RemoteCache { remoteCacheResolvers := pushRemoteCacheTo.value.toVector, ) ++ inTask(pushRemoteCache)( Seq( - ivyPaths := IvyPaths(baseDirectory.value, crossTarget.value / "remote-cache"), + ivyPaths := (Scope.Global / pushRemoteCache / ivyPaths).value, ivyConfiguration := { val config0 = Classpaths.mkIvyConfiguration.value config0 diff --git a/main/src/main/scala/sbt/internal/SysProp.scala b/main/src/main/scala/sbt/internal/SysProp.scala index 8ce0bb4b8..86bf9a0b6 100644 --- a/main/src/main/scala/sbt/internal/SysProp.scala +++ b/main/src/main/scala/sbt/internal/SysProp.scala @@ -8,13 +8,15 @@ package sbt package internal +import java.io.File import java.util.Locale import scala.util.control.NonFatal import scala.concurrent.duration._ -import sbt.internal.util.{ Terminal => ITerminal } +import sbt.internal.util.{ Terminal => ITerminal, Util } import sbt.internal.util.complete.SizeParser import sbt.nio.Keys._ +import sbt.io.syntax._ // See also BuildPaths.scala // See also LineReader.scala @@ -176,4 +178,40 @@ object SysProp { } } + private[this] def file(value: String): File = new File(value) + private[this] def home: File = file(sys.props("user.home")) + + /** Operating system specific cache directory, similar to Coursier cache. + */ + def globalLocalCache: File = { + val appName = "sbt" + def propCacheDir: Option[File] = sys.props.get("sbt.global.localcache").map(file) + def propCacheDir2: Option[File] = + sys.props.get(BuildPaths.GlobalBaseProperty) match { + case Some(base) => Some(file(base) / "cache") + case _ => None + } + def envCacheDir: Option[File] = sys.env.get("SBT_LOCAL_CACHE").map(file) + def windowsCacheDir: Option[File] = + sys.env.get("LOCALAPPDATA") match { + case Some(app) if Util.isWindows => Some(file(app) / appName) + case _ => None + } + def macCacheDir: Option[File] = + if (Util.isMac) Some(home / "Library" / "Caches" / appName) + else None + def linuxCache: File = + sys.env.get("XDG_CACHE_HOME") match { + case Some(cache) => file(cache) / appName + case _ => home / ".cache" / appName + } + def baseCache: File = + propCacheDir + .orElse(propCacheDir2) + .orElse(envCacheDir) + .orElse(windowsCacheDir) + .orElse(macCacheDir) + .getOrElse(linuxCache) + baseCache.getAbsoluteFile / "v1" + } }