From c8edb171eea9338c1d8faa589aa21034d87fb9b7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 22 Nov 2025 23:12:18 -0500 Subject: [PATCH 1/2] [2.x] Adds cleanExpunge command **Problem** We might want to clear the disk cache. **Solution** This adds cleanExpunge command. --- .../main/scala/sbt/BasicCommandStrings.scala | 5 ++- main/src/main/scala/sbt/Main.scala | 28 +++--------- main/src/main/scala/sbt/internal/Clean.scala | 43 ++++++++++++++++++- .../scala/sbt/util/ActionCacheStore.scala | 4 ++ 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index c3939a958..579fd7298 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 CleanExpunge: String = "cleanExpunge" + def cleanExpungeDetailed: 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..72b1dac26 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.cleanExpunge, 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..7c869b82b 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 cleanExpunge: Command = + val h = Help.more(CleanExpunge, cleanExpungeDetailed) + 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(CleanExpunge, 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 From d1e0a5a35d0105e65b4d172307714d71c1af27c8 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 23 Nov 2025 14:45:21 -0500 Subject: [PATCH 2/2] cleanFull --- main-command/src/main/scala/sbt/BasicCommandStrings.scala | 4 ++-- main/src/main/scala/sbt/Main.scala | 2 +- main/src/main/scala/sbt/internal/Clean.scala | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index 579fd7298..386208441 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -254,8 +254,8 @@ $AliasCommand name= def ClearCaches: String = "clearCaches" def ClearCachesDetailed: String = "Clears sbt's internal caches." - val CleanExpunge: String = "cleanExpunge" - def cleanExpungeDetailed: String = "Clears sbt's local 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 72b1dac26..a32f661b7 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -351,7 +351,7 @@ object BuiltinCommands { act, continuous, clearCaches, - Clean.cleanExpunge, + Clean.cleanFull, NetworkChannel.disconnect, waitCmd, promptChannel, diff --git a/main/src/main/scala/sbt/internal/Clean.scala b/main/src/main/scala/sbt/internal/Clean.scala index 7c869b82b..9d0094ce3 100644 --- a/main/src/main/scala/sbt/internal/Clean.scala +++ b/main/src/main/scala/sbt/internal/Clean.scala @@ -214,8 +214,8 @@ private[sbt] object Clean { val help = Help.more(ClearCaches, ClearCachesDetailed) Command.command(ClearCaches, help)(clearCachesFun) - def cleanExpunge: Command = - val h = Help.more(CleanExpunge, cleanExpungeDetailed) + def cleanFull: Command = + val h = Help.more(CleanFull, cleanFullDetailed) val expunge: State => State = (s: State) => val outputDirectory = s @@ -227,5 +227,5 @@ private[sbt] object Clean { case _ => () IO.delete(outputDirectory.toFile()) s - Command.command(CleanExpunge, h)(expunge andThen clearCachesFun) + Command.command(CleanFull, h)(expunge andThen clearCachesFun) }