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
This commit is contained in:
Dale Wijnand 2017-04-27 13:03:34 +01:00 committed by Eugene Yokota
parent bb16c7b068
commit b54c0ff059
4 changed files with 114 additions and 64 deletions

View File

@ -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) })

View File

@ -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)

View File

@ -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
}
}

View File

@ -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