From c89f14caa11240c2c0e35b92fd3ebbca071ec8e8 Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Sun, 5 Apr 2026 00:52:29 -0400 Subject: [PATCH 1/2] [2.x] fix: Fixes pollInterval (#9020) **Problem/Solution** checkBuildSources / pollInterval should just fallback to some value. --- main/src/main/scala/sbt/Defaults.scala | 3 +-- main/src/main/scala/sbt/nio/CheckBuildSources.scala | 11 ++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index bfd867aaf..05f5d1fd9 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -420,6 +420,7 @@ object Defaults extends BuildCommon { terminal := Def.uncached(state.value.get(terminalKey).getOrElse(Terminal(ITerminal.get))), InstallSbtn.installSbtn := InstallSbtn.installSbtnImpl.evaluated, InstallSbtn.installSbtn / aggregate := false, + checkBuildSources / pollInterval :== CheckBuildSources.defaultPollInterval, ) ++ LintUnused.lintSettings ++ DefaultBackgroundJobService.backgroundJobServiceSettings ++ RemoteCache.globalSettings @@ -555,8 +556,6 @@ object Defaults extends BuildCommon { sourceManaged := target.value / "src_managed", resourceManaged := target.value / "resource_managed", // Adds subproject build.sbt files to the global list of build files to monitor - Scope.Global / checkBuildSources / pollInterval :== - new FiniteDuration(Int.MinValue, TimeUnit.MILLISECONDS), Scope.Global / checkBuildSources / fileInputs ++= { if ((Scope.Global / onChangedBuildSource).value != IgnoreSourceChanges) Seq(baseDirectory.value.toGlob / "*.sbt") diff --git a/main/src/main/scala/sbt/nio/CheckBuildSources.scala b/main/src/main/scala/sbt/nio/CheckBuildSources.scala index 7b3bde263..1a39546d3 100644 --- a/main/src/main/scala/sbt/nio/CheckBuildSources.scala +++ b/main/src/main/scala/sbt/nio/CheckBuildSources.scala @@ -10,6 +10,7 @@ package sbt package internal.nio import java.nio.file.Path +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import sbt.BasicCommandStrings.{ RebootCommand, Shutdown, TerminateAction } import sbt.Keys.{ baseDirectory, pollInterval, state } @@ -65,7 +66,9 @@ private[sbt] class CheckBuildSources extends AutoCloseable { } private def reset(state: State): Unit = { val extracted = Project.extract(state) - val interval = extracted.get(checkBuildSources / pollInterval) + val interval = extracted + .getOpt(checkBuildSources / pollInterval) + .getOrElse(CheckBuildSources.defaultPollInterval) val newSources = extracted.get(Global / checkBuildSources / fileInputs).distinct if (interval >= 0.seconds || "polling" == SysProp.watchMode) { Option(repository.getAndSet(null)).foreach(_.close()) @@ -174,7 +177,9 @@ private[sbt] class CheckBuildSources extends AutoCloseable { override def close(): Unit = {} } -private[sbt] object CheckBuildSources { +private[sbt] object CheckBuildSources: + val defaultPollInterval: FiniteDuration = FiniteDuration(Int.MinValue, TimeUnit.MILLISECONDS) + private[sbt] val CheckBuildSourcesKey = AttributeKey[CheckBuildSources]("check-build-source", "", KeyRanks.Invisible) /* @@ -219,4 +224,4 @@ private[sbt] object CheckBuildSources { projectGlobs(projectDir, baseDir.toGlob / "*.sbt" :: Nil) } else Nil } -} +end CheckBuildSources From 75edb0afe0526a84f47b93a19d98f869dde7a775 Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Sun, 5 Apr 2026 03:13:23 -0400 Subject: [PATCH 2/2] [2.0.x] fix: Fixes metabuild reloading (#9019) **Problem** There seems to be multiple problems around metabuild reloading (reload plugins). 1. Originally reported issue 9006 states that the build can't come back from reload plugins. 2. During the course of fixing, I discovered that when reload plugins is used the project name is set to "project" instead of foo-build etc. 3. Also there was a bug in the rootPaths when reload plugins is used, which results in class not defined error. **Solution** 1. Fix the plugin context so reload plugin still behaves like a metabuild. 2. Fix the rootPaths. --- main/src/main/scala/sbt/internal/Load.scala | 8 ++++--- .../scala/sbt/internal/PluginManagement.scala | 5 +++- .../metabuild/project/FooPlugin.scala | 24 +++++++++++++++++++ .../metabuild/project/project/FooPlugin.scala | 24 +++++++++++++++++++ .../src/sbt-test/project-load/metabuild/test | 9 +++++++ 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 sbt-app/src/sbt-test/project-load/metabuild/project/FooPlugin.scala create mode 100644 sbt-app/src/sbt-test/project-load/metabuild/project/project/FooPlugin.scala create mode 100644 sbt-app/src/sbt-test/project-load/metabuild/test diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 8ca8d88d2..bc7e6c040 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -73,10 +73,10 @@ private[sbt] object Load { val launcher = scalaProvider.launcher val stagingDirectory = getStagingDirectory(state, globalBase).getCanonicalFile val javaHome = Util.javaHome - val out = baseDirectory.toPath.resolve("target").resolve("out") + val out = app.baseDirectory.toPath.resolve("target").resolve("out") val rootPaths = Map( "OUT" -> out, - "BASE" -> baseDirectory.toPath, + "BASE" -> app.baseDirectory.toPath, "SBT_BOOT" -> launcher.bootDirectory.toPath, "IVY_HOME" -> launcher.ivyHome.toPath, "JAVA_HOME" -> javaHome, @@ -119,7 +119,9 @@ private[sbt] object Load { ) val evalPluginDef: (BuildStructure, State) => PluginData = EvaluateTask.evalPluginDef val delegates = defaultDelegates - val pluginMgmt = PluginManagement(loader) + import sbt.ProjectExtra.projectReturn + val pluginContext = PluginManagement.Context(false, Project.projectReturn(state).size - 1) + val pluginMgmt = PluginManagement(loader, pluginContext) val inject = InjectSettings(injectGlobal(state), Nil, const(Nil)) SysProp.setSwovalTempDir() SysProp.setIpcSocketTempDir() diff --git a/main/src/main/scala/sbt/internal/PluginManagement.scala b/main/src/main/scala/sbt/internal/PluginManagement.scala index 0a9c94980..e551b3c44 100644 --- a/main/src/main/scala/sbt/internal/PluginManagement.scala +++ b/main/src/main/scala/sbt/internal/PluginManagement.scala @@ -54,12 +54,15 @@ object PluginManagement { val emptyContext: Context = Context(false, 0) def apply(initialLoader: ClassLoader): PluginManagement = + PluginManagement(initialLoader, emptyContext) + + def apply(initialLoader: ClassLoader, context: Context): PluginManagement = PluginManagement( Set.empty, Set.empty, new PluginClassLoader(initialLoader), initialLoader, - emptyContext + context ) def extractOverrides(classpath: Classpath): Set[ModuleID] = diff --git a/sbt-app/src/sbt-test/project-load/metabuild/project/FooPlugin.scala b/sbt-app/src/sbt-test/project-load/metabuild/project/FooPlugin.scala new file mode 100644 index 000000000..5633f3307 --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/metabuild/project/FooPlugin.scala @@ -0,0 +1,24 @@ +package example + +import sbt.* +import Keys.* +import complete.DefaultParsers.{ *, given } + +object FooPlugin extends AutoPlugin: + override def requires = empty + override def trigger = allRequirements + + lazy object autoImport: + @transient + lazy val foo = taskKey[Unit]("foo") + lazy val check = inputKey[Unit]("check") + + import autoImport.* + override def projectSettings: Seq[Def.Setting[?]] = Seq( + foo := println("foo"), + check := { + val args = spaceDelimited("").parsed + assert(name.value.endsWith(args.head), s"${name.value} does not end with ${args.head}") + }, + ) +end FooPlugin diff --git a/sbt-app/src/sbt-test/project-load/metabuild/project/project/FooPlugin.scala b/sbt-app/src/sbt-test/project-load/metabuild/project/project/FooPlugin.scala new file mode 100644 index 000000000..5633f3307 --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/metabuild/project/project/FooPlugin.scala @@ -0,0 +1,24 @@ +package example + +import sbt.* +import Keys.* +import complete.DefaultParsers.{ *, given } + +object FooPlugin extends AutoPlugin: + override def requires = empty + override def trigger = allRequirements + + lazy object autoImport: + @transient + lazy val foo = taskKey[Unit]("foo") + lazy val check = inputKey[Unit]("check") + + import autoImport.* + override def projectSettings: Seq[Def.Setting[?]] = Seq( + foo := println("foo"), + check := { + val args = spaceDelimited("").parsed + assert(name.value.endsWith(args.head), s"${name.value} does not end with ${args.head}") + }, + ) +end FooPlugin diff --git a/sbt-app/src/sbt-test/project-load/metabuild/test b/sbt-app/src/sbt-test/project-load/metabuild/test new file mode 100644 index 000000000..68aec72a2 --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/metabuild/test @@ -0,0 +1,9 @@ +> foo +> reload plugins +> check -build +> foo +> reload plugins +> reload return +> reload return +> compile +> foo