From a4e0d9a932ffa4f5b7c17c916619318dd3106dd3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Apr 2017 13:03:34 +0100 Subject: [PATCH 1/7] Refactor State#process for clarity --- main/command/src/main/scala/sbt/State.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/main/command/src/main/scala/sbt/State.scala b/main/command/src/main/scala/sbt/State.scala index bc79ba7a7..64cdf80e7 100644 --- a/main/command/src/main/scala/sbt/State.scala +++ b/main/command/src/main/scala/sbt/State.scala @@ -178,16 +178,16 @@ object State { /** Provides operations and transformations on State. */ implicit def stateOps(s: State): StateOps = new StateOps { def process(f: (String, State) => State): State = { - def doX(x: String, xs: Seq[String]) = { - log.debug(s"> $x") - f(x, s.copy(remainingCommands = xs, history = x :: s.history)) + def runCmd(cmd: String, remainingCommands: Seq[String]) = { + log.debug(s"> $cmd") + f(cmd, s.copy(remainingCommands = remainingCommands, history = cmd :: s.history)) } - def isInteractive = System.console() != null - def hasInput = System.console().reader().ready() + def isInteractive = System.console != null + def hasInput = Option(System.console) exists (_.reader.ready()) def hasShellCmd = s.definedCommands exists { case c: SimpleCommand => c.name == Shell; case _ => false } s.remainingCommands match { - case Seq() => if (isInteractive && hasInput && hasShellCmd) doX(Shell, Nil) else exit(true) - case Seq(x, xs @ _*) => doX(x, xs) + case Seq() => if (isInteractive && hasInput && hasShellCmd) runCmd(Shell, Nil) else exit(true) + case Seq(x, xs @ _*) => runCmd(x, xs) } } def :::(newCommands: Seq[String]): State = s.copy(remainingCommands = newCommands ++ s.remainingCommands) From fdd3f566941a278e8b8fb1644b2d8bca36071460 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Apr 2017 13:05:58 +0100 Subject: [PATCH 2/7] Allow --error to suppress warning messages Specifically: * Make them both commands, so they run after early commands (--error). * Make notifyUsersAboutShell aware of "iflast shell" instead of "boot". * Make writeSbtShell use a logger and tweak the message Refs #3091 --- main/src/main/scala/sbt/Main.scala | 110 ++++++++++++++++------------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 5bd13fd6a..0ceb4db02 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -29,59 +29,11 @@ final class xMain extends xsbti.AppMain { import BasicCommandStrings.runEarly import BuiltinCommands.{ initialize, defaults } import CommandStrings.{ BootCommand, DefaultsCommand, InitCommand } - if (!java.lang.Boolean.getBoolean("sbt.skip.version.write")) - setSbtVersion(configuration.baseDirectory(), configuration.provider().id().version()) val state = initialState(configuration, Seq(defaults, early), runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil) - notifyUsersAboutShell(state) runManaged(state) } - - private val sbtVersionRegex = """sbt\.version\s*=.*""".r - private def isSbtVersionLine(s: String) = sbtVersionRegex.pattern matcher s matches () - - private def isSbtProject(baseDir: File, projectDir: File) = - projectDir.exists() || (baseDir * "*.sbt").get.nonEmpty - - private def setSbtVersion(baseDir: File, sbtVersion: String) = { - val projectDir = baseDir / "project" - val buildProps = projectDir / "build.properties" - - val buildPropsLines = if (buildProps.canRead) IO.readLines(buildProps) else Nil - - val sbtVersionAbsent = buildPropsLines forall (!isSbtVersionLine(_)) - - if (sbtVersionAbsent) { - val errorMessage = s"WARN: No sbt.version set in project/build.properties, base directory: $baseDir" - try { - if (isSbtProject(baseDir, projectDir)) { - val line = s"sbt.version=$sbtVersion" - IO.writeLines(buildProps, line :: buildPropsLines) - println(s"Updated file $buildProps setting sbt.version to: $sbtVersion") - } else - println(errorMessage) - } catch { - case _: IOException => println(errorMessage) - } - } - } - - private def isInteractive = System.console() != null - private def hasShell(state: State) = state.remainingCommands contains Shell - - /** - * The "boot" command adds "iflast shell" ("if last shell") - * which basically means it falls back to shell if there are no further commands - */ - private def endsWithBoot(state: State) = state.remainingCommands.lastOption exists (_ == BootCommand) - - private def notifyUsersAboutShell(state: State) = - if (isInteractive && !hasShell(state) && !endsWithBoot(state)) { - state.log warn "Executing in batch mode." - state.log warn " For better performance, hit [ENTER] to switch to interactive mode, or" - state.log warn " consider launching sbt without any commands, or explicitly passing 'shell'" - } } final class ScriptMain extends xsbti.AppMain { @@ -143,9 +95,11 @@ object BuiltinCommands { projects, project, reboot, read, history, set, sessionCommand, inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion, PluginCross.pluginCross, PluginCross.pluginSwitch, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure, setLogLevel, plugin, plugins, + writeSbtVersion, notifyUsersAboutShell, ifLast, multi, shell, continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit, early, initialize, act) ++ compatCommands - def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil + def DefaultBootCommands: Seq[String] = + WriteSbtVersion :: NotifyUsersAboutShell :: LoadProject :: s"$IfLast $Shell" :: Nil def boot = Command.make(BootCommand)(bootParser) @@ -573,4 +527,62 @@ object BuiltinCommands { } s.put(Keys.stateCompilerCache, cache) } + + private val sbtVersionRegex = """sbt\.version\s*=.*""".r + private def isSbtVersionLine(s: String) = sbtVersionRegex.pattern matcher s matches () + + private def isSbtProject(baseDir: File, projectDir: File) = + projectDir.exists() || (baseDir * "*.sbt").get.nonEmpty + + private def writeSbtVersionUnconditionally(state: State) = { + val baseDir = state.baseDir + val sbtVersion = BuiltinCommands.sbtVersion(state) + val projectDir = baseDir / "project" + val buildProps = projectDir / "build.properties" + + val buildPropsLines = if (buildProps.canRead) IO.readLines(buildProps) else Nil + + val sbtVersionAbsent = buildPropsLines forall (!isSbtVersionLine(_)) + + if (sbtVersionAbsent) { + val warnMsg = s"No sbt.version set in project/build.properties, base directory: $baseDir" + try { + if (isSbtProject(baseDir, projectDir)) { + val line = s"sbt.version=$sbtVersion" + IO.writeLines(buildProps, line :: buildPropsLines) + state.log info s"Updated file $buildProps: set sbt.version to $sbtVersion" + } else + state.log warn warnMsg + } catch { + case _: IOException => state.log warn warnMsg + } + } + } + + private def writeSbtVersion(state: State) = + if (!java.lang.Boolean.getBoolean("sbt.skip.version.write")) + writeSbtVersionUnconditionally(state) + + private def WriteSbtVersion = "write-sbt-version" + + private def writeSbtVersion: Command = + Command.command(WriteSbtVersion) { state => writeSbtVersion(state); state } + + private def isInteractive = System.console() != null + + private def intendsToInvokeShell(state: State) = + (state.remainingCommands contains Shell) || + (state.remainingCommands.lastOption exists (_ == s"$IfLast $Shell")) + + private def notifyUsersAboutShell(state: State) = + if (isInteractive && !intendsToInvokeShell(state)) { + state.log warn "Executing in batch mode." + state.log warn " For better performance, hit [ENTER] to switch to interactive mode, or" + state.log warn " consider launching sbt without any commands, or explicitly passing 'shell'" + } + + private def NotifyUsersAboutShell = "notify-users-about-shell" + + private def notifyUsersAboutShell: Command = + Command.command(NotifyUsersAboutShell) { state => notifyUsersAboutShell(state); state } } From 2ee0b3c0643d713c3b76d832a7e207416b7406b8 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Apr 2017 13:24:05 +0100 Subject: [PATCH 3/7] Exclude "new" from startup messages Fixes #3097 --- main/src/main/scala/sbt/Main.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 0ceb4db02..34c85f25b 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -18,7 +18,7 @@ import java.net.URI import java.util.Locale import scala.util.control.NonFatal -import BasicCommandStrings.Shell +import BasicCommandStrings.{ Shell, TemplateCommand } import CommandStrings.BootCommand /** This class is the entry point for sbt. */ @@ -559,8 +559,10 @@ object BuiltinCommands { } } + private def intendsToInvokeNew(state: State) = state.remainingCommands contains TemplateCommand + private def writeSbtVersion(state: State) = - if (!java.lang.Boolean.getBoolean("sbt.skip.version.write")) + if (!java.lang.Boolean.getBoolean("sbt.skip.version.write") && !intendsToInvokeNew(state)) writeSbtVersionUnconditionally(state) private def WriteSbtVersion = "write-sbt-version" @@ -575,7 +577,7 @@ object BuiltinCommands { (state.remainingCommands.lastOption exists (_ == s"$IfLast $Shell")) private def notifyUsersAboutShell(state: State) = - if (isInteractive && !intendsToInvokeShell(state)) { + if (isInteractive && !intendsToInvokeShell(state) && !intendsToInvokeNew(state)) { state.log warn "Executing in batch mode." state.log warn " For better performance, hit [ENTER] to switch to interactive mode, or" state.log warn " consider launching sbt without any commands, or explicitly passing 'shell'" From a6af8c50c80504c64026962e7add75cf9ef7bb1b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Apr 2017 13:25:07 +0100 Subject: [PATCH 4/7] Notify users at info level & in 1 line Refs #3091 --- main/src/main/scala/sbt/Main.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 34c85f25b..ad42f485b 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -577,11 +577,8 @@ object BuiltinCommands { (state.remainingCommands.lastOption exists (_ == s"$IfLast $Shell")) private def notifyUsersAboutShell(state: State) = - if (isInteractive && !intendsToInvokeShell(state) && !intendsToInvokeNew(state)) { - state.log warn "Executing in batch mode." - state.log warn " For better performance, hit [ENTER] to switch to interactive mode, or" - state.log warn " consider launching sbt without any commands, or explicitly passing 'shell'" - } + if (isInteractive && !intendsToInvokeShell(state) && !intendsToInvokeNew(state)) + state.log info "Executing in batch mode. For better performance use sbt's shell; hit [ENTER] to do so now" private def NotifyUsersAboutShell = "notify-users-about-shell" From 1c1be9a597a94bd2f61de3948d6cae939cd14e6b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Apr 2017 13:26:17 +0100 Subject: [PATCH 5/7] Introduce suppressSbtShellNotification This requires moving the notification to after LoadProject, so much later in time, sadly.. Refs #3091 --- main/src/main/scala/sbt/Keys.scala | 1 + main/src/main/scala/sbt/Main.scala | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index fd75b43ba..439896c12 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -64,6 +64,7 @@ object Keys { val shellPrompt = SettingKey(BasicKeys.shellPrompt) val analysis = AttributeKey[inc.Analysis]("analysis", "Analysis of compilation, including dependencies and generated outputs.", DSetting) val watch = SettingKey(BasicKeys.watch) + val suppressSbtShellNotification = SettingKey[Boolean]("suppressSbtShellNotification", """True to suppress the "Executing in batch mode.." message.""", CSetting) val pollInterval = SettingKey[Int]("poll-interval", "Interval between checks for modified sources by the continuous execution command.", BMinusSetting) val watchSources = TaskKey[Seq[File]]("watch-sources", "Defines the sources in this project for continuous execution to watch for changes.", BMinusSetting) val watchTransitiveSources = TaskKey[Seq[File]]("watch-transitive-sources", "Defines the sources in all projects for continuous execution to watch.", CSetting) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index ad42f485b..43f5de17f 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -99,7 +99,7 @@ object BuiltinCommands { ifLast, multi, shell, continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit, early, initialize, act) ++ compatCommands def DefaultBootCommands: Seq[String] = - WriteSbtVersion :: NotifyUsersAboutShell :: LoadProject :: s"$IfLast $Shell" :: Nil + WriteSbtVersion :: LoadProject :: NotifyUsersAboutShell :: s"$IfLast $Shell" :: Nil def boot = Command.make(BootCommand)(bootParser) @@ -576,9 +576,11 @@ object BuiltinCommands { (state.remainingCommands contains Shell) || (state.remainingCommands.lastOption exists (_ == s"$IfLast $Shell")) - private def notifyUsersAboutShell(state: State) = - if (isInteractive && !intendsToInvokeShell(state) && !intendsToInvokeNew(state)) + private def notifyUsersAboutShell(state: State): Unit = { + val suppress = Project extract state getOpt Keys.suppressSbtShellNotification getOrElse false + if (!suppress && isInteractive && !intendsToInvokeShell(state) && !intendsToInvokeNew(state)) state.log info "Executing in batch mode. For better performance use sbt's shell; hit [ENTER] to do so now" + } private def NotifyUsersAboutShell = "notify-users-about-shell" From 54055835be32cd8c34f2b40e99d8b3614b3c7ddc Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Apr 2017 15:37:07 +0100 Subject: [PATCH 6/7] Notify users about shell only if compile is present This is a change in strategy. The motivation is the need to find a good balance between: + informing the uninformed that would benefit from this information, & + not spamming the already informed Making it dependent on "compile" being present in remainingCommands will probably make it trigger for, for example, Maven users who are used to running "mvn compile" and always run "sbt compile", and who therefore are unneccesarily suffering terribly slow compile speeds by starting up the jvm and sbt every time. Fixes #3091 --- main/src/main/scala/sbt/Main.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 43f5de17f..cffeb90d1 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -572,13 +572,11 @@ object BuiltinCommands { private def isInteractive = System.console() != null - private def intendsToInvokeShell(state: State) = - (state.remainingCommands contains Shell) || - (state.remainingCommands.lastOption exists (_ == s"$IfLast $Shell")) + private def intendsToInvokeCompile(state: State) = state.remainingCommands contains Keys.compile.key.label private def notifyUsersAboutShell(state: State): Unit = { val suppress = Project extract state getOpt Keys.suppressSbtShellNotification getOrElse false - if (!suppress && isInteractive && !intendsToInvokeShell(state) && !intendsToInvokeNew(state)) + if (!suppress && isInteractive && intendsToInvokeCompile(state)) state.log info "Executing in batch mode. For better performance use sbt's shell; hit [ENTER] to do so now" } From 5b808ee38ed31470f86196c52ce641da8b879ff7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Apr 2017 16:31:27 +0100 Subject: [PATCH 7/7] Notes for changes to startup messages --- .../tweak-new-startup-messages.markdown | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 notes/0.13.16/tweak-new-startup-messages.markdown diff --git a/notes/0.13.16/tweak-new-startup-messages.markdown b/notes/0.13.16/tweak-new-startup-messages.markdown new file mode 100644 index 000000000..756826049 --- /dev/null +++ b/notes/0.13.16/tweak-new-startup-messages.markdown @@ -0,0 +1,36 @@ +### Improvements + +- Improves the new startup messages. See below. + +### Bug fixes + +- Fixes the new startup messages. See below. + +### Improvements and bug fixes to the new startup messages + +The two new startup messages introduced in sbt 0.13.15 are: + ++ when writing out `sbt.version` for build reproducability, and ++ when informing the user sbt shell for the performance improvement + +When writing out `sbt.version` the messaging now: + ++ correctly uses a logger rather than println ++ honours the log level set, for instance, by `--error` ++ never runs when sbt "new" is being run + +When informing the user about sbt shell the messaging now: + ++ is a 1 line message, rather than 3 ++ is at info level, rather than warn level ++ can be suppressed with `suppressSbtShellNotification := false` ++ only triggers when "compile" is being run ++ never shows when sbt "new" is being run + +[#3091][]/[#3097][]/[#3147][] by [@dwijnand][] + +[#3091]: https://github.com/sbt/sbt/issues/3091 +[#3097]: https://github.com/sbt/sbt/issues/3097 +[#3147]: https://github.com/sbt/sbt/pull/3147 + +[@dwijnand]: https://github.com/dwijnand