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