refactor: pass zincDir and Logger to deleteZincBridgeSecondaryCache

Addresses review: avoid heavy State stubs in tests; resolve zinc directory
at the reboot-dev call site only.
This commit is contained in:
bitloi 2026-04-12 07:33:38 +02:00
parent b1a6a1dea7
commit 2ed6c7e7d6
No known key found for this signature in database
GPG Key ID: DB743C90C0FE29BA
2 changed files with 23 additions and 148 deletions

View File

@ -77,7 +77,10 @@ private[sbt] object MainLoop:
case e: RebootCurrent =>
deleteLastLog(logBacking)
deleteCurrentArtifacts(state)
deleteZincBridgeSecondaryCache(state)
deleteZincBridgeSecondaryCache(
state.log,
BuildPaths.getZincDirectory(state, BuildPaths.getGlobalBase(state)),
)
throw new xsbti.FullReload(e.arguments.toArray, false)
case NonFatal(e) =>
System.err.println(
@ -111,12 +114,11 @@ private[sbt] object MainLoop:
}
}
/** Removes the Zinc compiler bridge secondary cache (`…/zinc/org.scala-sbt`). */
private[sbt] def deleteZincBridgeSecondaryCache(state: State): Unit =
val zincDir = BuildPaths.getZincDirectory(state, BuildPaths.getGlobalBase(state))
/** Removes the Zinc compiler bridge secondary cache (`zincDir/org.scala-sbt`). */
private[sbt] def deleteZincBridgeSecondaryCache(log: Logger, zincDir: File): Unit =
val bridgeCache = zincDir / SbtArtifacts.Organization
if bridgeCache.exists() then
state.log.info(s"deleting $bridgeCache")
log.info(s"deleting $bridgeCache")
IO.delete(bridgeCache)
/** Runs the next sequence of commands with global logging in place. */

View File

@ -9,168 +9,41 @@
package sbt
import java.io.File
import java.util.concurrent.Callable
import sbt.internal.util.{ AttributeMap, ConsoleOut, GlobalLogging, MainAppender }
import sbt.internal.util.{ ConsoleOut, GlobalLogging, MainAppender }
import sbt.io.IO
import sbt.io.syntax.*
import sbt.librarymanagement.SbtArtifacts
import xsbti.{
AppConfiguration,
AppMain,
AppProvider,
ApplicationID as XApplicationID,
ComponentProvider,
CrossValue,
GlobalLock,
Launcher,
Repository,
ScalaProvider,
}
import sbt.util.Logger
object MainLoopZincCacheTest extends verify.BasicTestSuite:
private object Stubs:
val NoGlobalLock: GlobalLock = new GlobalLock:
def apply[T](lockFile: File, run: Callable[T]): T = run.call()
lazy val componentProvider: ComponentProvider = new ComponentProvider:
def componentLocation(id: String): File = new File(id)
def component(id: String): Array[File] = Array.empty
def defineComponent(id: String, jars: Array[File]): Unit = ()
def addToComponent(id: String, jars: Array[File]): Boolean = false
def lockFile(): File = new File(System.getProperty("java.io.tmpdir"), "stub-components.lock")
lazy val launcher: Launcher = new Launcher:
def getScala(version: String): ScalaProvider = Stubs.scalaProvider
def getScala(version: String, reason: String): ScalaProvider = Stubs.scalaProvider
def getScala(version: String, reason: String, scalaOrg: String): ScalaProvider =
Stubs.scalaProvider
def app(id: XApplicationID, version: String): AppProvider = Stubs.appProvider
def topLoader(): ClassLoader = classOf[String].getClassLoader
def globalLock(): GlobalLock = NoGlobalLock
def bootDirectory(): File = new File(System.getProperty("java.io.tmpdir"))
def ivyRepositories(): Array[Repository] = Array.empty
def appRepositories(): Array[Repository] = Array.empty
def isOverrideRepositories: Boolean = false
def ivyHome(): File = new File(System.getProperty("java.io.tmpdir"))
def checksums(): Array[String] = Array.empty
lazy val scalaProvider: ScalaProvider = new ScalaProvider:
def launcher(): Launcher = Stubs.launcher
def version(): String = "3.8.3"
def loader(): ClassLoader = classOf[String].getClassLoader
def jars(): Array[File] = Array.empty
def libraryJar(): File = new File("scala-library.jar")
def compilerJar(): File = new File("scala-compiler.jar")
def app(id: XApplicationID): AppProvider = Stubs.appProvider
val appId: sbt.ApplicationID = sbt.ApplicationID(
"org.scala-sbt",
"sbt",
"2.0.0",
"sbt.xMain",
Seq.empty,
CrossValue.Disabled,
Seq.empty,
)
lazy val appProvider: AppProvider = new AppProvider:
def scalaProvider(): ScalaProvider = Stubs.scalaProvider
def id(): XApplicationID = appId
def loader(): ClassLoader = classOf[String].getClassLoader
def mainClass(): Class[? <: AppMain] = classOf[xMain]
def entryPoint(): Class[?] = classOf[xMain]
def newMain(): AppMain = new xMain()
def mainClasspath(): Array[File] = Array.empty
def components(): ComponentProvider = Stubs.componentProvider
def appConfiguration(baseDir: File): AppConfiguration = new AppConfiguration:
def arguments(): Array[String] = Array.empty
def baseDirectory(): File = baseDir
def provider(): AppProvider = Stubs.appProvider
end Stubs
private def mkState(zincRoot: File, logFile: File, baseDir: File) =
val attrs = AttributeMap.empty
.put(BuildPaths.globalBaseDirectory, zincRoot.getParentFile)
.put(BuildPaths.globalZincDirectory, zincRoot)
State(
configuration = Stubs.appConfiguration(baseDir),
definedCommands = Nil,
exitHooks = Set.empty,
onFailure = None,
remainingCommands = Nil,
history = State.newHistory,
attributes = attrs,
globalLogging = GlobalLogging.initial(
private def withTestLog[A](f: Logger => A): A =
val logFile = File.createTempFile("sbt-mlz", ".log")
try
val gl = GlobalLogging.initial(
MainAppender.globalDefault(ConsoleOut.globalProxy),
logFile,
ConsoleOut.globalProxy
),
currentCommand = None,
next = State.Continue
)
)
f(gl.full)
finally IO.delete(logFile)
test("deleteZincBridgeSecondaryCache removes org.scala-sbt under global zinc"):
test("deleteZincBridgeSecondaryCache removes org.scala-sbt under zincDir"):
IO.withTemporaryDirectory: tmp =>
val zincRoot = tmp / "zinc"
val bridge = zincRoot / SbtArtifacts.Organization
IO.write(bridge / "marker.txt", "cached")
val logFile = File.createTempFile("sbt-mlz", ".log")
try
MainLoop.deleteZincBridgeSecondaryCache(mkState(zincRoot, logFile, tmp))
assert(!bridge.exists(), s"expected $bridge deleted")
finally IO.delete(logFile)
withTestLog: log =>
MainLoop.deleteZincBridgeSecondaryCache(log, zincRoot)
assert(!bridge.exists(), s"expected $bridge deleted")
test("deleteZincBridgeSecondaryCache is a no-op when org.scala-sbt is absent"):
IO.withTemporaryDirectory: tmp =>
val zincRoot = tmp / "zinc"
IO.createDirectory(zincRoot)
val logFile = File.createTempFile("sbt-mlz", ".log")
try
MainLoop.deleteZincBridgeSecondaryCache(mkState(zincRoot, logFile, tmp))
assert(zincRoot.exists())
finally IO.delete(logFile)
test("deleteZincBridgeSecondaryCache respects sbt.global.zinc system property"):
IO.withTemporaryDirectory: customZinc =>
val prop = BuildPaths.GlobalZincProperty
val prev = sys.props.get(prop)
try
sys.props(prop) = customZinc.getAbsolutePath
val bridge = customZinc / SbtArtifacts.Organization
IO.write(bridge / "x.jar", Array.emptyByteArray)
val logFile = File.createTempFile("sbt-mlz", ".log")
try
val attrs = AttributeMap.empty.put(
BuildPaths.globalBaseDirectory,
customZinc.getParentFile / "unused-base"
)
val state = State(
configuration = Stubs.appConfiguration(customZinc.getParentFile),
definedCommands = Nil,
exitHooks = Set.empty,
onFailure = None,
remainingCommands = Nil,
history = State.newHistory,
attributes = attrs,
globalLogging = GlobalLogging.initial(
MainAppender.globalDefault(ConsoleOut.globalProxy),
logFile,
ConsoleOut.globalProxy
),
currentCommand = None,
next = State.Continue
)
MainLoop.deleteZincBridgeSecondaryCache(state)
assert(!bridge.exists())
finally IO.delete(logFile)
finally
prev match
case Some(v) => sys.props(prop) = v
case None => sys.props.remove(prop)
withTestLog: log =>
MainLoop.deleteZincBridgeSecondaryCache(log, zincRoot)
assert(zincRoot.exists())
end MainLoopZincCacheTest