From b54c0ff059eb4e853fa6237697be445056084409 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 27 Apr 2017 13:03:34 +0100 Subject: [PATCH] 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 Fixes #3097 --- main-command/src/main/scala/sbt/State.scala | 17 ++- main/src/main/scala/sbt/Keys.scala | 1 + main/src/main/scala/sbt/Main.scala | 124 ++++++++++-------- .../tweak-new-startup-messages.markdown | 36 +++++ 4 files changed, 114 insertions(+), 64 deletions(-) create mode 100644 notes/0.13.16/tweak-new-startup-messages.markdown diff --git a/main-command/src/main/scala/sbt/State.scala b/main-command/src/main/scala/sbt/State.scala index 687b5366a..f9f5de313 100644 --- a/main-command/src/main/scala/sbt/State.scala +++ b/main-command/src/main/scala/sbt/State.scala @@ -222,20 +222,23 @@ object State { /** Provides operations and transformations on State. */ implicit def stateOps(s: State): StateOps = new StateOps { def process(f: (Exec, State) => State): State = { - def doX(x: Exec, xs: List[Exec]) = { - log.debug(s"> $x") - f(x, s.copy(remainingCommands = xs, currentCommand = Some(x), history = x :: s.history)) + def runCmd(cmd: Exec, remainingCommands: List[Exec]) = { + log.debug(s"> $cmd") + f(cmd, + s.copy(remainingCommands = remainingCommands, + currentCommand = Some(cmd), + 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 List() => - if (isInteractive && hasInput && hasShellCmd) doX(Exec(Shell, s.source), Nil) + if (isInteractive && hasInput && hasShellCmd) runCmd(Exec(Shell, s.source), Nil) else exit(true) - case List(x, xs @ _*) => doX(x, xs.toList) + case List(x, xs @ _*) => runCmd(x, xs.toList) } } def :::(newCommands: List[String]): State = ++:(newCommands map { Exec(_, s.source) }) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 24b69956d..8172ecf5c 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -129,6 +129,7 @@ object Keys { val serverPort = SettingKey(BasicKeys.serverPort) val analysis = AttributeKey[CompileAnalysis]("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 18511a428..367e046e9 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -54,7 +54,7 @@ import java.net.URI import java.util.Locale import scala.util.control.NonFatal -import BasicCommandStrings.{ Shell, OldShell } +import BasicCommandStrings.{ Shell, OldShell, TemplateCommand } import CommandStrings.BootCommand /** This class is the entry point for sbt. */ @@ -64,67 +64,12 @@ final class xMain extends xsbti.AppMain { import BasicCommandStrings.runEarly import BuiltinCommands.defaults import sbt.internal.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 hasCommand(state: State, cmd: String): Boolean = - (state.remainingCommands find { x => - x.commandLine == cmd - }).isDefined - - /** - * 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 (_.commandLine == BootCommand) - - private def notifyUsersAboutShell(state: State) = - if (isInteractive && !hasCommand(state, Shell) && !hasCommand(state, OldShell) && !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 { @@ -238,6 +183,8 @@ object BuiltinCommands { setLogLevel, plugin, plugins, + writeSbtVersion, + notifyUsersAboutShell, ifLast, multi, shell, @@ -259,7 +206,8 @@ object BuiltinCommands { act ) ++ compatCommands - def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil + def DefaultBootCommands: Seq[String] = + WriteSbtVersion :: LoadProject :: NotifyUsersAboutShell :: s"$IfLast $Shell" :: Nil def boot = Command.make(BootCommand)(bootParser) @@ -763,4 +711,66 @@ object BuiltinCommands { if (exec.commandLine.trim.isEmpty) newState else newState.clearGlobalLog } + + 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 intendsToInvokeNew(state: State) = state.remainingCommands contains TemplateCommand + + private def writeSbtVersion(state: State) = + if (!java.lang.Boolean.getBoolean("sbt.skip.version.write") && !intendsToInvokeNew(state)) + 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 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 && intendsToInvokeCompile(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" + + private def notifyUsersAboutShell: Command = + Command.command(NotifyUsersAboutShell) { state => + notifyUsersAboutShell(state); state + } } 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