mirror of https://github.com/sbt/sbt.git
split command core to main/command/
This commit is contained in:
parent
bf472b8ff4
commit
98c98f9c26
|
|
@ -11,7 +11,7 @@ package sbt
|
|||
import DefaultParsers._
|
||||
import Types.idFun
|
||||
import java.net.URI
|
||||
import CommandSupport.ShowCommand
|
||||
import CommandStrings.ShowCommand
|
||||
|
||||
final class ParsedKey(val key: ScopedKey[_], val mask: ScopeMask)
|
||||
object Act
|
||||
|
|
|
|||
|
|
@ -3,37 +3,15 @@
|
|||
*/
|
||||
package sbt
|
||||
|
||||
import complete.HistoryCommands
|
||||
import scala.annotation.tailrec
|
||||
|
||||
import java.io.File
|
||||
import Path._
|
||||
|
||||
object CommandSupport
|
||||
object CommandStrings
|
||||
{
|
||||
def logger(s: State) = globalLogging(s).full
|
||||
def globalLogging(s: State) = s get Keys.globalLogging getOrElse error("Global logging misconfigured")
|
||||
@deprecated("Use the `log` member of a State instance directly.", "0.12.0")
|
||||
def logger(s: State) = s.log
|
||||
|
||||
// slightly better fallback in case of older launcher
|
||||
def bootDirectory(state: State): File =
|
||||
try { state.configuration.provider.scalaProvider.launcher.bootDirectory }
|
||||
catch { case e: NoSuchMethodError => new File(".").getAbsoluteFile }
|
||||
|
||||
private def canRead = (_: File).canRead
|
||||
def notReadable(files: Seq[File]): Seq[File] = files filterNot canRead
|
||||
def readable(files: Seq[File]): Seq[File] = files filter canRead
|
||||
def sbtRCs(s: State): Seq[File] =
|
||||
(Path.userHome / sbtrc) ::
|
||||
(s.baseDir / sbtrc asFile) ::
|
||||
Nil
|
||||
|
||||
def readLines(files: Seq[File]): Seq[String] = files flatMap (line => IO.readLines(line)) flatMap processLine
|
||||
def processLine(s: String) = { val trimmed = s.trim; if(ignoreLine(trimmed)) None else Some(trimmed) }
|
||||
def ignoreLine(s: String) = s.isEmpty || s.startsWith("#")
|
||||
@deprecated("Use the `globalLogging` member of a State instance directly.", "0.12.0")
|
||||
def globalLogging(s: State) = s.globalLogging
|
||||
|
||||
/** The prefix used to identify a request to execute the remaining input on source changes.*/
|
||||
val ContinuousExecutePrefix = "~"
|
||||
val HelpCommand = "help"
|
||||
val AboutCommand = "about"
|
||||
val TasksCommand = "tasks"
|
||||
val ProjectCommand = "project"
|
||||
|
|
@ -41,8 +19,14 @@ object CommandSupport
|
|||
val ShowCommand = "show"
|
||||
val BootCommand = "boot"
|
||||
|
||||
val Exit = "exit"
|
||||
val Quit = "quit"
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
val ContinuousExecutePrefix = BasicCommandStrings.ContinuousExecutePrefix
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
val Exit = BasicCommandStrings.Exit
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
val Quit = BasicCommandStrings.Quit
|
||||
|
||||
val EvalCommand = "eval"
|
||||
val evalBrief = (EvalCommand + " <expression>", "Evaluates the given Scala expression and prints the result and type.")
|
||||
|
|
@ -128,9 +112,8 @@ SetCommand + """ <setting-expression>
|
|||
def sessionBrief = (SessionCommand + " <session-command>", "Manipulates session settings. For details, run 'help " + SessionCommand + "'.")
|
||||
|
||||
/** The command name to terminate the program.*/
|
||||
val TerminateAction: String = Exit
|
||||
|
||||
def continuousBriefHelp = (ContinuousExecutePrefix + " <command>", "Executes the specified command whenever source files change.")
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
val TerminateAction: String = BasicCommandStrings.TerminateAction
|
||||
|
||||
def tasksPreamble = """
|
||||
This is a list of tasks defined for the current project.
|
||||
|
|
@ -140,11 +123,6 @@ Tasks produce values. Use the 'show' command to run the task and print the resu
|
|||
def tasksBrief = "Displays the tasks defined for the current project."
|
||||
def tasksDetailed = "Displays the tasks defined directly or indirectly for the current project."
|
||||
|
||||
def helpBrief = (HelpCommand + " [command]*", "Displays this help message or prints detailed help on requested commands.")
|
||||
def helpDetailed = """
|
||||
If an argument is provided, this prints detailed help for that command.
|
||||
Otherwise, this prints a help summary."""
|
||||
|
||||
def aboutBrief = "Displays basic information about sbt and the build."
|
||||
def aboutDetailed = aboutBrief
|
||||
|
||||
|
|
@ -175,126 +153,23 @@ ProjectCommand +
|
|||
def projectsBrief = projectsDetailed
|
||||
def projectsDetailed = "Displays the names of available projects."
|
||||
|
||||
def historyHelp = Help.briefDetail(HistoryCommands.descriptions)
|
||||
|
||||
def exitBrief = "Terminates the build."
|
||||
|
||||
def sbtrc = ".sbtrc"
|
||||
|
||||
def ReadCommand = "<"
|
||||
def ReadFiles = " file1 file2 ..."
|
||||
def ReadBrief = (ReadCommand + " <file>*", "Reads command lines from the provided files.")
|
||||
def ReadDetailed =
|
||||
ReadCommand + ReadFiles + """
|
||||
|
||||
Reads the lines from the given files and inserts them as commands.
|
||||
All empty lines and lines that start with '#' are ignored.
|
||||
If a file does not exist or is not readable, this command fails.
|
||||
|
||||
All the lines from all the files are read before any of the commands
|
||||
are executed. Thus, if any file is not readable, none of commands
|
||||
from any of the files (even the existing ones) will be run.
|
||||
|
||||
You probably need to escape this command if entering it at your shell."""
|
||||
|
||||
def ApplyCommand = "apply"
|
||||
def ApplyBrief = (ApplyCommand + " <module-name>*", ApplyDetailed)
|
||||
def ApplyDetailed = "Transforms the current State by calling <module-name>.apply(currentState) for each listed."
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
def ReadCommand = BasicCommandStrings.ReadCommand
|
||||
|
||||
def DefaultsCommand = "add-default-commands"
|
||||
def DefaultsBrief = (DefaultsCommand, DefaultsDetailed)
|
||||
def DefaultsDetailed = "Registers default built-in commands"
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
def RebootCommand = "reboot"
|
||||
def RebootSummary = RebootCommand + " [full]"
|
||||
def RebootBrief = (RebootSummary, "Reboots sbt and then executes the remaining commands.")
|
||||
def RebootDetailed =
|
||||
RebootSummary + """
|
||||
|
||||
This command is equivalent to exiting sbt, restarting, and running the
|
||||
remaining commands with the exception that the JVM is not shut down.
|
||||
|
||||
If 'full' is specified, the boot directory (`~/.sbt/boot` by default)
|
||||
is deleted before restarting. This forces an update of sbt and Scala
|
||||
and is useful when working with development versions of sbt or Scala."""
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
def Multi = ";"
|
||||
def MultiBrief = (Multi + " <command> (" + Multi + " <command>)*", "Runs the provided semicolon-separated commands.")
|
||||
def MultiDetailed =
|
||||
Multi + " command1 " + Multi + """ command2 ...
|
||||
|
||||
Runs the specified commands."""
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
def AppendCommand = "append"
|
||||
def AppendLastBrief = (AppendCommand + " <command>", AppendLastDetailed)
|
||||
def AppendLastDetailed = "Appends 'command' to list of commands to run."
|
||||
|
||||
val AliasCommand = "alias"
|
||||
def AliasBrief = (AliasCommand, "Adds, removes, or prints command aliases.")
|
||||
def AliasDetailed =
|
||||
AliasCommand + """
|
||||
|
||||
Prints a list of defined aliases.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name
|
||||
|
||||
Prints the alias defined for `name`.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name=value
|
||||
|
||||
Sets the alias `name` to `value`, replacing any existing alias with that name.
|
||||
Whenever `name` is entered, the corresponding `value` is run.
|
||||
If any argument is provided to `name`, it is appended as argument to `value`.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name=
|
||||
|
||||
Removes the alias for `name`."""
|
||||
|
||||
def Discover = "discover"
|
||||
def DiscoverBrief = (DiscoverSyntax, "Finds annotated classes and subclasses.")
|
||||
def DiscoverSyntax = Discover + " [-module true|false] [-sub <names>] [-annot <names>]"
|
||||
def DiscoverDetailed =
|
||||
DiscoverSyntax + """
|
||||
|
||||
Looks for public, concrete classes that match the requested query using the current sbt.inc.Analysis instance.
|
||||
|
||||
-module
|
||||
Specifies whether modules (true) or classes (false) are found.
|
||||
The default is classes/traits (false).
|
||||
|
||||
-sub
|
||||
Specifies comma-separated class names.
|
||||
Classes that have one or more of these classes as an ancestor are included in the resulting list.
|
||||
|
||||
-annot
|
||||
Specifies comma-separated annotation names.
|
||||
Classes with one or more of these annotations on the class or one of its non-private methods are included in the resulting list.
|
||||
"""
|
||||
|
||||
def CompileName = "direct-compile"
|
||||
def CompileBrief = (CompileSyntax, "Incrementally compiles the provided sources.")
|
||||
def CompileSyntax = CompileName + " -src <paths> [-cp <paths>] [-d <path>]"
|
||||
def CompileDetailed =
|
||||
CompileSyntax + """
|
||||
|
||||
Incrementally compiles Scala and Java sources.
|
||||
|
||||
<paths> are explicit paths separated by the platform path separator.
|
||||
|
||||
The specified output path will contain the following directory structure:
|
||||
|
||||
scala_<version>/
|
||||
classes/
|
||||
cache/
|
||||
|
||||
Compiled classes will be written to the 'classes' directory.
|
||||
Cached information about the compilation will be written to 'cache'.
|
||||
"""
|
||||
|
||||
val FailureWall = "---"
|
||||
|
||||
def Load = "load"
|
||||
def LoadLabel = "a project"
|
||||
|
|
@ -308,31 +183,20 @@ CompileSyntax + """
|
|||
def LoadProjectBrief = (LoadProject, LoadProjectDetailed)
|
||||
def LoadProjectDetailed = "Loads the project in the current directory"
|
||||
|
||||
def Shell = "shell"
|
||||
def ShellBrief = ShellDetailed
|
||||
def ShellDetailed = "Provides an interactive prompt from which commands can be run."
|
||||
@deprecated("Moved to State", "0.12.0")
|
||||
val FailureWall = State.FailureWall
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
def Shell = BasicCommandStrings.Shell
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
def ClearOnFailure = "--"
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
def OnFailure = "-"
|
||||
def OnFailureBrief = (OnFailure + " command", "Registers 'command' to run if a command fails.")
|
||||
def OnFailureDetailed =
|
||||
OnFailure + """ command
|
||||
|
||||
Registers 'command' to run when a command fails to complete normally.
|
||||
|
||||
Only one failure command may be registered at a time, so this command
|
||||
replaces the previous command if there is one.
|
||||
|
||||
The failure command resets when it runs once, so it must be added
|
||||
again if desired."""
|
||||
|
||||
@deprecated("Moved to BasicCommandStrings", "0.12.0")
|
||||
def IfLast = "iflast"
|
||||
def IfLastBrief = (IfLast + " <command>", IfLastCommon)
|
||||
def IfLastCommon = "If there are no more commands after this one, 'command' is run."
|
||||
def IfLastDetailed =
|
||||
IfLast + """ command
|
||||
|
||||
""" + IfLastCommon
|
||||
|
||||
def InitCommand = "initialize"
|
||||
def InitBrief = (InitCommand, "Initializes command processing.")
|
||||
|
|
@ -352,4 +216,12 @@ load-commands -base ~/.sbt/commands
|
|||
< .sbtrc
|
||||
Runs commands from ~/.sbtrc and ./.sbtrc if they exist
|
||||
"""
|
||||
|
||||
import java.io.File
|
||||
import Path._
|
||||
|
||||
def sbtRCs(s: State): Seq[File] =
|
||||
(Path.userHome / sbtrc) ::
|
||||
(s.baseDir / sbtrc asFile) ::
|
||||
Nil
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ object IvyConsole
|
|||
lazy val command =
|
||||
Command.command(Name) { state =>
|
||||
val Dependencies(managed, repos, unmanaged) = parseDependencies(state.remainingCommands, state.log)
|
||||
val base = new File(CommandSupport.bootDirectory(state), Name)
|
||||
val base = new File(CommandUtil.bootDirectory(state), Name)
|
||||
IO.createDirectory(base)
|
||||
|
||||
val (eval, structure) = Load.defaultLoad(state, base, state.log)
|
||||
|
|
|
|||
|
|
@ -56,11 +56,10 @@ object Keys
|
|||
// val onComplete = SettingKey[RMap[Task,Result] => RMap[Task,Result]]("on-complete", "Transformation to apply to the final task result map. This may also be used to register hooks to run when task evaluation completes.")
|
||||
|
||||
// Command keys
|
||||
val globalLogging = AttributeKey[GlobalLogging]("global-logging", "Provides a global Logger, including command logging.")
|
||||
val historyPath = SettingKey[Option[File]]("history", "The location where command line history is persisted.")
|
||||
val shellPrompt = SettingKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.")
|
||||
val historyPath = SettingKey(BasicKeys.historyPath)
|
||||
val shellPrompt = SettingKey(BasicKeys.shellPrompt)
|
||||
val analysis = AttributeKey[inc.Analysis]("analysis", "Analysis of compilation, including dependencies and generated outputs.")
|
||||
val watch = SettingKey[Watched]("watch", "Continuous execution configuration.")
|
||||
val watch = SettingKey(BasicKeys.watch)
|
||||
val pollInterval = SettingKey[Int]("poll-interval", "Interval between checks for modified sources by the continuous execution command.")
|
||||
val watchSources = TaskKey[Seq[File]]("watch-sources", "Defines the sources in this project for continuous execution to watch for changes.")
|
||||
val watchTransitiveSources = TaskKey[Seq[File]]("watch-transitive-sources", "Defines the sources in all projects for continuous execution to watch.")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ package sbt
|
|||
import inc.{FileValueCache, Locate}
|
||||
import Project.{inScope, ScopedKey, ScopeLocal, Setting}
|
||||
import Keys.{appConfiguration, baseDirectory, configuration, streams, Streams, thisProject, thisProjectRef}
|
||||
import Keys.{globalLogging, isDummy, loadedBuild, parseResult, resolvedScoped, taskDefinitionKey}
|
||||
import Keys.{isDummy, loadedBuild, parseResult, resolvedScoped, taskDefinitionKey}
|
||||
import tools.nsc.reporters.ConsoleReporter
|
||||
import Build.{analyzed, data}
|
||||
import Scope.{GlobalScope, ThisScope}
|
||||
|
|
@ -489,7 +489,7 @@ object Load
|
|||
val inputs = Compiler.inputs(data(classpath), sources, target, Nil, Nil, definesClass, Compiler.DefaultMaxErrors, CompileOrder.Mixed)(compilers, log)
|
||||
val analysis =
|
||||
try { Compiler(inputs, log) }
|
||||
catch { case _: xsbti.CompileFailed => throw new NoMessageException } // compiler already logged errors
|
||||
catch { case _: xsbti.CompileFailed => throw new AlreadyHandledException } // compiler already logged errors
|
||||
(inputs, analysis)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package sbt
|
|||
import std.Transform
|
||||
import Project.ScopedKey
|
||||
import Scope.GlobalScope
|
||||
import MainLogging._
|
||||
import Keys.{logLevel, logManager, persistLogLevel, persistTraceLevel, state, traceLevel}
|
||||
|
||||
object LogManager
|
||||
|
|
@ -21,11 +22,6 @@ object LogManager
|
|||
lazy val default: LogManager = withLoggers()
|
||||
def defaults(extra: ScopedKey[_] => Seq[AbstractLogger]): LogManager = withLoggers(extra = extra)
|
||||
|
||||
def defaultScreen: AbstractLogger = ConsoleLogger()
|
||||
|
||||
def defaultBacked(useColor: Boolean = ConsoleLogger.formatEnabled): PrintWriter => ConsoleLogger =
|
||||
to => ConsoleLogger(ConsoleLogger.printWriterOut(to), useColor = useColor) // TODO: should probably filter ANSI codes when useColor=false
|
||||
|
||||
def withScreenLogger(mk: => AbstractLogger): LogManager = withLoggers(mk)
|
||||
|
||||
def withLoggers(screen: => AbstractLogger = defaultScreen, backed: PrintWriter => AbstractLogger = defaultBacked(), extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil): LogManager =
|
||||
|
|
@ -42,40 +38,12 @@ object LogManager
|
|||
val backingLevel = getOr(persistLogLevel.key, Level.Debug)
|
||||
val screenTrace = getOr(traceLevel.key, -1)
|
||||
val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue)
|
||||
val extraBacked = (state get Keys.globalLogging).map(_.backed).toList
|
||||
val extraBacked = state.globalLogging.backed :: Nil
|
||||
multiLogger( new MultiLoggerConfig(console, backed, extraBacked ::: extra, screenLevel, backingLevel, screenTrace, backingTrace) )
|
||||
}
|
||||
def multiLogger(config: MultiLoggerConfig): Logger =
|
||||
{
|
||||
import config._
|
||||
val multi = new MultiLogger(console :: backed :: extra)
|
||||
// sets multi to the most verbose for clients that inspect the current level
|
||||
multi setLevel Level.unionAll(backingLevel :: screenLevel :: extra.map(_.getLevel))
|
||||
// set the specific levels
|
||||
console setLevel screenLevel
|
||||
backed setLevel backingLevel
|
||||
console setTrace screenTrace
|
||||
backed setTrace backingTrace
|
||||
multi: Logger
|
||||
}
|
||||
def globalDefault(writer: PrintWriter, backing: GlobalLogBacking): GlobalLogging =
|
||||
{
|
||||
val backed = defaultBacked()(writer)
|
||||
val full = multiLogger(defaultMultiConfig( backed ) )
|
||||
GlobalLogging(full, backed, backing)
|
||||
}
|
||||
|
||||
def defaultMultiConfig(backing: AbstractLogger): MultiLoggerConfig =
|
||||
new MultiLoggerConfig(defaultScreen, backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue)
|
||||
}
|
||||
final case class MultiLoggerConfig(console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger], screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int)
|
||||
|
||||
trait LogManager
|
||||
{
|
||||
def apply(data: Settings[Scope], state: State, task: ScopedKey[_], writer: PrintWriter): Logger
|
||||
}
|
||||
final case class GlobalLogBacking(file: File, last: Option[File])
|
||||
{
|
||||
def shift(newFile: File) = GlobalLogBacking(newFile, Some(file))
|
||||
def unshift = GlobalLogBacking(last getOrElse file, None)
|
||||
}
|
||||
final case class GlobalLogging(full: Logger, backed: ConsoleLogger, backing: GlobalLogBacking)
|
||||
|
|
|
|||
370
main/Main.scala
370
main/Main.scala
|
|
@ -3,132 +3,64 @@
|
|||
*/
|
||||
package sbt
|
||||
|
||||
import Execute.NodeView
|
||||
import complete.{DefaultParsers, HistoryCommands, Parser}
|
||||
import HistoryCommands.{Start => HistoryPrefix}
|
||||
import complete.{DefaultParsers, Parser}
|
||||
import compiler.EvalImports
|
||||
import Types.{const,idFun}
|
||||
import Types.idFun
|
||||
import Aggregation.AnyKeys
|
||||
|
||||
import Command.applyEffect
|
||||
import Keys.{analysis,historyPath,globalLogging,shellPrompt}
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConversions._
|
||||
import Function.tupled
|
||||
import java.net.URI
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import Path._
|
||||
import StandardMain._
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
|
||||
/** This class is the entry point for sbt.*/
|
||||
final class xMain extends xsbti.AppMain
|
||||
{
|
||||
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
|
||||
{
|
||||
import BuiltinCommands.{initialAttributes, initialize, defaults, DefaultBootCommands}
|
||||
import CommandSupport.{BootCommand, DefaultsCommand, InitCommand}
|
||||
val initialCommandDefs = Seq(initialize, defaults)
|
||||
val commands = DefaultsCommand +: InitCommand +: BootCommand +: configuration.arguments.map(_.trim)
|
||||
val state = State( configuration, initialCommandDefs, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue )
|
||||
MainLoop.runLogged(state)
|
||||
import BuiltinCommands.{initialize, defaults}
|
||||
import CommandStrings.{BootCommand, DefaultsCommand, InitCommand}
|
||||
MainLoop.runLogged( initialState(configuration,
|
||||
Seq(initialize, defaults),
|
||||
DefaultsCommand :: InitCommand :: BootCommand :: Nil)
|
||||
)
|
||||
}
|
||||
}
|
||||
final class ScriptMain extends xsbti.AppMain
|
||||
{
|
||||
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
|
||||
{
|
||||
import BuiltinCommands.{initialAttributes, ScriptCommands}
|
||||
val commands = Script.Name +: configuration.arguments.map(_.trim)
|
||||
val state = State( configuration, ScriptCommands, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue )
|
||||
MainLoop.runLogged(state)
|
||||
}
|
||||
MainLoop.runLogged( initialState(configuration,
|
||||
BuiltinCommands.ScriptCommands,
|
||||
Script.Name :: Nil)
|
||||
)
|
||||
}
|
||||
final class ConsoleMain extends xsbti.AppMain
|
||||
{
|
||||
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
|
||||
{
|
||||
import BuiltinCommands.{initialAttributes, ConsoleCommands}
|
||||
val commands = IvyConsole.Name +: configuration.arguments.map(_.trim)
|
||||
val state = State( configuration, ConsoleCommands, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue )
|
||||
MainLoop.runLogged(state)
|
||||
}
|
||||
MainLoop.runLogged( initialState(configuration,
|
||||
BuiltinCommands.ConsoleCommands,
|
||||
IvyConsole.Name :: Nil)
|
||||
)
|
||||
}
|
||||
object MainLoop
|
||||
|
||||
object StandardMain
|
||||
{
|
||||
/** Entry point to run the remaining commands in State with managed global logging.*/
|
||||
def runLogged(state: State): xsbti.MainResult =
|
||||
runLoggedLoop(state, GlobalLogBacking(newBackingFile(), None))
|
||||
|
||||
/** Constructs a new, (weakly) unique, temporary file to use as the backing for global logging. */
|
||||
def newBackingFile(): File = File.createTempFile("sbt",".log")
|
||||
|
||||
/** Run loop that evaluates remaining commands and manages changes to global logging configuration.*/
|
||||
@tailrec def runLoggedLoop(state: State, logBacking: GlobalLogBacking): xsbti.MainResult =
|
||||
runAndClearLast(state, logBacking) match {
|
||||
case ret: Return => // delete current and last log files when exiting normally
|
||||
logBacking.file.delete()
|
||||
deleteLastLog(logBacking)
|
||||
ret.result
|
||||
case clear: ClearGlobalLog => // delete previous log file, move current to previous, and start writing to a new file
|
||||
deleteLastLog(logBacking)
|
||||
runLoggedLoop(clear.state, logBacking shift newBackingFile())
|
||||
case keep: KeepGlobalLog => // make previous log file the current log file
|
||||
logBacking.file.delete
|
||||
runLoggedLoop(keep.state, logBacking.unshift)
|
||||
}
|
||||
|
||||
/** Runs the next sequence of commands, cleaning up global logging after any exceptions. */
|
||||
def runAndClearLast(state: State, logBacking: GlobalLogBacking): RunNext =
|
||||
try
|
||||
runWithNewLog(state, logBacking)
|
||||
catch {
|
||||
case e: xsbti.FullReload =>
|
||||
deleteLastLog(logBacking)
|
||||
throw e // pass along a reboot request
|
||||
case e =>
|
||||
System.err.println("sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file)
|
||||
deleteLastLog(logBacking)
|
||||
throw e
|
||||
}
|
||||
|
||||
/** Deletes the previous global log file. */
|
||||
def deleteLastLog(logBacking: GlobalLogBacking): Unit =
|
||||
logBacking.last.foreach(_.delete())
|
||||
|
||||
/** Runs the next sequence of commands with global logging in place. */
|
||||
def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext =
|
||||
Using.fileWriter(append = true)(logBacking.file) { writer =>
|
||||
val out = new java.io.PrintWriter(writer)
|
||||
val loggedState = state.put(globalLogging, LogManager.globalDefault(out, logBacking))
|
||||
try run(loggedState) finally out.close()
|
||||
}
|
||||
sealed trait RunNext
|
||||
final class ClearGlobalLog(val state: State) extends RunNext
|
||||
final class KeepGlobalLog(val state: State) extends RunNext
|
||||
final class Return(val result: xsbti.MainResult) extends RunNext
|
||||
|
||||
/** Runs the next sequence of commands that doesn't require global logging changes.*/
|
||||
@tailrec def run(state: State): RunNext =
|
||||
state.next match
|
||||
{
|
||||
case State.Continue => run(next(state))
|
||||
case State.ClearGlobalLog => new ClearGlobalLog(state.continue)
|
||||
case State.KeepLastLog => new KeepGlobalLog(state.continue)
|
||||
case ret: State.Return => new Return(ret.result)
|
||||
}
|
||||
|
||||
def next(state: State): State =
|
||||
ErrorHandling.wideConvert { state.process(Command.process) } match
|
||||
{
|
||||
case Right(s) => s
|
||||
case Left(t: xsbti.FullReload) => throw t
|
||||
case Left(t) => BuiltinCommands.handleException(t, state)
|
||||
}
|
||||
def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State =
|
||||
{
|
||||
val commands = preCommands ++ configuration.arguments.map(_.trim)
|
||||
State( configuration, initialDefinitions, Set.empty, None, commands, State.newHistory, BuiltinCommands.initialAttributes, initialGlobalLogging, State.Continue )
|
||||
}
|
||||
def initialGlobalLogging: GlobalLogging =
|
||||
GlobalLogging.initial(MainLogging.globalDefault _, File.createTempFile("sbt",".log"))
|
||||
}
|
||||
|
||||
import DefaultParsers._
|
||||
import CommandSupport._
|
||||
import CommandStrings._
|
||||
import BasicCommands._
|
||||
import CommandUtil._
|
||||
|
||||
object BuiltinCommands
|
||||
{
|
||||
def initialAttributes = AttributeMap.empty
|
||||
|
|
@ -140,22 +72,9 @@ object BuiltinCommands
|
|||
def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil
|
||||
|
||||
def boot = Command.make(BootCommand)(bootParser)
|
||||
def nop = Command.custom(s => success(() => s))
|
||||
def ignore = Command.command(FailureWall)(idFun)
|
||||
|
||||
def detail(selected: Seq[String], detailMap: Map[String, String]): Seq[String] =
|
||||
selected.distinct flatMap { detailMap get _ }
|
||||
|
||||
def help = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
|
||||
def about = Command.command(AboutCommand, aboutBrief, aboutDetailed) { s => logger(s).info(aboutString(s)); s }
|
||||
|
||||
def helpParser(s: State) =
|
||||
{
|
||||
val h = (Help.empty /: s.definedCommands)(_ ++ _.help(s))
|
||||
val helpCommands = h.detail.keySet
|
||||
val args = (token(Space) ~> token( NotSpace examples helpCommands )).*
|
||||
applyEffect(args)(runHelp(s, h))
|
||||
}
|
||||
// This parser schedules the default boot commands unless overridden by an alias
|
||||
def bootParser(s: State) =
|
||||
{
|
||||
|
|
@ -163,16 +82,6 @@ object BuiltinCommands
|
|||
delegateToAlias(BootCommand, success(orElse) )(s)
|
||||
}
|
||||
|
||||
def runHelp(s: State, h: Help)(args: Seq[String]): State =
|
||||
{
|
||||
val message =
|
||||
if(args.isEmpty)
|
||||
aligned(" ", " ", h.brief).mkString("\n", "\n", "\n")
|
||||
else
|
||||
detail(args, h.detail) mkString("\n", "\n\n", "\n")
|
||||
System.out.println(message)
|
||||
s
|
||||
}
|
||||
def sbtVersion(s: State): String = s.configuration.provider.id.version
|
||||
def scalaVersion(s: State): String = s.configuration.provider.scalaProvider.version
|
||||
def aboutString(s: State): String =
|
||||
|
|
@ -230,154 +139,15 @@ object BuiltinCommands
|
|||
aligned(" ", " ", taskDetail(s)) mkString("\n", "\n", "")
|
||||
|
||||
def taskStrings(key: AttributeKey[_]): Option[(String, String)] = key.description map { d => (key.label, d) }
|
||||
def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] =
|
||||
{
|
||||
val width = in.map(_._1.length).max
|
||||
in.map { case (a, b) => (" " + fill(a, width) + sep + b) }
|
||||
}
|
||||
def fill(s: String, size: Int) = s + " " * math.max(size - s.length, 0)
|
||||
|
||||
def alias = Command.make(AliasCommand, AliasBrief, AliasDetailed) { s =>
|
||||
val name = token(OpOrID.examples( aliasNames(s) : _*) )
|
||||
val assign = token(OptSpace ~ '=' ~ OptSpace)
|
||||
val sfree = removeAliases(s)
|
||||
val to = matched(sfree.combinedParser, partial = true) | any.+.string
|
||||
val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?)
|
||||
applyEffect(base)(t => runAlias(s, t) )
|
||||
}
|
||||
|
||||
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
|
||||
args match
|
||||
{
|
||||
case None => printAliases(s); s
|
||||
case Some(x ~ None) if !x.isEmpty => printAlias(s, x.trim); s
|
||||
case Some(name ~ Some(None)) => removeAlias(s, name.trim)
|
||||
case Some(name ~ Some(Some(value))) => addAlias(s, name.trim, value.trim)
|
||||
}
|
||||
|
||||
def shell = Command.command(Shell, ShellBrief, ShellDetailed) { s =>
|
||||
val history = (s get historyPath.key) getOrElse Some((s.baseDir / ".history").asFile)
|
||||
val prompt = (s get shellPrompt.key) match { case Some(pf) => pf(s); case None => "> " }
|
||||
val reader = new FullReader(history, s.combinedParser)
|
||||
val line = reader.readLine(prompt)
|
||||
line match {
|
||||
case Some(line) =>
|
||||
val newState = s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands)
|
||||
if(line.trim.isEmpty) newState else newState.clearGlobalLog
|
||||
case None => s
|
||||
}
|
||||
}
|
||||
|
||||
def multiParser(s: State): Parser[Seq[String]] =
|
||||
{
|
||||
val nonSemi = token(charClass(_ != ';').+, hide= const(true))
|
||||
( token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser&nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim) ).+
|
||||
}
|
||||
|
||||
def multiApplied(s: State) =
|
||||
Command.applyEffect( multiParser(s) )( _ ::: s )
|
||||
|
||||
def multi = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed) )
|
||||
|
||||
lazy val otherCommandParser = (s: State) => token(OptSpace ~> combinedLax(s, any.+) )
|
||||
def combinedLax(s: State, any: Parser[_]): Parser[String] =
|
||||
matched(s.combinedParser | token(any, hide= const(true)))
|
||||
|
||||
def ifLast = Command(IfLast, IfLastBrief, IfLastDetailed)(otherCommandParser) { (s, arg) =>
|
||||
if(s.remainingCommands.isEmpty) arg :: s else s
|
||||
}
|
||||
def append = Command(AppendCommand, AppendLastBrief, AppendLastDetailed)(otherCommandParser) { (s, arg) =>
|
||||
s.copy(remainingCommands = s.remainingCommands :+ arg)
|
||||
}
|
||||
|
||||
def setOnFailure = Command(OnFailure, OnFailureBrief, OnFailureDetailed)(otherCommandParser) { (s, arg) =>
|
||||
s.copy(onFailure = Some(arg))
|
||||
}
|
||||
def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
|
||||
|
||||
def reboot = Command(RebootCommand, RebootBrief, RebootDetailed)(rebootParser) { (s, full) =>
|
||||
s.reboot(full)
|
||||
}
|
||||
def rebootParser(s: State) = token(Space ~> "full" ^^^ true) ?? false
|
||||
|
||||
def defaults = Command.command(DefaultsCommand) { s =>
|
||||
s ++ DefaultCommands
|
||||
}
|
||||
def call = Command(ApplyCommand, ApplyBrief, ApplyDetailed)(_ => spaceDelimited("<class name>")) { (state,args) =>
|
||||
val loader = getClass.getClassLoader
|
||||
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader))
|
||||
(state /: loaded) { case (s, obj: (State => State)) => obj(s) }
|
||||
}
|
||||
|
||||
def initialize = Command.command(InitCommand) { s =>
|
||||
/*"load-commands -base ~/.sbt/commands" :: */readLines( readable( sbtRCs(s) ) ) ::: s
|
||||
}
|
||||
|
||||
def readParser(s: State) =
|
||||
{
|
||||
val files = (token(Space) ~> fileParser(s.baseDir)).+
|
||||
val portAndSuccess = token(OptSpace) ~> Port
|
||||
portAndSuccess || files
|
||||
}
|
||||
|
||||
def read = Command.make(ReadCommand, ReadBrief, ReadDetailed)(s => applyEffect(readParser(s))(doRead(s)) )
|
||||
|
||||
def doRead(s: State)(arg: Either[Int, Seq[File]]): State =
|
||||
arg match
|
||||
{
|
||||
case Left(portAndSuccess) =>
|
||||
val port = math.abs(portAndSuccess)
|
||||
val previousSuccess = portAndSuccess >= 0
|
||||
readMessage(port, previousSuccess) match
|
||||
{
|
||||
case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy(onFailure = Some(ReadCommand + " " + (-port)))
|
||||
case None =>
|
||||
System.err.println("Connection closed.")
|
||||
s.fail
|
||||
}
|
||||
case Right(from) =>
|
||||
val notFound = notReadable(from)
|
||||
if(notFound.isEmpty)
|
||||
readLines(from) ::: s // this means that all commands from all files are loaded, parsed, and inserted before any are executed
|
||||
else {
|
||||
logger(s).error("Command file(s) not readable: \n\t" + notFound.mkString("\n\t"))
|
||||
s
|
||||
}
|
||||
}
|
||||
private def readMessage(port: Int, previousSuccess: Boolean): Option[String] =
|
||||
{
|
||||
// split into two connections because this first connection ends the previous communication
|
||||
xsbt.IPC.client(port) { _.send(previousSuccess.toString) }
|
||||
// and this second connection starts the next communication
|
||||
xsbt.IPC.client(port) { ipc =>
|
||||
val message = ipc.receive
|
||||
if(message eq null) None else Some(message)
|
||||
}
|
||||
}
|
||||
|
||||
def continuous =
|
||||
Command(ContinuousExecutePrefix, Help(continuousBriefHelp) )(otherCommandParser) { (s, arg) =>
|
||||
withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w =>
|
||||
val repeat = ContinuousExecutePrefix + (if(arg.startsWith(" ")) arg else " " + arg)
|
||||
Watched.executeContinuously(w, s, arg, repeat)
|
||||
}
|
||||
}
|
||||
|
||||
def history = Command.custom(historyParser, historyHelp)
|
||||
def historyParser(s: State): Parser[() => State] =
|
||||
Command.applyEffect(HistoryCommands.actionParser) { histFun =>
|
||||
val logError = (msg: String) => s.log.error(msg)
|
||||
val hp = s get historyPath.key getOrElse None
|
||||
val lines = hp.toList.flatMap( p => IO.readLines(p) ).toIndexedSeq
|
||||
histFun( complete.History(lines, hp, logError) ) match
|
||||
{
|
||||
case Some(commands) =>
|
||||
commands foreach println //printing is more appropriate than logging
|
||||
(commands ::: s).continue
|
||||
case None => s.fail
|
||||
}
|
||||
}
|
||||
|
||||
def eval = Command.single(EvalCommand, evalBrief, evalDetailed) { (s, arg) =>
|
||||
val log = logger(s)
|
||||
val extracted = Project extract s
|
||||
|
|
@ -482,7 +252,7 @@ object BuiltinCommands
|
|||
/** Determines the log file that last* commands should operate on. See also isLastOnly. */
|
||||
def lastLogFile(s: State) =
|
||||
{
|
||||
val backing = CommandSupport.globalLogging(s).backing
|
||||
val backing = s.globalLogging.backing
|
||||
if(isLastOnly(s)) backing.last else Some(backing.file)
|
||||
}
|
||||
|
||||
|
|
@ -514,7 +284,7 @@ object BuiltinCommands
|
|||
}
|
||||
|
||||
def act = Command.customHelp(Act.actParser, actHelp)
|
||||
def actHelp = (s: State) => CommandSupport.showHelp ++ keysHelp(s)
|
||||
def actHelp = (s: State) => CommandStrings.showHelp ++ keysHelp(s)
|
||||
def keysHelp(s: State): Help =
|
||||
if(Project.isProjectLoaded(s))
|
||||
Help.detailOnly(taskDetail(s))
|
||||
|
|
@ -530,16 +300,9 @@ object BuiltinCommands
|
|||
for( (uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, log)
|
||||
s
|
||||
}
|
||||
def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State =
|
||||
(s get key) match {
|
||||
case None => logger(s).error(ifMissing); s.fail
|
||||
case Some(nav) => f(nav)
|
||||
}
|
||||
|
||||
def project = Command.make(ProjectCommand, projectBrief, projectDetailed)(ProjectNavigation.command)
|
||||
|
||||
def exit = Command.command(TerminateAction, exitBrief, exitBrief ) ( _ exit true )
|
||||
|
||||
def loadFailed = Command.command(LoadFailed)(handleLoadFailed)
|
||||
@tailrec def handleLoadFailed(s: State): State =
|
||||
{
|
||||
|
|
@ -576,71 +339,4 @@ object BuiltinCommands
|
|||
SessionSettings.checkSession(session, s)
|
||||
Project.setProject(session, structure, s)
|
||||
}
|
||||
|
||||
def handleException(e: Throwable, s: State): State =
|
||||
handleException(e, s, logger(s))
|
||||
def handleException(e: Throwable, s: State, log: Logger): State =
|
||||
{
|
||||
e match
|
||||
{
|
||||
case _: Incomplete => () // already handled by evaluateTask
|
||||
case _: NoMessageException => ()
|
||||
case ite: InvocationTargetException =>
|
||||
val cause = ite.getCause
|
||||
if(cause == null || cause == ite) logFullException(ite, log) else handleException(cause, s, log)
|
||||
case _: MessageOnlyException => log.error(e.toString)
|
||||
case _: Project.Uninitialized => logFullException(e, log, true)
|
||||
case _ => logFullException(e, log)
|
||||
}
|
||||
s.fail
|
||||
}
|
||||
def logFullException(e: Throwable, log: Logger, messageOnly: Boolean = false)
|
||||
{
|
||||
log.trace(e)
|
||||
log.error(if(messageOnly) e.getMessage else ErrorHandling reducedToString e)
|
||||
log.error("Use 'last' for the full log.")
|
||||
}
|
||||
|
||||
def addAlias(s: State, name: String, value: String): State =
|
||||
if(Command validID name) {
|
||||
val removed = removeAlias(s, name)
|
||||
if(value.isEmpty) removed else removed.copy(definedCommands = newAlias(name, value) +: removed.definedCommands)
|
||||
} else {
|
||||
System.err.println("Invalid alias name '" + name + "'.")
|
||||
s.fail
|
||||
}
|
||||
|
||||
def removeAliases(s: State): State = removeTagged(s, CommandAliasKey)
|
||||
def removeAlias(s: State, name: String): State = s.copy(definedCommands = s.definedCommands.filter(c => !isAliasNamed(name, c)) )
|
||||
|
||||
def removeTagged(s: State, tag: AttributeKey[_]): State = s.copy(definedCommands = removeTagged(s.definedCommands, tag))
|
||||
def removeTagged(as: Seq[Command], tag: AttributeKey[_]): Seq[Command] = as.filter(c => ! (c.tags contains tag))
|
||||
|
||||
def isAliasNamed(name: String, c: Command): Boolean = isNamed(name, getAlias(c))
|
||||
def isNamed(name: String, alias: Option[(String,String)]): Boolean = alias match { case None => false; case Some((n,_)) => name == n }
|
||||
|
||||
def getAlias(c: Command): Option[(String,String)] = c.tags get CommandAliasKey
|
||||
def printAlias(s: State, name: String): Unit = printAliases(aliases(s,(n,v) => n == name) )
|
||||
def printAliases(s: State): Unit = printAliases(allAliases(s))
|
||||
def printAliases(as: Seq[(String,String)]): Unit =
|
||||
for( (name,value) <- as)
|
||||
println("\t" + name + " = " + value)
|
||||
|
||||
def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1)
|
||||
def allAliases(s: State): Seq[(String,String)] = aliases(s, (n,v) => true)
|
||||
def aliases(s: State, pred: (String,String) => Boolean): Seq[(String,String)] =
|
||||
s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred)))
|
||||
|
||||
def newAlias(name: String, value: String): Command =
|
||||
Command.make(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
|
||||
def aliasBody(name: String, value: String)(state: State): Parser[() => State] =
|
||||
OptSpace ~> Parser(Command.combine(removeAlias(state,name).definedCommands)(state))(value)
|
||||
|
||||
def delegateToAlias(name: String, orElse: Parser[() => State])(state: State): Parser[() => State] =
|
||||
aliases(state, (nme,_) => nme == name).headOption match {
|
||||
case None => orElse
|
||||
case Some((n,v)) => aliasBody(n,v)(state)
|
||||
}
|
||||
|
||||
val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias", "Internal: marker for Commands created as aliases for another command.")
|
||||
}
|
||||
|
|
@ -203,7 +203,7 @@ object Project extends Init[Scope] with ProjectExtra
|
|||
val prompt = get(shellPrompt)
|
||||
val watched = get(watch)
|
||||
val commandDefs = allCommands.distinct.flatten[Command].map(_ tag (projectCommand, true))
|
||||
val newDefinedCommands = commandDefs ++ BuiltinCommands.removeTagged(s.definedCommands, projectCommand)
|
||||
val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged(s.definedCommands, projectCommand)
|
||||
val newAttrs = setCond(Watched.Configuration, watched, s.attributes).put(historyPath.key, history)
|
||||
s.copy(attributes = setCond(shellPrompt.key, prompt, newAttrs), definedCommands = newDefinedCommands)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ object Script
|
|||
val scriptArg = state.remainingCommands.headOption getOrElse error("No script file specified")
|
||||
val script = new File(scriptArg).getAbsoluteFile
|
||||
val hash = Hash.halve(Hash.toHex(Hash(script.getAbsolutePath)))
|
||||
val base = new File(CommandSupport.bootDirectory(state), hash)
|
||||
val base = new File(CommandUtil.bootDirectory(state), hash)
|
||||
IO.createDirectory(base)
|
||||
|
||||
val (eval, structure) = Load.defaultLoad(state, base, state.log)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,6 @@ object TaskData
|
|||
private[this] def fakeState(structure: BuildStructure): State =
|
||||
{
|
||||
val config = Keys.appConfiguration in Scope.GlobalScope get structure.data
|
||||
State(config.get, Nil, Set.empty, None, Nil, State.newHistory, AttributeMap.empty, State.Continue)
|
||||
State(config.get, Nil, Set.empty, None, Nil, State.newHistory, AttributeMap.empty, StandardMain.initialGlobalLogging, State.Continue)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import complete.HistoryCommands
|
||||
import scala.annotation.tailrec
|
||||
|
||||
import java.io.File
|
||||
import Path._
|
||||
|
||||
object BasicCommandStrings
|
||||
{
|
||||
val HelpCommand = "help"
|
||||
val Exit = "exit"
|
||||
val Quit = "quit"
|
||||
|
||||
/** The command name to terminate the program.*/
|
||||
val TerminateAction: String = Exit
|
||||
|
||||
def helpBrief = (HelpCommand + " [command]*", "Displays this help message or prints detailed help on requested commands.")
|
||||
def helpDetailed = """
|
||||
If an argument is provided, this prints detailed help for that command.
|
||||
Otherwise, this prints a help summary."""
|
||||
|
||||
def historyHelp = Help.briefDetail(HistoryCommands.descriptions)
|
||||
|
||||
def exitBrief = "Terminates the build."
|
||||
|
||||
def ReadCommand = "<"
|
||||
def ReadFiles = " file1 file2 ..."
|
||||
def ReadBrief = (ReadCommand + " <file>*", "Reads command lines from the provided files.")
|
||||
def ReadDetailed =
|
||||
ReadCommand + ReadFiles + """
|
||||
|
||||
Reads the lines from the given files and inserts them as commands.
|
||||
All empty lines and lines that start with '#' are ignored.
|
||||
If a file does not exist or is not readable, this command fails.
|
||||
|
||||
All the lines from all the files are read before any of the commands
|
||||
are executed. Thus, if any file is not readable, none of commands
|
||||
from any of the files (even the existing ones) will be run.
|
||||
|
||||
You probably need to escape this command if entering it at your shell."""
|
||||
|
||||
def ApplyCommand = "apply"
|
||||
def ApplyBrief = (ApplyCommand + " <module-name>*", ApplyDetailed)
|
||||
def ApplyDetailed = "Transforms the current State by calling <module-name>.apply(currentState) for each listed module name."
|
||||
|
||||
def RebootCommand = "reboot"
|
||||
def RebootSummary = RebootCommand + " [full]"
|
||||
def RebootBrief = (RebootSummary, "Reboots sbt and then executes the remaining commands.")
|
||||
def RebootDetailed =
|
||||
RebootSummary + """
|
||||
|
||||
This command is equivalent to exiting sbt, restarting, and running the
|
||||
remaining commands with the exception that the JVM is not shut down.
|
||||
|
||||
If 'full' is specified, the boot directory (`~/.sbt/boot` by default)
|
||||
is deleted before restarting. This forces an update of sbt and Scala
|
||||
and is useful when working with development versions of sbt or Scala."""
|
||||
|
||||
def Multi = ";"
|
||||
def MultiBrief = (Multi + " <command> (" + Multi + " <command>)*", "Runs the provided semicolon-separated commands.")
|
||||
def MultiDetailed =
|
||||
Multi + " command1 " + Multi + """ command2 ...
|
||||
|
||||
Runs the specified commands."""
|
||||
|
||||
def AppendCommand = "append"
|
||||
def AppendLastBrief = (AppendCommand + " <command>", AppendLastDetailed)
|
||||
def AppendLastDetailed = "Appends 'command' to list of commands to run."
|
||||
|
||||
val AliasCommand = "alias"
|
||||
def AliasBrief = (AliasCommand, "Adds, removes, or prints command aliases.")
|
||||
def AliasDetailed =
|
||||
AliasCommand + """
|
||||
|
||||
Prints a list of defined aliases.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name
|
||||
|
||||
Prints the alias defined for `name`.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name=value
|
||||
|
||||
Sets the alias `name` to `value`, replacing any existing alias with that name.
|
||||
Whenever `name` is entered, the corresponding `value` is run.
|
||||
If any argument is provided to `name`, it is appended as argument to `value`.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name=
|
||||
|
||||
Removes the alias for `name`."""
|
||||
|
||||
def Shell = "shell"
|
||||
def ShellBrief = ShellDetailed
|
||||
def ShellDetailed = "Provides an interactive prompt from which commands can be run."
|
||||
|
||||
def ClearOnFailure = "--"
|
||||
def OnFailure = "-"
|
||||
def OnFailureBrief = (OnFailure + " command", "Registers 'command' to run if a command fails.")
|
||||
def OnFailureDetailed =
|
||||
OnFailure + """ command
|
||||
|
||||
Registers 'command' to run when a command fails to complete normally.
|
||||
|
||||
Only one failure command may be registered at a time, so this command
|
||||
replaces the previous command if there is one.
|
||||
|
||||
The failure command resets when it runs once, so it must be added
|
||||
again if desired."""
|
||||
|
||||
def IfLast = "iflast"
|
||||
def IfLastBrief = (IfLast + " <command>", IfLastCommon)
|
||||
def IfLastCommon = "If there are no more commands after this one, 'command' is run."
|
||||
def IfLastDetailed =
|
||||
IfLast + """ command
|
||||
|
||||
""" + IfLastCommon
|
||||
|
||||
val ContinuousExecutePrefix = "~"
|
||||
def continuousBriefHelp = (ContinuousExecutePrefix + " <command>", "Executes the specified command whenever source files change.")
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
package sbt
|
||||
|
||||
import complete.{DefaultParsers, HistoryCommands, Parser}
|
||||
import DefaultParsers._
|
||||
import Types.{const,idFun}
|
||||
import Function.tupled
|
||||
import Command.applyEffect
|
||||
import State.FailureWall
|
||||
import HistoryCommands.{Start => HistoryPrefix}
|
||||
import BasicCommandStrings._
|
||||
import CommandUtil._
|
||||
import BasicKeys._
|
||||
|
||||
import java.io.File
|
||||
|
||||
object BasicCommands
|
||||
{
|
||||
lazy val allBasicCommands = Seq(nop, ignore, help, multi, ifLast, append, setOnFailure, clearOnFailure, reboot, call, exit, continuous, history, shell, read, alias)
|
||||
|
||||
def nop = Command.custom(s => success(() => s))
|
||||
def ignore = Command.command(FailureWall)(idFun)
|
||||
|
||||
def help = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
|
||||
|
||||
def helpParser(s: State) =
|
||||
{
|
||||
val h = (Help.empty /: s.definedCommands)(_ ++ _.help(s))
|
||||
val helpCommands = h.detail.keySet
|
||||
val args = (token(Space) ~> token( NotSpace examples helpCommands )).*
|
||||
applyEffect(args)(runHelp(s, h))
|
||||
}
|
||||
|
||||
def runHelp(s: State, h: Help)(args: Seq[String]): State =
|
||||
{
|
||||
val message =
|
||||
if(args.isEmpty)
|
||||
aligned(" ", " ", h.brief).mkString("\n", "\n", "\n")
|
||||
else
|
||||
detail(args, h.detail) mkString("\n", "\n\n", "\n")
|
||||
System.out.println(message)
|
||||
s
|
||||
}
|
||||
def detail(selected: Seq[String], detailMap: Map[String, String]): Seq[String] =
|
||||
selected.distinct flatMap { detailMap get _ }
|
||||
|
||||
def multiParser(s: State): Parser[Seq[String]] =
|
||||
{
|
||||
val nonSemi = token(charClass(_ != ';').+, hide= const(true))
|
||||
( token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser&nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim) ).+
|
||||
}
|
||||
|
||||
def multiApplied(s: State) =
|
||||
Command.applyEffect( multiParser(s) )( _ ::: s )
|
||||
|
||||
def multi = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed) )
|
||||
|
||||
lazy val otherCommandParser = (s: State) => token(OptSpace ~> combinedLax(s, any.+) )
|
||||
def combinedLax(s: State, any: Parser[_]): Parser[String] =
|
||||
matched(s.combinedParser | token(any, hide= const(true)))
|
||||
|
||||
def ifLast = Command(IfLast, IfLastBrief, IfLastDetailed)(otherCommandParser) { (s, arg) =>
|
||||
if(s.remainingCommands.isEmpty) arg :: s else s
|
||||
}
|
||||
def append = Command(AppendCommand, AppendLastBrief, AppendLastDetailed)(otherCommandParser) { (s, arg) =>
|
||||
s.copy(remainingCommands = s.remainingCommands :+ arg)
|
||||
}
|
||||
|
||||
def setOnFailure = Command(OnFailure, OnFailureBrief, OnFailureDetailed)(otherCommandParser) { (s, arg) =>
|
||||
s.copy(onFailure = Some(arg))
|
||||
}
|
||||
def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
|
||||
|
||||
def reboot = Command(RebootCommand, RebootBrief, RebootDetailed)(rebootParser) { (s, full) =>
|
||||
s.reboot(full)
|
||||
}
|
||||
def rebootParser(s: State) = token(Space ~> "full" ^^^ true) ?? false
|
||||
|
||||
def call = Command(ApplyCommand, ApplyBrief, ApplyDetailed)(_ => spaceDelimited("<class name>")) { (state,args) =>
|
||||
val loader = getClass.getClassLoader
|
||||
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader))
|
||||
(state /: loaded) { case (s, obj: (State => State)) => obj(s) }
|
||||
}
|
||||
|
||||
def exit = Command.command(TerminateAction, exitBrief, exitBrief ) ( _ exit true )
|
||||
|
||||
|
||||
def continuous =
|
||||
Command(ContinuousExecutePrefix, Help(continuousBriefHelp) )(otherCommandParser) { (s, arg) =>
|
||||
withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w =>
|
||||
val repeat = ContinuousExecutePrefix + (if(arg.startsWith(" ")) arg else " " + arg)
|
||||
Watched.executeContinuously(w, s, arg, repeat)
|
||||
}
|
||||
}
|
||||
|
||||
def history = Command.custom(historyParser, BasicCommandStrings.historyHelp)
|
||||
def historyParser(s: State): Parser[() => State] =
|
||||
Command.applyEffect(HistoryCommands.actionParser) { histFun =>
|
||||
val logError = (msg: String) => s.log.error(msg)
|
||||
val hp = s get historyPath getOrElse None
|
||||
val lines = hp.toList.flatMap( p => IO.readLines(p) ).toIndexedSeq
|
||||
histFun( complete.History(lines, hp, logError) ) match
|
||||
{
|
||||
case Some(commands) =>
|
||||
commands foreach println //printing is more appropriate than logging
|
||||
(commands ::: s).continue
|
||||
case None => s.fail
|
||||
}
|
||||
}
|
||||
|
||||
def shell = Command.command(Shell, ShellBrief, ShellDetailed) { s =>
|
||||
val history = (s get historyPath) getOrElse Some(new File(s.baseDir, ".history"))
|
||||
val prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " }
|
||||
val reader = new FullReader(history, s.combinedParser)
|
||||
val line = reader.readLine(prompt)
|
||||
line match {
|
||||
case Some(line) =>
|
||||
val newState = s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands)
|
||||
if(line.trim.isEmpty) newState else newState.clearGlobalLog
|
||||
case None => s
|
||||
}
|
||||
}
|
||||
|
||||
def read = Command.make(ReadCommand, ReadBrief, ReadDetailed)(s => applyEffect(readParser(s))(doRead(s)) )
|
||||
def readParser(s: State) =
|
||||
{
|
||||
val files = (token(Space) ~> fileParser(s.baseDir)).+
|
||||
val portAndSuccess = token(OptSpace) ~> Port
|
||||
portAndSuccess || files
|
||||
}
|
||||
def doRead(s: State)(arg: Either[Int, Seq[File]]): State =
|
||||
arg match
|
||||
{
|
||||
case Left(portAndSuccess) =>
|
||||
val port = math.abs(portAndSuccess)
|
||||
val previousSuccess = portAndSuccess >= 0
|
||||
readMessage(port, previousSuccess) match
|
||||
{
|
||||
case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy(onFailure = Some(ReadCommand + " " + (-port)))
|
||||
case None =>
|
||||
System.err.println("Connection closed.")
|
||||
s.fail
|
||||
}
|
||||
case Right(from) =>
|
||||
val notFound = notReadable(from)
|
||||
if(notFound.isEmpty)
|
||||
readLines(from) ::: s // this means that all commands from all files are loaded, parsed, and inserted before any are executed
|
||||
else {
|
||||
s.log.error("Command file(s) not readable: \n\t" + notFound.mkString("\n\t"))
|
||||
s
|
||||
}
|
||||
}
|
||||
private def readMessage(port: Int, previousSuccess: Boolean): Option[String] =
|
||||
{
|
||||
// split into two connections because this first connection ends the previous communication
|
||||
xsbt.IPC.client(port) { _.send(previousSuccess.toString) }
|
||||
// and this second connection starts the next communication
|
||||
xsbt.IPC.client(port) { ipc =>
|
||||
val message = ipc.receive
|
||||
if(message eq null) None else Some(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def alias = Command.make(AliasCommand, AliasBrief, AliasDetailed) { s =>
|
||||
val name = token(OpOrID.examples( aliasNames(s) : _*) )
|
||||
val assign = token(OptSpace ~ '=' ~ OptSpace)
|
||||
val sfree = removeAliases(s)
|
||||
val to = matched(sfree.combinedParser, partial = true) | any.+.string
|
||||
val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?)
|
||||
applyEffect(base)(t => runAlias(s, t) )
|
||||
}
|
||||
|
||||
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
|
||||
args match
|
||||
{
|
||||
case None => printAliases(s); s
|
||||
case Some(x ~ None) if !x.isEmpty => printAlias(s, x.trim); s
|
||||
case Some(name ~ Some(None)) => removeAlias(s, name.trim)
|
||||
case Some(name ~ Some(Some(value))) => addAlias(s, name.trim, value.trim)
|
||||
}
|
||||
def addAlias(s: State, name: String, value: String): State =
|
||||
if(Command validID name) {
|
||||
val removed = removeAlias(s, name)
|
||||
if(value.isEmpty) removed else removed.copy(definedCommands = newAlias(name, value) +: removed.definedCommands)
|
||||
} else {
|
||||
System.err.println("Invalid alias name '" + name + "'.")
|
||||
s.fail
|
||||
}
|
||||
|
||||
def removeAliases(s: State): State = removeTagged(s, CommandAliasKey)
|
||||
def removeAlias(s: State, name: String): State = s.copy(definedCommands = s.definedCommands.filter(c => !isAliasNamed(name, c)) )
|
||||
|
||||
def removeTagged(s: State, tag: AttributeKey[_]): State = s.copy(definedCommands = removeTagged(s.definedCommands, tag))
|
||||
def removeTagged(as: Seq[Command], tag: AttributeKey[_]): Seq[Command] = as.filter(c => ! (c.tags contains tag))
|
||||
|
||||
def isAliasNamed(name: String, c: Command): Boolean = isNamed(name, getAlias(c))
|
||||
def isNamed(name: String, alias: Option[(String,String)]): Boolean = alias match { case None => false; case Some((n,_)) => name == n }
|
||||
|
||||
def getAlias(c: Command): Option[(String,String)] = c.tags get CommandAliasKey
|
||||
def printAlias(s: State, name: String): Unit = printAliases(aliases(s,(n,v) => n == name) )
|
||||
def printAliases(s: State): Unit = printAliases(allAliases(s))
|
||||
def printAliases(as: Seq[(String,String)]): Unit =
|
||||
for( (name,value) <- as)
|
||||
println("\t" + name + " = " + value)
|
||||
|
||||
def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1)
|
||||
def allAliases(s: State): Seq[(String,String)] = aliases(s, (n,v) => true)
|
||||
def aliases(s: State, pred: (String,String) => Boolean): Seq[(String,String)] =
|
||||
s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred)))
|
||||
|
||||
def newAlias(name: String, value: String): Command =
|
||||
Command.make(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
|
||||
def aliasBody(name: String, value: String)(state: State): Parser[() => State] =
|
||||
OptSpace ~> Parser(Command.combine(removeAlias(state,name).definedCommands)(state))(value)
|
||||
|
||||
def delegateToAlias(name: String, orElse: Parser[() => State])(state: State): Parser[() => State] =
|
||||
aliases(state, (nme,_) => nme == name).headOption match {
|
||||
case None => orElse
|
||||
case Some((n,v)) => aliasBody(n,v)(state)
|
||||
}
|
||||
|
||||
val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias", "Internal: marker for Commands created as aliases for another command.")
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
|
||||
object BasicKeys
|
||||
{
|
||||
val historyPath = AttributeKey[Option[File]]("history", "The location where command line history is persisted.")
|
||||
val shellPrompt = AttributeKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.")
|
||||
val watch = AttributeKey[Watched]("watch", "Continuous execution configuration.")
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
|
||||
object CommandUtil
|
||||
{
|
||||
def readLines(files: Seq[File]): Seq[String] = files flatMap (line => IO.readLines(line)) flatMap processLine
|
||||
def processLine(s: String) = { val trimmed = s.trim; if(ignoreLine(trimmed)) None else Some(trimmed) }
|
||||
def ignoreLine(s: String) = s.isEmpty || s.startsWith("#")
|
||||
|
||||
private def canRead = (_: File).canRead
|
||||
def notReadable(files: Seq[File]): Seq[File] = files filterNot canRead
|
||||
def readable(files: Seq[File]): Seq[File] = files filter canRead
|
||||
|
||||
// slightly better fallback in case of older launcher
|
||||
def bootDirectory(state: State): File =
|
||||
try { state.configuration.provider.scalaProvider.launcher.bootDirectory }
|
||||
catch { case e: NoSuchMethodError => new File(".").getAbsoluteFile }
|
||||
|
||||
def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] =
|
||||
{
|
||||
val width = in.map(_._1.length).max
|
||||
in.map { case (a, b) => (" " + fill(a, width) + sep + b) }
|
||||
}
|
||||
def fill(s: String, size: Int) = s + " " * math.max(size - s.length, 0)
|
||||
|
||||
def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State =
|
||||
(s get key) match {
|
||||
case None => s.log.error(ifMissing); s.fail
|
||||
case Some(nav) => f(nav)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009, 2010, 2011 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import java.io.{File, PrintWriter}
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
|
||||
object MainLoop
|
||||
{
|
||||
/** Entry point to run the remaining commands in State with managed global logging.*/
|
||||
def runLogged(state: State): xsbti.MainResult =
|
||||
runLoggedLoop(state, state.globalLogging.backing)
|
||||
|
||||
/** Run loop that evaluates remaining commands and manages changes to global logging configuration.*/
|
||||
@tailrec def runLoggedLoop(state: State, logBacking: GlobalLogBacking): xsbti.MainResult =
|
||||
runAndClearLast(state, logBacking) match {
|
||||
case ret: Return => // delete current and last log files when exiting normally
|
||||
logBacking.file.delete()
|
||||
deleteLastLog(logBacking)
|
||||
ret.result
|
||||
case clear: ClearGlobalLog => // delete previous log file, move current to previous, and start writing to a new file
|
||||
deleteLastLog(logBacking)
|
||||
runLoggedLoop(clear.state, logBacking.shiftNew())
|
||||
case keep: KeepGlobalLog => // make previous log file the current log file
|
||||
logBacking.file.delete
|
||||
runLoggedLoop(keep.state, logBacking.unshift)
|
||||
}
|
||||
|
||||
/** Runs the next sequence of commands, cleaning up global logging after any exceptions. */
|
||||
def runAndClearLast(state: State, logBacking: GlobalLogBacking): RunNext =
|
||||
try
|
||||
runWithNewLog(state, logBacking)
|
||||
catch {
|
||||
case e: xsbti.FullReload =>
|
||||
deleteLastLog(logBacking)
|
||||
throw e // pass along a reboot request
|
||||
case e =>
|
||||
System.err.println("sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file)
|
||||
deleteLastLog(logBacking)
|
||||
throw e
|
||||
}
|
||||
|
||||
/** Deletes the previous global log file. */
|
||||
def deleteLastLog(logBacking: GlobalLogBacking): Unit =
|
||||
logBacking.last.foreach(_.delete())
|
||||
|
||||
/** Runs the next sequence of commands with global logging in place. */
|
||||
def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext =
|
||||
Using.fileWriter(append = true)(logBacking.file) { writer =>
|
||||
val out = new java.io.PrintWriter(writer)
|
||||
val loggedState = state.copy(globalLogging = logBacking.newLogger(out, logBacking))
|
||||
try run(loggedState) finally out.close()
|
||||
}
|
||||
sealed trait RunNext
|
||||
final class ClearGlobalLog(val state: State) extends RunNext
|
||||
final class KeepGlobalLog(val state: State) extends RunNext
|
||||
final class Return(val result: xsbti.MainResult) extends RunNext
|
||||
|
||||
/** Runs the next sequence of commands that doesn't require global logging changes.*/
|
||||
@tailrec def run(state: State): RunNext =
|
||||
state.next match
|
||||
{
|
||||
case State.Continue => run(next(state))
|
||||
case State.ClearGlobalLog => new ClearGlobalLog(state.continue)
|
||||
case State.KeepLastLog => new KeepGlobalLog(state.continue)
|
||||
case ret: State.Return => new Return(ret.result)
|
||||
}
|
||||
|
||||
def next(state: State): State =
|
||||
ErrorHandling.wideConvert { state.process(Command.process) } match
|
||||
{
|
||||
case Right(s) => s
|
||||
case Left(t: xsbti.FullReload) => throw t
|
||||
case Left(t) => handleException(t, state)
|
||||
}
|
||||
|
||||
def handleException(e: Throwable, s: State): State =
|
||||
handleException(e, s, s.log)
|
||||
def handleException(e: Throwable, s: State, log: Logger): State =
|
||||
{
|
||||
e match
|
||||
{
|
||||
case _: AlreadyHandledException | _: UnprintableException => ()
|
||||
case ite: InvocationTargetException =>
|
||||
val cause = ite.getCause
|
||||
if(cause == null || cause == ite) logFullException(ite, log) else handleException(cause, s, log)
|
||||
case _: MessageOnlyException => log.error(e.toString)
|
||||
case _ => logFullException(e, log)
|
||||
}
|
||||
s.fail
|
||||
}
|
||||
def logFullException(e: Throwable, log: Logger)
|
||||
{
|
||||
log.trace(e)
|
||||
log.error(ErrorHandling reducedToString e)
|
||||
log.error("Use 'last' for the full log.")
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ package sbt
|
|||
|
||||
import java.io.File
|
||||
import java.util.concurrent.Callable
|
||||
import CommandSupport.{FailureWall, logger}
|
||||
|
||||
/**
|
||||
Data structure representing all command execution information.
|
||||
|
|
@ -27,6 +26,7 @@ final case class State(
|
|||
remainingCommands: Seq[String],
|
||||
history: State.History,
|
||||
attributes: AttributeMap,
|
||||
globalLogging: GlobalLogging,
|
||||
next: State.Next
|
||||
) extends Identity {
|
||||
lazy val combinedParser = Command.combine(definedCommands)(this)
|
||||
|
|
@ -112,6 +112,8 @@ trait StateOps {
|
|||
|
||||
object State
|
||||
{
|
||||
final val FailureWall = "---"
|
||||
|
||||
/** Represents the next action for the command processor.*/
|
||||
sealed trait Next
|
||||
/** Indicates that the command processor should process the next command.*/
|
||||
|
|
@ -175,7 +177,7 @@ object State
|
|||
def update[T](key: AttributeKey[T])(f: Option[T] => T): State = put(key, f(get(key)))
|
||||
def has(key: AttributeKey[_]) = s.attributes contains key
|
||||
def remove(key: AttributeKey[_]) = s.copy(attributes = s.attributes remove key)
|
||||
def log = CommandSupport.logger(s)
|
||||
def log = s.globalLogging.full
|
||||
def fail =
|
||||
{
|
||||
val remaining = s.remainingCommands.dropWhile(_ != FailureWall)
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
*/
|
||||
package sbt
|
||||
|
||||
import CommandSupport.{ClearOnFailure,FailureWall}
|
||||
import BasicCommandStrings.ClearOnFailure
|
||||
import State.FailureWall
|
||||
import annotation.tailrec
|
||||
import java.io.File
|
||||
import Types.const
|
||||
|
|
@ -63,7 +64,7 @@ object Watched
|
|||
catch { case e: Exception =>
|
||||
val log = s.log
|
||||
log.error("Error occurred obtaining files to watch. Terminating continuous execution...")
|
||||
BuiltinCommands.handleException(e, s, log)
|
||||
MainLoop.handleException(e, s, log)
|
||||
(false, watchState, s.fail)
|
||||
}
|
||||
|
||||
|
|
@ -111,8 +111,11 @@ object Sbt extends Build
|
|||
classfileSub, classpathSub, compileIncrementalSub, compilePersistSub, compilerSub, completeSub, apiSub,
|
||||
interfaceSub, ioSub, ivySub, logSub, processSub, runSub, stdTaskSub, taskSub, trackingSub, testingSub)
|
||||
|
||||
lazy val commandSub = testedBaseProject(commandPath, "Command") dependsOn(interfaceSub, ioSub, launchInterfaceSub, logSub, completeSub, classpathSub)
|
||||
|
||||
// The main integration project for sbt. It brings all of the subsystems together, configures them, and provides for overriding conventions.
|
||||
lazy val mainSub = testedBaseProject(mainPath, "Main") dependsOn(actionsSub, interfaceSub, ioSub, ivySub, launchInterfaceSub, logSub, processSub, runSub)
|
||||
lazy val mainSub = testedBaseProject(mainPath, "Main") dependsOn(actionsSub, interfaceSub, ioSub, ivySub, launchInterfaceSub, logSub, processSub, runSub, commandSub)
|
||||
|
||||
// Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object
|
||||
// technically, we need a dependency on all of mainSub's dependencies, but we don't do that since this is strictly an integration project
|
||||
// with the sole purpose of providing certain identifiers without qualification (with a package object)
|
||||
|
|
@ -126,6 +129,7 @@ object Sbt extends Build
|
|||
def utilPath = file("util")
|
||||
def compilePath = file("compile")
|
||||
def mainPath = file("main")
|
||||
def commandPath = mainPath / "command"
|
||||
def scriptedPath = file("scripted")
|
||||
|
||||
def sbtSettings = Seq(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
package object sbt extends sbt.std.TaskExtra with sbt.Types with sbt.ProcessExtra with sbt.impl.DependencyBuilders
|
||||
with sbt.PathExtra with sbt.ProjectExtra with sbt.DependencyFilterExtra with sbt.BuildExtra
|
||||
{
|
||||
@deprecated("Renamed to CommandStrings.", "0.12.0")
|
||||
val CommandSupport = CommandStrings
|
||||
|
||||
@deprecated("Use SettingKey, which is a drop-in replacement.", "0.11.1")
|
||||
type ScopedSetting[T] = SettingKey[T]
|
||||
@deprecated("Use TaskKey, which is a drop-in replacement.", "0.11.1")
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import Incomplete.{Error, Value => IValue}
|
|||
* @param causes a list of incompletions that prevented `node` from completing
|
||||
* @param directCause the exception that caused `node` to not complete */
|
||||
final case class Incomplete(node: Option[AnyRef], tpe: IValue = Error, message: Option[String] = None, causes: Seq[Incomplete] = Nil, directCause: Option[Throwable] = None)
|
||||
extends Exception(message.orNull, directCause.orNull) {
|
||||
extends Exception(message.orNull, directCause.orNull) with UnprintableException {
|
||||
override def toString = "Incomplete(node=" + node + ", tpe=" + tpe + ", msg=" + message + ", causes=" + causes + ", directCause=" + directCause +")"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ trait Init[Scope]
|
|||
if(dist < 0) None else Some(dist)
|
||||
}
|
||||
|
||||
final class Uninitialized(val undefined: Seq[Undefined], msg: String) extends Exception(msg)
|
||||
final class Uninitialized(val undefined: Seq[Undefined], override val toString: String) extends Exception(toString)
|
||||
final class Undefined(val definingKey: ScopedKey[_], val referencedKey: ScopedKey[_])
|
||||
final class RuntimeUndefined(val undefined: Seq[Undefined]) extends RuntimeException("References to undefined settings at runtime.")
|
||||
def Undefined(definingKey: ScopedKey[_], referencedKey: ScopedKey[_]): Undefined = new Undefined(definingKey, referencedKey)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,11 @@
|
|||
package sbt
|
||||
|
||||
final class MessageOnlyException(override val toString: String) extends RuntimeException(toString)
|
||||
final class NoMessageException extends RuntimeException
|
||||
|
||||
/** A dummy exception for the top-level exception handler to know that an exception
|
||||
* has been handled, but is being passed further up to indicate general failure. */
|
||||
final class AlreadyHandledException extends RuntimeException
|
||||
|
||||
/** A marker trait for a top-level exception handler to know that this exception
|
||||
* doesn't make sense to display. */
|
||||
trait UnprintableException extends Throwable
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.{File, PrintWriter}
|
||||
|
||||
final case class GlobalLogging(full: Logger, backed: ConsoleLogger, backing: GlobalLogBacking)
|
||||
final case class GlobalLogBacking(file: File, last: Option[File], newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: () => File)
|
||||
{
|
||||
def shift(newFile: File) = GlobalLogBacking(newFile, Some(file), newLogger, newBackingFile)
|
||||
def shiftNew() = shift(newBackingFile())
|
||||
def unshift = GlobalLogBacking(last getOrElse file, None, newLogger, newBackingFile)
|
||||
}
|
||||
object GlobalLogBacking
|
||||
{
|
||||
def apply(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File): GlobalLogBacking =
|
||||
GlobalLogBacking(newBackingFile, None, newLogger, newBackingFile _)
|
||||
}
|
||||
object GlobalLogging
|
||||
{
|
||||
def initial(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File): GlobalLogging =
|
||||
{
|
||||
val log = ConsoleLogger()
|
||||
GlobalLogging(log, log, GlobalLogBacking(newLogger, newBackingFile))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package sbt
|
||||
|
||||
import java.io.PrintWriter
|
||||
|
||||
object MainLogging
|
||||
{
|
||||
def multiLogger(config: MultiLoggerConfig): Logger =
|
||||
{
|
||||
import config._
|
||||
val multi = new MultiLogger(console :: backed :: extra)
|
||||
// sets multi to the most verbose for clients that inspect the current level
|
||||
multi setLevel Level.unionAll(backingLevel :: screenLevel :: extra.map(_.getLevel))
|
||||
// set the specific levels
|
||||
console setLevel screenLevel
|
||||
backed setLevel backingLevel
|
||||
console setTrace screenTrace
|
||||
backed setTrace backingTrace
|
||||
multi: Logger
|
||||
}
|
||||
def globalDefault(writer: PrintWriter, backing: GlobalLogBacking): GlobalLogging =
|
||||
{
|
||||
val backed = defaultBacked()(writer)
|
||||
val full = multiLogger(defaultMultiConfig( backed ) )
|
||||
GlobalLogging(full, backed, backing)
|
||||
}
|
||||
|
||||
def defaultMultiConfig(backing: AbstractLogger): MultiLoggerConfig =
|
||||
new MultiLoggerConfig(defaultScreen, backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue)
|
||||
|
||||
def defaultScreen: AbstractLogger = ConsoleLogger()
|
||||
|
||||
def defaultBacked(useColor: Boolean = ConsoleLogger.formatEnabled): PrintWriter => ConsoleLogger =
|
||||
to => ConsoleLogger(ConsoleLogger.printWriterOut(to), useColor = useColor) // TODO: should probably filter ANSI codes when useColor=false
|
||||
}
|
||||
|
||||
final case class MultiLoggerConfig(console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger], screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int)
|
||||
Loading…
Reference in New Issue