From c37b7a155586420654f011f8f21a21fa07ef0bed Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 20 Sep 2024 04:00:03 -0400 Subject: [PATCH 1/4] feat: Non-blocking run **Problem** `run` currently blocks all other commands, such as BSP commands. **Solution** `run` no longer blocks the command execution loop. Instead it pauses the prompt on the current command channel. --- .../scala/sbt/internal/CommandChannel.scala | 4 +++ .../main/scala/sbt/BackgroundJobService.scala | 2 ++ main/src/main/scala/sbt/Defaults.scala | 23 +++++++++----- main/src/main/scala/sbt/Keys.scala | 4 +-- .../main/scala/sbt/internal/Aggregation.scala | 6 +++- .../scala/sbt/internal/CommandExchange.scala | 12 ++++--- .../DefaultBackgroundJobService.scala | 31 ++++++++++++++++--- run/src/main/scala/sbt/Run.scala | 11 +++++++ 8 files changed, 72 insertions(+), 21 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/CommandChannel.scala b/main-command/src/main/scala/sbt/internal/CommandChannel.scala index 49f139a93..7d9eb94d1 100644 --- a/main-command/src/main/scala/sbt/internal/CommandChannel.scala +++ b/main-command/src/main/scala/sbt/internal/CommandChannel.scala @@ -101,6 +101,10 @@ abstract class CommandChannel { } private[sbt] def terminal: Terminal + private[sbt] var _active: Boolean = true + private[sbt] def pause(): Unit = _active = false + private[sbt] def isPaused: Boolean = !_active + private[sbt] def resume(): Unit = _active = true } // case class Exec(commandLine: String, source: Option[CommandSource]) diff --git a/main/src/main/scala/sbt/BackgroundJobService.scala b/main/src/main/scala/sbt/BackgroundJobService.scala index a9189d334..c0ec87e55 100644 --- a/main/src/main/scala/sbt/BackgroundJobService.scala +++ b/main/src/main/scala/sbt/BackgroundJobService.scala @@ -86,6 +86,8 @@ abstract class BackgroundJobService extends Closeable { hashContents: Boolean, converter: FileConverter, ): Classpath = copyClasspath(products, full, workingDirectory, converter) + + private[sbt] def pauseChannelDuringJob(state: State, handle: JobHandle): Unit } object BackgroundJobService { diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 41ac5bfcc..29e7ad2ac 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2145,21 +2145,28 @@ object Defaults extends BuildCommon { } } - // runMain calls bgRunMain in the background and waits for the result. - def foregroundRunMainTask: Initialize[InputTask[Unit]] = - Def.inputTask[Unit] { + // `runMain` calls bgRunMain in the background and pauses the current channel + def foregroundRunMainTask: Initialize[InputTask[RunVoid]] = + Def.inputTask { val handle = bgRunMain.evaluated val service = bgJobService.value - service.waitForTry(handle).get - () + val st = state.value + st.remainingCommands match + case Nil => service.waitForTry(handle).get + case _ => service.pauseChannelDuringJob(st, handle) + RunVoid } - // run calls bgRun in the background and waits for the result. - def foregroundRunTask: Initialize[InputTask[Unit]] = + // `run` task calls bgRun in the background and pauses the current channel + def foregroundRunTask: Initialize[InputTask[RunVoid]] = Def.inputTask { val handle = bgRun.evaluated val service = bgJobService.value - service.waitForTry(handle).get + val st = state.value + st.remainingCommands match + case Nil => service.waitForTry(handle).get + case _ => service.pauseChannelDuringJob(st, handle) + RunVoid } def runMainTask( diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 8da19e030..7109141ee 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -319,8 +319,8 @@ object Keys { // Run Keys val selectMainClass = taskKey[Option[String]]("Selects the main class to run.").withRank(BMinusTask) val mainClass = taskKey[Option[String]]("Defines the main class for packaging or running.").withRank(BPlusTask) - val run = inputKey[Unit]("Runs a main class, passing along arguments provided on the command line.").withRank(APlusTask) - val runMain = inputKey[Unit]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(ATask) + val run = inputKey[RunVoid]("Runs a main class, passing along arguments provided on the command line.").withRank(APlusTask) + val runMain = inputKey[RunVoid]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(ATask) val discoveredMainClasses = taskKey[Seq[String]]("Auto-detects main classes.").withRank(BMinusTask) val runner = taskKey[ScalaRun]("Implementation used to run a main class.").withRank(DTask) val trapExit = settingKey[Boolean]("If true, enables exit trapping and thread management for 'run'-like tasks. This was removed in sbt 1.6.0 due to JDK 17 deprecating Security Manager.").withRank(CSetting) diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index 289f0ff62..8ff941992 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -79,10 +79,14 @@ object Aggregation { val success = results match case Result.Value(_) => true case Result.Inc(_) => false + // run task ends earlier than the program run + val isRunVoid = results match + case Result.Value(Seq(KeyValue(_, RunVoid))) => true + case _ => false results.toEither.foreach { r => if show.taskValues then printSettings(r, show.print) else () } - if show.success && !state.get(suppressShow).getOrElse(false) then + if show.success && !isRunVoid && !state.get(suppressShow).getOrElse(false) then printSuccess(start, stop, extracted, success, cacheSummary, log) else () diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 637270ef3..8569bb474 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -146,10 +146,11 @@ private[sbt] final class CommandExchange { } private def addConsoleChannel(): Unit = - if (!Terminal.startedByRemoteClient) { + if Terminal.startedByRemoteClient then () + else val name = ConsoleChannel.defaultName subscribe(new ConsoleChannel(name, mkAskUser(name))) - } + def run(s: State): State = run(s, s.get(autoStartServer).getOrElse(true)) def run(s: State, autoStart: Boolean): State = { if (autoStartServerSysProp && autoStart) runServer(s) @@ -376,13 +377,14 @@ private[sbt] final class CommandExchange { private[sbt] def setExec(exec: Option[Exec]): Unit = currentExecRef.set(exec.orNull) - def prompt(event: ConsolePromptEvent): Unit = { + def prompt(event: ConsolePromptEvent): Unit = currentExecRef.set(null) channels.foreach { case c if ContinuousCommands.isInWatch(lastState.get, c) => - case c => c.prompt(event) + case c => + if c.isPaused then () + else c.prompt(event) } - } def unprompt(event: ConsoleUnpromptEvent): Unit = channels.foreach(_.unprompt(event)) def logMessage(event: LogEvent): Unit = { diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index e1b6179a9..555b90df9 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -24,7 +24,7 @@ import sbt.internal.inc.classpath.ClasspathFilter import sbt.internal.util.{ Attributed, ManagedLogger } import sbt.io.syntax._ import sbt.io.{ Hash, IO } -import sbt.util.Logger +import sbt.util.{ Level, Logger } import scala.concurrent.ExecutionContext import scala.concurrent.duration._ @@ -79,6 +79,9 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe private val nextId = new AtomicLong(1) private val pool = new BackgroundThreadPool() private val context = LoggerContext() + // EC for onStop handler below + given ExecutionContext = + ExecutionContext.fromExecutor(pool.executor) private[sbt] def serviceTempDirBase: File private[sbt] def useLog4J: Boolean @@ -119,10 +122,6 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe val workingDirectory: File, val job: BackgroundJob ) extends AbstractJobHandle { - // EC for onStop handler below - implicit val executionContext: ExecutionContext = - ExecutionContext.fromExecutor(pool.executor) - def humanReadableName: String = job.humanReadableName job.onStop { () => @@ -306,6 +305,28 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe converter: FileConverter, ): Classpath = copyClasspath(products, full, workingDirectory, hashFileContents = true, converter) + + private[sbt] def pauseChannelDuringJob(state: State, handle: JobHandle): Unit = + currentChannel(state) match + case Some(channel) => + val level = channel.logLevel + channel.setLevel(Level.Error) + channel.pause() + handle match + case t: ThreadJobHandle => + t.job.onStop: () => + channel.setLevel(level) + channel.resume() + channel.prompt(ConsolePromptEvent(state)) + case _ => () + case _ => () + + private[sbt] def currentChannel(state: State): Option[CommandChannel] = + state.currentCommand match + case Some(e: Exec) if e.source.isDefined => + val source = e.source.get + StandardMain.exchange.channelForName(source.channelName) + case _ => None } private[sbt] object BackgroundThreadPool { diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index 56297b484..f2f841c4e 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -212,3 +212,14 @@ object Run: case str => str }).mkString(" ") end Run + +/** + * RunVoid is a special `Unit` type used to indicate that it's a `run` task. + * When a task returns RunVoid, [success] log is omitted. + */ +sealed trait RunVoid + +/** + * The only resident of RunVoid type. + */ +case object RunVoid extends RunVoid From 1f71332edcdd54924a1e7586d0228cbbf5ebe227 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 21 Sep 2024 01:07:27 -0400 Subject: [PATCH 2/4] runBlock --- main/src/main/scala/sbt/BackgroundJobService.scala | 2 ++ main/src/main/scala/sbt/Defaults.scala | 14 ++++++++++---- main/src/main/scala/sbt/Keys.scala | 5 +++-- main/src/main/scala/sbt/internal/Aggregation.scala | 8 ++++---- run/src/main/scala/sbt/Run.scala | 11 ----------- sbt-app/src/sbt-test/actions/add-alias/build.sbt | 4 ++-- .../src/sbt-test/classloader-cache/jni/build.sbt | 4 ++-- .../dependency-management/aar-packaging/test | 2 +- .../exclude-dependencies2/test | 2 +- .../sbt-test/dependency-management/profiles/test | 2 +- .../dependency-management/stdlib-unfreeze/test | 4 ++-- .../dependency-management/url-no-head/test | 2 +- sbt-app/src/sbt-test/package/resources/test | 4 ++-- .../sbt-test/source-dependencies/constants/test | 8 ++++---- .../source-dependencies/implicit-search/build.sbt | 3 +-- .../source-dependencies/implicit-search/test | 4 ++-- .../sbt-test/source-dependencies/java-basic/test | 4 ++-- .../source-dependencies/linearization/test | 4 ++-- .../src/sbt-test/source-dependencies/named/test | 4 ++-- .../sbt-test/source-dependencies/specialized/test | 4 ++-- 20 files changed, 46 insertions(+), 49 deletions(-) diff --git a/main/src/main/scala/sbt/BackgroundJobService.scala b/main/src/main/scala/sbt/BackgroundJobService.scala index c0ec87e55..973ebe075 100644 --- a/main/src/main/scala/sbt/BackgroundJobService.scala +++ b/main/src/main/scala/sbt/BackgroundJobService.scala @@ -110,3 +110,5 @@ abstract class JobHandle { def humanReadableName: String def spawningTask: ScopedKey[_] } + +case class RunInfo(handle: JobHandle) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 29e7ad2ac..ee3088346 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1054,6 +1054,12 @@ object Defaults extends BuildCommon { }, runMain := foregroundRunMainTask.evaluated, run := foregroundRunTask.evaluated, + runBlock := { + val r = run.evaluated + val service = bgJobService.value + service.waitForTry(r.handle).get + r + }, fgRun := runTask(fullClasspath, (run / mainClass), (run / runner)).evaluated, fgRunMain := runMainTask(fullClasspath, (run / runner)).evaluated, copyResources := copyResourcesTask.value, @@ -2146,7 +2152,7 @@ object Defaults extends BuildCommon { } // `runMain` calls bgRunMain in the background and pauses the current channel - def foregroundRunMainTask: Initialize[InputTask[RunVoid]] = + def foregroundRunMainTask: Initialize[InputTask[RunInfo]] = Def.inputTask { val handle = bgRunMain.evaluated val service = bgJobService.value @@ -2154,11 +2160,11 @@ object Defaults extends BuildCommon { st.remainingCommands match case Nil => service.waitForTry(handle).get case _ => service.pauseChannelDuringJob(st, handle) - RunVoid + RunInfo(handle) } // `run` task calls bgRun in the background and pauses the current channel - def foregroundRunTask: Initialize[InputTask[RunVoid]] = + def foregroundRunTask: Initialize[InputTask[RunInfo]] = Def.inputTask { val handle = bgRun.evaluated val service = bgJobService.value @@ -2166,7 +2172,7 @@ object Defaults extends BuildCommon { st.remainingCommands match case Nil => service.waitForTry(handle).get case _ => service.pauseChannelDuringJob(st, handle) - RunVoid + RunInfo(handle) } def runMainTask( diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 7109141ee..bfa5b7225 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -319,8 +319,9 @@ object Keys { // Run Keys val selectMainClass = taskKey[Option[String]]("Selects the main class to run.").withRank(BMinusTask) val mainClass = taskKey[Option[String]]("Defines the main class for packaging or running.").withRank(BPlusTask) - val run = inputKey[RunVoid]("Runs a main class, passing along arguments provided on the command line.").withRank(APlusTask) - val runMain = inputKey[RunVoid]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(ATask) + val run = inputKey[RunInfo]("Runs a main class, passing along arguments provided on the command line.").withRank(APlusTask) + val runBlock = inputKey[RunInfo]("Runs a main class, and blocks until it's done.").withRank(DTask) + val runMain = inputKey[RunInfo]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(ATask) val discoveredMainClasses = taskKey[Seq[String]]("Auto-detects main classes.").withRank(BMinusTask) val runner = taskKey[ScalaRun]("Implementation used to run a main class.").withRank(DTask) val trapExit = settingKey[Boolean]("If true, enables exit trapping and thread management for 'run'-like tasks. This was removed in sbt 1.6.0 due to JDK 17 deprecating Security Manager.").withRank(CSetting) diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index 8ff941992..7fb75371e 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -80,13 +80,13 @@ object Aggregation { case Result.Value(_) => true case Result.Inc(_) => false // run task ends earlier than the program run - val isRunVoid = results match - case Result.Value(Seq(KeyValue(_, RunVoid))) => true - case _ => false + val isRunInfo = results match + case Result.Value(Seq(KeyValue(_, RunInfo(_)))) => true + case _ => false results.toEither.foreach { r => if show.taskValues then printSettings(r, show.print) else () } - if show.success && !isRunVoid && !state.get(suppressShow).getOrElse(false) then + if show.success && !isRunInfo && !state.get(suppressShow).getOrElse(false) then printSuccess(start, stop, extracted, success, cacheSummary, log) else () diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index f2f841c4e..56297b484 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -212,14 +212,3 @@ object Run: case str => str }).mkString(" ") end Run - -/** - * RunVoid is a special `Unit` type used to indicate that it's a `run` task. - * When a task returns RunVoid, [success] log is omitted. - */ -sealed trait RunVoid - -/** - * The only resident of RunVoid type. - */ -case object RunVoid extends RunVoid diff --git a/sbt-app/src/sbt-test/actions/add-alias/build.sbt b/sbt-app/src/sbt-test/actions/add-alias/build.sbt index 089e388c1..c8673eaea 100644 --- a/sbt-app/src/sbt-test/actions/add-alias/build.sbt +++ b/sbt-app/src/sbt-test/actions/add-alias/build.sbt @@ -1,3 +1,3 @@ -addCommandAlias("demo-success", "run true") -addCommandAlias("demo-failure", "run false") +addCommandAlias("demo-success", "runBlock true") +addCommandAlias("demo-failure", "runBlock false") addCommandAlias("z", "scalaVersion") diff --git a/sbt-app/src/sbt-test/classloader-cache/jni/build.sbt b/sbt-app/src/sbt-test/classloader-cache/jni/build.sbt index e9449cc48..e92d9b23d 100644 --- a/sbt-app/src/sbt-test/classloader-cache/jni/build.sbt +++ b/sbt-app/src/sbt-test/classloader-cache/jni/build.sbt @@ -7,7 +7,7 @@ val dropLibraryPath = taskKey[Unit]("Drop the last path from the java.library.pa val wrappedRun = taskKey[Unit]("Run with modified java.library.path") val wrappedTest = taskKey[Unit]("Test with modified java.library.path") -def wrap(task: InputKey[Unit]): Def.Initialize[Task[Unit]] = +def wrap[A1](task: InputKey[A1]): Def.Initialize[Task[Unit]] = Def.sequential(appendToLibraryPath, task.toTask(""), dropLibraryPath) // ThisBuild / turbo := true @@ -35,6 +35,6 @@ val root = (project in file(".")).settings( val cp = System.getProperty("java.library.path", "").split(":").dropRight(1) System.setProperty("java.library.path", cp.mkString(":")) }, - wrappedRun := wrap(Runtime / run).value, + wrappedRun := wrap(Runtime / runBlock).value, wrappedTest := wrap(Test / testOnly).value ) diff --git a/sbt-app/src/sbt-test/dependency-management/aar-packaging/test b/sbt-app/src/sbt-test/dependency-management/aar-packaging/test index 2182f57b0..5efe58689 100644 --- a/sbt-app/src/sbt-test/dependency-management/aar-packaging/test +++ b/sbt-app/src/sbt-test/dependency-management/aar-packaging/test @@ -1,3 +1,3 @@ $ delete output -> run +> runBlock $ exists output diff --git a/sbt-app/src/sbt-test/dependency-management/exclude-dependencies2/test b/sbt-app/src/sbt-test/dependency-management/exclude-dependencies2/test index 2182f57b0..5efe58689 100644 --- a/sbt-app/src/sbt-test/dependency-management/exclude-dependencies2/test +++ b/sbt-app/src/sbt-test/dependency-management/exclude-dependencies2/test @@ -1,3 +1,3 @@ $ delete output -> run +> runBlock $ exists output diff --git a/sbt-app/src/sbt-test/dependency-management/profiles/test b/sbt-app/src/sbt-test/dependency-management/profiles/test index 2182f57b0..5efe58689 100644 --- a/sbt-app/src/sbt-test/dependency-management/profiles/test +++ b/sbt-app/src/sbt-test/dependency-management/profiles/test @@ -1,3 +1,3 @@ $ delete output -> run +> runBlock $ exists output diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze/test b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze/test index 7df93ab6c..8984f6503 100644 --- a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze/test +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze/test @@ -1,12 +1,12 @@ > a/checkLibs > b/checkLibs -> b/run +> b/runBlock $ exists s2.13.8.txt $ delete s2.13.8.txt # don't crash when expanding the macro -> b3/run +> b3/runBlock $ exists s2.13.10.txt $ delete s2.13.10.txt diff --git a/sbt-app/src/sbt-test/dependency-management/url-no-head/test b/sbt-app/src/sbt-test/dependency-management/url-no-head/test index 2182f57b0..5efe58689 100644 --- a/sbt-app/src/sbt-test/dependency-management/url-no-head/test +++ b/sbt-app/src/sbt-test/dependency-management/url-no-head/test @@ -1,3 +1,3 @@ $ delete output -> run +> runBlock $ exists output diff --git a/sbt-app/src/sbt-test/package/resources/test b/sbt-app/src/sbt-test/package/resources/test index 27ee7a65d..83139bf77 100644 --- a/sbt-app/src/sbt-test/package/resources/test +++ b/sbt-app/src/sbt-test/package/resources/test @@ -4,7 +4,7 @@ # This should fail because the Main object is in package jartest and the resource is directly # in src/main/resources --> run +-> runBlock > package @@ -18,7 +18,7 @@ $ copy-file src/main/resources/main_resource_test src/main/resources/jartest/mai $ delete src/main/resources/main_resource_test # This should succeed because sbt should put the resource on the runClasspath -> run +> runBlock # This is necessary because package bases whether or not to run on last modified times, which don't have # high enough resolution to notice the above move of main_resource_test diff --git a/sbt-app/src/sbt-test/source-dependencies/constants/test b/sbt-app/src/sbt-test/source-dependencies/constants/test index ff8815d85..9d448cea2 100644 --- a/sbt-app/src/sbt-test/source-dependencies/constants/test +++ b/sbt-app/src/sbt-test/source-dependencies/constants/test @@ -3,14 +3,14 @@ $ copy-file changes/B.scala B.scala $ copy-file changes/A1.scala A.scala -> run 1 +> runBlock 1 $ copy-file changes/A2.scala A.scala -> run 2 +> runBlock 2 > clean > ++2.13.12! $ copy-file changes/A1.scala A.scala -> run 1 +> runBlock 1 $ copy-file changes/A2.scala A.scala -> run 2 +> runBlock 2 diff --git a/sbt-app/src/sbt-test/source-dependencies/implicit-search/build.sbt b/sbt-app/src/sbt-test/source-dependencies/implicit-search/build.sbt index 07fe33830..8f5047976 100644 --- a/sbt-app/src/sbt-test/source-dependencies/implicit-search/build.sbt +++ b/sbt-app/src/sbt-test/source-dependencies/implicit-search/build.sbt @@ -1,2 +1 @@ -ThisBuild / scalaVersion := "2.12.17" - +scalaVersion := "2.12.19" diff --git a/sbt-app/src/sbt-test/source-dependencies/implicit-search/test b/sbt-app/src/sbt-test/source-dependencies/implicit-search/test index 18d69f6b8..d7d7190b5 100644 --- a/sbt-app/src/sbt-test/source-dependencies/implicit-search/test +++ b/sbt-app/src/sbt-test/source-dependencies/implicit-search/test @@ -2,10 +2,10 @@ $ copy-file changes/A1.scala A.scala $ copy-file changes/B.scala B.scala $ copy-file changes/C.scala C.scala > compile --> run +-> runBlock $ copy-file changes/A2.scala A.scala $ sleep 1000 > compile -> run +> runBlock diff --git a/sbt-app/src/sbt-test/source-dependencies/java-basic/test b/sbt-app/src/sbt-test/source-dependencies/java-basic/test index 9dac40735..04a3d604d 100644 --- a/sbt-app/src/sbt-test/source-dependencies/java-basic/test +++ b/sbt-app/src/sbt-test/source-dependencies/java-basic/test @@ -36,10 +36,10 @@ $ delete src/main/java/a/A.java # It shouldn't run though, because it doesn't have a main method $ copy-file changes/B1.java src/main/java/a/b/B.java > compile --> run +-> runBlock # Replace B with a new B that has a main method and should therefore run # if the main method was properly detected $ copy-file changes/B3.java src/main/java/a/b/B.java -> run \ No newline at end of file +> runBlock diff --git a/sbt-app/src/sbt-test/source-dependencies/linearization/test b/sbt-app/src/sbt-test/source-dependencies/linearization/test index 22f17664a..f6c002d7c 100644 --- a/sbt-app/src/sbt-test/source-dependencies/linearization/test +++ b/sbt-app/src/sbt-test/source-dependencies/linearization/test @@ -1,7 +1,7 @@ > compile # the value of F.x should be 16 -> run 16 +> runBlock 16 # modify D.scala so that the linearization changes $ copy-file changes/D.scala D.scala @@ -12,4 +12,4 @@ $ sleep 1000 # if F is recompiled, the value of x should be 11, otherwise it will still be 16 # and this will fail -> run 11 \ No newline at end of file +> runBlock 11 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/source-dependencies/named/test b/sbt-app/src/sbt-test/source-dependencies/named/test index 28f1c58d3..626ddc2f4 100644 --- a/sbt-app/src/sbt-test/source-dependencies/named/test +++ b/sbt-app/src/sbt-test/source-dependencies/named/test @@ -4,7 +4,7 @@ > compile # result should be 1 -> run 1 +> runBlock 1 # change order of arguments in A.x $ copy-file changes/A.scala A.scala @@ -13,4 +13,4 @@ $ copy-file changes/A.scala A.scala > compile # Should still get 1 and not -1 -> run 1 +> runBlock 1 diff --git a/sbt-app/src/sbt-test/source-dependencies/specialized/test b/sbt-app/src/sbt-test/source-dependencies/specialized/test index b9e1ad09c..76c2120a8 100644 --- a/sbt-app/src/sbt-test/source-dependencies/specialized/test +++ b/sbt-app/src/sbt-test/source-dependencies/specialized/test @@ -2,7 +2,7 @@ > compile # verify that erased A.x can be called normally and reflectively -> run false +> runBlock false # make A.x specialized $ copy-file changes/A.scala A.scala @@ -12,4 +12,4 @@ $ copy-file changes/A.scala A.scala # verify that specialized A.x can be called normally and reflectively # NOTE: this test doesn't actually work correctly: have to check the output to see that B.scala was recompiled -> run true \ No newline at end of file +> runBlock true \ No newline at end of file From 683b09afa87321f23a34ad87f4a5dfadaf646133 Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Tue, 1 Oct 2024 00:46:52 -0400 Subject: [PATCH 3/4] Update main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala Co-authored-by: adpi2 --- .../scala/sbt/internal/DefaultBackgroundJobService.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index 555b90df9..3bbfe77d0 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -309,11 +309,11 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe private[sbt] def pauseChannelDuringJob(state: State, handle: JobHandle): Unit = currentChannel(state) match case Some(channel) => - val level = channel.logLevel - channel.setLevel(Level.Error) - channel.pause() handle match case t: ThreadJobHandle => + val level = channel.logLevel + channel.setLevel(Level.Error) + channel.pause() t.job.onStop: () => channel.setLevel(level) channel.resume() From 8aa740574b91d670919df50663218688cc7eac27 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 1 Oct 2024 03:15:28 -0400 Subject: [PATCH 4/4] Move suspendChannel to EvaluateTask --- .../main/scala/sbt/BackgroundJobService.scala | 7 ++++++- main/src/main/scala/sbt/Defaults.scala | 18 ++++-------------- main/src/main/scala/sbt/EvaluateTask.scala | 11 +++++++++++ main/src/main/scala/sbt/Keys.scala | 6 +++--- .../main/scala/sbt/internal/Aggregation.scala | 18 +++++++++++++----- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/main/src/main/scala/sbt/BackgroundJobService.scala b/main/src/main/scala/sbt/BackgroundJobService.scala index 973ebe075..e268b96ce 100644 --- a/main/src/main/scala/sbt/BackgroundJobService.scala +++ b/main/src/main/scala/sbt/BackgroundJobService.scala @@ -111,4 +111,9 @@ abstract class JobHandle { def spawningTask: ScopedKey[_] } -case class RunInfo(handle: JobHandle) +/** + * This datatype is used signal the task engine or the commands + * that the background job is emulated to be a foreground job on + * the originating channel. + */ +case class EmulateForeground(handle: JobHandle) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index ee3088346..05fa8d9fd 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2152,27 +2152,17 @@ object Defaults extends BuildCommon { } // `runMain` calls bgRunMain in the background and pauses the current channel - def foregroundRunMainTask: Initialize[InputTask[RunInfo]] = + def foregroundRunMainTask: Initialize[InputTask[EmulateForeground]] = Def.inputTask { val handle = bgRunMain.evaluated - val service = bgJobService.value - val st = state.value - st.remainingCommands match - case Nil => service.waitForTry(handle).get - case _ => service.pauseChannelDuringJob(st, handle) - RunInfo(handle) + EmulateForeground(handle) } // `run` task calls bgRun in the background and pauses the current channel - def foregroundRunTask: Initialize[InputTask[RunInfo]] = + def foregroundRunTask: Initialize[InputTask[EmulateForeground]] = Def.inputTask { val handle = bgRun.evaluated - val service = bgJobService.value - val st = state.value - st.remainingCommands match - case Nil => service.waitForTry(handle).get - case _ => service.pauseChannelDuringJob(st, handle) - RunInfo(handle) + EmulateForeground(handle) } def runMainTask( diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 3e1dabea6..b9f6be65b 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -512,6 +512,16 @@ object EvaluateTask { case Some(t: Task[?]) => transformNode(t).isEmpty case _ => true } + def suspendChannel[A1]( + state: State, + result: Result[A1] + ): Unit = + (state.getSetting(Global / Keys.bgJobService), result) match + case (Some(service), Result.Value(List(KeyValue(_, EmulateForeground(handle))))) => + state.remainingCommands match + case Nil => service.waitForTry(handle).get + case _ => service.pauseChannelDuringJob(state, handle) + case _ => () def run() = { val x = new Execute( Execute.config(config.checkCycles, overwriteNode), @@ -529,6 +539,7 @@ object EvaluateTask { } finally shutdown() val replaced = transformInc(result) logIncResult(replaced, state, streams) + suspendChannel(newState, replaced) (newState, replaced) } object runningEngine extends RunningTaskEngine { diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index bfa5b7225..c34311b5b 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -319,9 +319,9 @@ object Keys { // Run Keys val selectMainClass = taskKey[Option[String]]("Selects the main class to run.").withRank(BMinusTask) val mainClass = taskKey[Option[String]]("Defines the main class for packaging or running.").withRank(BPlusTask) - val run = inputKey[RunInfo]("Runs a main class, passing along arguments provided on the command line.").withRank(APlusTask) - val runBlock = inputKey[RunInfo]("Runs a main class, and blocks until it's done.").withRank(DTask) - val runMain = inputKey[RunInfo]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(ATask) + val run = inputKey[EmulateForeground]("Runs a main class, passing along arguments provided on the command line.").withRank(APlusTask) + val runBlock = inputKey[EmulateForeground]("Runs a main class, and blocks until it's done.").withRank(DTask) + val runMain = inputKey[EmulateForeground]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(ATask) val discoveredMainClasses = taskKey[Seq[String]]("Auto-detects main classes.").withRank(BMinusTask) val runner = taskKey[ScalaRun]("Implementation used to run a main class.").withRank(DTask) val trapExit = settingKey[Boolean]("If true, enables exit trapping and thread management for 'run'-like tasks. This was removed in sbt 1.6.0 due to JDK 17 deprecating Security Manager.").withRank(CSetting) diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index 7fb75371e..690788ce3 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -79,17 +79,25 @@ object Aggregation { val success = results match case Result.Value(_) => true case Result.Inc(_) => false - // run task ends earlier than the program run - val isRunInfo = results match - case Result.Value(Seq(KeyValue(_, RunInfo(_)))) => true - case _ => false + val isPaused = currentChannel(state) match + case Some(channel) => channel.isPaused + case None => false results.toEither.foreach { r => if show.taskValues then printSettings(r, show.print) else () } - if show.success && !isRunInfo && !state.get(suppressShow).getOrElse(false) then + if !isPaused && show.success && !state.get(suppressShow).getOrElse(false) then printSuccess(start, stop, extracted, success, cacheSummary, log) else () + private def currentChannel(state: State): Option[CommandChannel] = + state.currentCommand match + case Some(exec) => + exec.source match + case Some(source) => + StandardMain.exchange.channels.find(_.name == source.channelName) + case _ => None + case _ => None + def timedRun[A]( s: State, ts: Values[Task[A]],