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" + } }