From 64c7071ff291aef8259628b9cce151885aed7858 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sat, 9 Apr 2022 15:12:20 -0700 Subject: [PATCH 1/5] Move on-termination test --- sbt-app/src/sbt-test/watch/{on-termination => on-error}/build.sbt | 0 .../watch/{on-termination => on-error}/project/Build.scala | 0 sbt-app/src/sbt-test/watch/{on-termination => on-error}/test | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename sbt-app/src/sbt-test/watch/{on-termination => on-error}/build.sbt (100%) rename sbt-app/src/sbt-test/watch/{on-termination => on-error}/project/Build.scala (100%) rename sbt-app/src/sbt-test/watch/{on-termination => on-error}/test (100%) diff --git a/sbt-app/src/sbt-test/watch/on-termination/build.sbt b/sbt-app/src/sbt-test/watch/on-error/build.sbt similarity index 100% rename from sbt-app/src/sbt-test/watch/on-termination/build.sbt rename to sbt-app/src/sbt-test/watch/on-error/build.sbt diff --git a/sbt-app/src/sbt-test/watch/on-termination/project/Build.scala b/sbt-app/src/sbt-test/watch/on-error/project/Build.scala similarity index 100% rename from sbt-app/src/sbt-test/watch/on-termination/project/Build.scala rename to sbt-app/src/sbt-test/watch/on-error/project/Build.scala diff --git a/sbt-app/src/sbt-test/watch/on-termination/test b/sbt-app/src/sbt-test/watch/on-error/test similarity index 100% rename from sbt-app/src/sbt-test/watch/on-termination/test rename to sbt-app/src/sbt-test/watch/on-error/test From ca7c872e27609bb54c0c6478b02fae2fab31f57d Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sat, 9 Apr 2022 15:05:54 -0700 Subject: [PATCH 2/5] Restore watchOnTermination At some point the watchOnTermination callback stopped working. I'm not exactly sure how or why that happened but it is fairly straightforward to restore. The one tricky thing was that the callback has the signature (Watch.Action, _, _, _) => State, which requires propagating the action to the failWatch command. The easiest way to do this was to add a mutable field to the ContinuousState. This is rather ugly and reflects some poor design choices but a more comprehensive refactor is out of the scope of this fix. This commit adds a scripted test that ensures that the callback is invoked both in the successful and unsuccessful watch cases. In each case the callback deletes a file and we ensure that the file is indeed absent after the watch exits. --- .../main/scala/sbt/internal/Continuous.scala | 68 +++++++++++++------ .../src/sbt-test/watch/on-termination/bar.txt | 0 .../sbt-test/watch/on-termination/build.sbt | 15 ++++ .../src/sbt-test/watch/on-termination/foo.txt | 0 .../src/sbt-test/watch/on-termination/test | 8 +++ 5 files changed, 70 insertions(+), 21 deletions(-) create mode 100644 sbt-app/src/sbt-test/watch/on-termination/bar.txt create mode 100644 sbt-app/src/sbt-test/watch/on-termination/build.sbt create mode 100644 sbt-app/src/sbt-test/watch/on-termination/foo.txt create mode 100644 sbt-app/src/sbt-test/watch/on-termination/test diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index c206cb71e..68c2d4a87 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -1128,7 +1128,28 @@ private[sbt] object Continuous extends DeprecatedContinuous { val callbacks: Callbacks, val dynamicInputs: mutable.Set[DynamicInput], val pending: Boolean, + var failAction: Option[Watch.Action], ) { + def this( + count: Int, + commands: Seq[String], + beforeCommandImpl: (State, mutable.Set[DynamicInput]) => State, + afterCommand: State => State, + afterWatch: State => State, + callbacks: Callbacks, + dynamicInputs: mutable.Set[DynamicInput], + pending: Boolean, + ) = this( + count, + commands, + beforeCommandImpl, + afterCommand, + afterWatch, + callbacks, + dynamicInputs, + pending, + None + ) def beforeCommand(state: State): State = beforeCommandImpl(state, dynamicInputs) def incremented: ContinuousState = withCount(count + 1) def withPending(p: Boolean) = @@ -1323,7 +1344,8 @@ private[sbt] object ContinuousCommands { case Watch.Prompt => stop.map(_ :: s"$PromptChannel ${channel.name}" :: Nil mkString ";") case Watch.Run(commands) => stop.map(_ +: commands.map(_.commandLine).filter(_.nonEmpty) mkString "; ") - case Watch.HandleError(_) => + case a @ Watch.HandleError(_) => + cs.failAction = Some(a) stop.map(_ :: s"$failWatch ${channel.name}" :: Nil mkString "; ") case _ => stop } @@ -1353,27 +1375,31 @@ private[sbt] object ContinuousCommands { } cs.afterCommand(postState) } - private[sbt] val stopWatchCommand = watchCommand(stopWatch) { (channel, state) => - state.get(watchStates).flatMap(_.get(channel)) match { - case Some(cs) => - val afterWatchState = cs.afterWatch(state) - cs.callbacks.onExit() - StandardMain.exchange - .channelForName(channel) - .foreach { c => - c.terminal.setPrompt(Prompt.Pending) - c.unprompt(ConsoleUnpromptEvent(Some(CommandSource(channel)))) + private[this] val exitWatchShared = (error: Boolean) => + (channel: String, state: State) => + state.get(watchStates).flatMap(_.get(channel)) match { + case Some(cs) => + val afterWatchState = cs.afterWatch(state) + cs.callbacks.onExit() + StandardMain.exchange + .channelForName(channel) + .foreach { c => + c.terminal.setPrompt(Prompt.Pending) + c.unprompt(ConsoleUnpromptEvent(Some(CommandSource(channel)))) + } + val newState = afterWatchState.get(watchStates) match { + case None => afterWatchState + case Some(w) => afterWatchState.put(watchStates, w - channel) } - afterWatchState.get(watchStates) match { - case None => afterWatchState - case Some(w) => afterWatchState.put(watchStates, w - channel) - } - case _ => state - } - } - private[sbt] val failWatchCommand = watchCommand(failWatch) { (channel, state) => - state.fail - } + val commands = cs.commands.mkString("; ") + val count = cs.count + val action = cs.failAction.getOrElse(Watch.CancelWatch) + val st = cs.callbacks.onTermination(action, commands, count, newState) + if (error) st.fail else st + case _ => if (error) state.fail else state + } + private[sbt] val stopWatchCommand = watchCommand(stopWatch)(exitWatchShared(false)) + private[sbt] val failWatchCommand = watchCommand(failWatch)(exitWatchShared(true)) /* * Creates a FileTreeRepository where it is safe to call close without inadvertently cancelling * still active watches. diff --git a/sbt-app/src/sbt-test/watch/on-termination/bar.txt b/sbt-app/src/sbt-test/watch/on-termination/bar.txt new file mode 100644 index 000000000..e69de29bb diff --git a/sbt-app/src/sbt-test/watch/on-termination/build.sbt b/sbt-app/src/sbt-test/watch/on-termination/build.sbt new file mode 100644 index 000000000..798f18016 --- /dev/null +++ b/sbt-app/src/sbt-test/watch/on-termination/build.sbt @@ -0,0 +1,15 @@ +watchOnIteration := { (count, project, commands) => + Watch.CancelWatch +} +watchOnTermination := { (action, count, command, state) => + action match { + case Watch.CancelWatch => + java.nio.file.Files.delete(java.nio.file.Paths.get("foo.txt")) + case Watch.HandleError(e) => + if (e.getMessage == "fail") + java.nio.file.Files.delete(java.nio.file.Paths.get("bar.txt")) + else + throw new IllegalStateException("unexpected error") + } + state +} diff --git a/sbt-app/src/sbt-test/watch/on-termination/foo.txt b/sbt-app/src/sbt-test/watch/on-termination/foo.txt new file mode 100644 index 000000000..e69de29bb diff --git a/sbt-app/src/sbt-test/watch/on-termination/test b/sbt-app/src/sbt-test/watch/on-termination/test new file mode 100644 index 000000000..1634ee847 --- /dev/null +++ b/sbt-app/src/sbt-test/watch/on-termination/test @@ -0,0 +1,8 @@ +$ exists foo.txt +> ~compile +$ absent foo.txt +> set watchOnIteration := { (_, _, _) => new Watch.HandleError(new IllegalStateException("fail")) } +$ exists bar.txt +-> ~compile +$ absent bar.txt + From 45518c7f24a216afeb742bec2df135db0a2e0994 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Sat, 16 Apr 2022 21:53:32 +0900 Subject: [PATCH 3/5] Update semanticdbVersion --- build.sbt | 2 +- main/src/main/scala/sbt/plugins/SemanticdbPlugin.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index a33ac73ae..c2e3feb81 100644 --- a/build.sbt +++ b/build.sbt @@ -46,7 +46,7 @@ ThisBuild / resolvers += Resolver.mavenLocal Global / semanticdbEnabled := !(Global / insideCI).value // Change main/src/main/scala/sbt/plugins/SemanticdbPlugin.scala too, if you change this. -Global / semanticdbVersion := "4.4.28" +Global / semanticdbVersion := "4.5.4" val excludeLint = SettingKey[Set[Def.KeyedInitialize[_]]]("excludeLintKeys") Global / excludeLint := (Global / excludeLint).?.value.getOrElse(Set.empty) Global / excludeLint += componentID diff --git a/main/src/main/scala/sbt/plugins/SemanticdbPlugin.scala b/main/src/main/scala/sbt/plugins/SemanticdbPlugin.scala index 76b9c8f1b..f8f2eebde 100644 --- a/main/src/main/scala/sbt/plugins/SemanticdbPlugin.scala +++ b/main/src/main/scala/sbt/plugins/SemanticdbPlugin.scala @@ -26,7 +26,7 @@ object SemanticdbPlugin extends AutoPlugin { semanticdbEnabled := SysProp.semanticdb, semanticdbIncludeInJar := false, semanticdbOptions := List(), - semanticdbVersion := "4.4.28" + semanticdbVersion := "4.5.4" ) override lazy val projectSettings: Seq[Def.Setting[_]] = Seq( From 20a9269e791747d01721e65b4829d0a22aef9e96 Mon Sep 17 00:00:00 2001 From: Philippus Date: Sun, 17 Apr 2022 12:59:12 +0200 Subject: [PATCH 4/5] Add SBTN_AUTO_COMPLETE environment variable --- .../src/main/scala/sbt/internal/client/NetworkClient.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 71a11e6fd..eb7fc322e 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -139,7 +139,7 @@ class NetworkClient( private val rebooting = new AtomicBoolean(false) private lazy val noTab = arguments.completionArguments.contains("--no-tab") private lazy val noStdErr = arguments.completionArguments.contains("--no-stderr") && - !sys.env.contains("SBTC_AUTO_COMPLETE") + !sys.env.contains("SBTN_AUTO_COMPLETE") && !sys.env.contains("SBTC_AUTO_COMPLETE") private def mkSocket(file: File): (Socket, Option[String]) = ClientSocket.socket(file, useJNI) From 11275f604cdfd435d7042badfd136b3ff1586e0a Mon Sep 17 00:00:00 2001 From: Philippus Date: Sun, 17 Apr 2022 12:56:06 +0200 Subject: [PATCH 5/5] Fix typos --- .../main/scala/sbt/internal/client/NetworkClient.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index eb7fc322e..8909d6d42 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -67,7 +67,7 @@ trait ConsoleInterface { } /** - * A NetworkClient connects to a running an sbt instance or starts a + * A NetworkClient connects to a running sbt instance or starts a * new instance if there isn't already one running. Once connected, * it can send commands for sbt to run, it can send completions to sbt * and print the completions to stdout so that a shell can consume @@ -78,15 +78,15 @@ trait ConsoleInterface { * needs to start it. It also contains the sbt command * arguments to send to the server if any are present. * @param console a logging instance. This can use a ConsoleAppender or - * just simply print to a PrintSream. + * just simply print to a PrintStream. * @param inputStream the InputStream from which the client reads bytes. It * is not hardcoded to System.in so that a NetworkClient * can be remotely controlled by a java process, which - * is useful in test. + * is useful in testing. * @param errorStream the sink for messages that we always want to be printed. * It is usually System.err but could be overridden in tests * or set to a null OutputStream if the NetworkClient needs - * to be silent + * to be silent. * @param printStream the sink for standard out messages. It is typically * System.out but in the case of completions, the bytes written * to System.out are usually treated as completion results