diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index c3939a958..386208441 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -252,7 +252,10 @@ $AliasCommand name= def continuousBriefHelp: (String, String) = (ContinuousExecutePrefix + " ", continuousDetail) def ClearCaches: String = "clearCaches" - def ClearCachesDetailed: String = "Clears all of sbt's internal caches." + def ClearCachesDetailed: String = "Clears sbt's internal caches." + + val CleanFull: String = "cleanFull" + def cleanFullDetailed: String = "Clears sbt's local caches." private[sbt] val networkExecPrefix = "__" private[sbt] val DisconnectNetworkChannel = s"${networkExecPrefix}disconnectNetworkChannel" diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 6093b5578..a32f661b7 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -28,13 +28,12 @@ import sbt.internal.nio.{ CheckBuildSources, FileTreeRepository } import sbt.internal.server.{ BuildServerProtocol, NetworkChannel } import sbt.internal.util.Terminal.hasConsole import sbt.internal.util.Types.{ const, idFun } -import sbt.internal.util.complete.{ Parser, SizeParser } +import sbt.internal.util.complete.Parser import sbt.internal.util.{ Terminal as ITerminal, * } import sbt.io.* import sbt.io.syntax.* import sbt.util.{ Level, Logger, Show } import xsbti.AppProvider -import xsbti.compile.CompilerCache import scala.annotation.{ nowarn, tailrec } import scala.concurrent.ExecutionContext @@ -352,6 +351,7 @@ object BuiltinCommands { act, continuous, clearCaches, + Clean.cleanFull, NetworkChannel.disconnect, waitCmd, promptChannel, @@ -970,7 +970,7 @@ object BuiltinCommands { session, structure, s2, - st => setupGlobalFileTreeRepository(addCacheStoreFactoryFactory(st)) + st => setupGlobalFileTreeRepository(Clean.addCacheStoreFactoryFactory(st)) ) val s4 = s3.put(Keys.useLog4J.key, Project.extract(s3).get(Keys.useLog4J)) addSuperShellParams(CheckBuildSources.init(LintUnused.lintUnusedFunc(s4))) @@ -992,27 +992,9 @@ object BuiltinCommands { .put(Keys.superShellThreshold.key, threshold) .put(Keys.superShellMaxTasks.key, maxItems) } - private val addCacheStoreFactoryFactory: State => State = (s: State) => { - val size = Project - .extract(s) - .getOpt(Keys.fileCacheSize) - .flatMap(SizeParser(_)) - .getOrElse(SysProp.fileCacheSize) - s.get(Keys.cacheStoreFactoryFactory).foreach(_.close()) - s.put(Keys.cacheStoreFactoryFactory, InMemoryCacheStore.factory(size)) - } - def registerCompilerCache(s: State): State = { - s.get(Keys.stateCompilerCache).foreach(_.clear()) - s.put(Keys.stateCompilerCache, CompilerCache.fresh) - } - - def clearCaches: Command = { - val help = Help.more(ClearCaches, ClearCachesDetailed) - val f: State => State = - registerCompilerCache andThen (_.initializeClassLoaderCache) andThen addCacheStoreFactoryFactory - Command.command(ClearCaches, help)(f) - } + def registerCompilerCache(s: State): State = Clean.registerCompilerCache(s) + def clearCaches: Command = Clean.clearCaches private[sbt] def waitCmd: Command = Command.arb(_ => diff --git a/main/src/main/scala/sbt/internal/Clean.scala b/main/src/main/scala/sbt/internal/Clean.scala index b74d5d988..9d0094ce3 100644 --- a/main/src/main/scala/sbt/internal/Clean.scala +++ b/main/src/main/scala/sbt/internal/Clean.scala @@ -12,19 +12,23 @@ package internal import java.io.IOException import java.nio.file.{ DirectoryNotEmptyException, Files, Path } +import sbt.BasicCommandStrings.* import sbt.Def.* import sbt.Keys.* // import sbt.Project.richInitializeTask import sbt.ProjectExtra.* import sbt.ScopeAxis.Zero import sbt.io.syntax.* +import sbt.io.IO import sbt.nio.Keys.* import sbt.nio.file.* import sbt.nio.file.syntax.pathToPathOps import sbt.nio.file.Glob.{ GlobOps } -import sbt.util.Level +import sbt.util.{ DiskActionCacheStore, Level } +import sbt.internal.util.complete.SizeParser import sjsonnew.JsonFormat import xsbti.{ PathBasedFile, VirtualFileRef } +import xsbti.compile.CompilerCache private[sbt] object Clean { @@ -187,4 +191,41 @@ private[sbt] object Clean { debug(s"Caught unexpected exception $e deleting $path") } } + + val registerCompilerCache: State => State = (s: State) => + s.get(Keys.stateCompilerCache).foreach(_.clear()) + s.put(Keys.stateCompilerCache, CompilerCache.fresh) + + val addCacheStoreFactoryFactory: State => State = (s: State) => + val size = Project + .extract(s) + .getOpt(Keys.fileCacheSize) + .flatMap(SizeParser(_)) + .getOrElse(SysProp.fileCacheSize) + s.get(Keys.cacheStoreFactoryFactory).foreach(_.close()) + s.put(Keys.cacheStoreFactoryFactory, InMemoryCacheStore.factory(size)) + + private val clearCachesFun: State => State = + registerCompilerCache + .andThen(_.initializeClassLoaderCache) + .andThen(addCacheStoreFactoryFactory) + + def clearCaches: Command = + val help = Help.more(ClearCaches, ClearCachesDetailed) + Command.command(ClearCaches, help)(clearCachesFun) + + def cleanFull: Command = + val h = Help.more(CleanFull, cleanFullDetailed) + val expunge: State => State = + (s: State) => + val outputDirectory = s + .get(BasicKeys.rootOutputDirectory) + .getOrElse(sys.error("outputDirectory has not been set")) + val cacheStore = s.get(BasicKeys.cacheStores).getOrElse(Nil) + cacheStore.foreach: + case d: DiskActionCacheStore => d.clear() + case _ => () + IO.delete(outputDirectory.toFile()) + s + Command.command(CleanFull, h)(expunge andThen clearCachesFun) } diff --git a/util-cache/src/main/scala/sbt/util/ActionCacheStore.scala b/util-cache/src/main/scala/sbt/util/ActionCacheStore.scala index 7fb6e479f..8d23dbeae 100644 --- a/util-cache/src/main/scala/sbt/util/ActionCacheStore.scala +++ b/util-cache/src/main/scala/sbt/util/ActionCacheStore.scala @@ -192,6 +192,10 @@ class DiskActionCacheStore(base: Path, converter: FileConverter) extends Abstrac private val symlinkSupported: AtomicBoolean = AtomicBoolean(true) override def storeName: String = "disk" + + def clear(): Unit = + if Files.exists(base) then IO.delete(base.toFile()) + else () override def get(request: GetActionResultRequest): Either[Throwable, ActionResult] = val acFile = acBase.toFile / request.actionDigest.toString.replace("/", "-") if acFile.exists then