Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

222 lines (194 sloc) 10.794 kb
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt
import java.io.File
import java.util.concurrent.Callable
/**
Data structure representing all command execution information.
@param configuration provides access to the launcher environment, including the application configuration, Scala versions, jvm/filesystem wide locking, and the launcher itself
@param definedCommands the list of command definitions that evaluate command strings. These may be modified to change the available commands.
@param onFailure the command to execute when another command fails. `onFailure` is cleared before the failure handling command is executed.
@param remainingCommands the sequence of commands to execute. This sequence may be modified to change the commands to be executed. Typically, the `::` and `:::` methods are used to prepend new commands to run.
@param exitHooks code to run before sbt exits, usually to ensure resources are cleaned up.
@param history tracks the recently executed commands
@param attributes custom command state. It is important to clean up attributes when no longer needed to avoid memory leaks and class loader leaks.
@param next the next action for the command processor to take. This may be to continue with the next command, adjust global logging, or exit.
*/
final case class State(
configuration: xsbti.AppConfiguration,
definedCommands: Seq[Command],
exitHooks: Set[ExitHook],
onFailure: Option[String],
remainingCommands: Seq[String],
history: State.History,
attributes: AttributeMap,
globalLogging: GlobalLogging,
next: State.Next
) extends Identity {
lazy val combinedParser = Command.combine(definedCommands)(this)
}
trait Identity {
override final def hashCode = super.hashCode
override final def equals(a: Any) = super.equals(a)
override final def toString = super.toString
}
/** StateOps methods to be merged at the next binary incompatible release (0.13.0). */
private[sbt] trait NewStateOps
{
def interactive: Boolean
def setInteractive(flag: Boolean): State
def classLoaderCache: classpath.ClassLoaderCache
def initializeClassLoaderCache: State
}
/** Convenience methods for State transformations and operations. */
trait StateOps {
def process(f: (String, State) => State): State
/** Schedules `commands` to be run before any remaining commands.*/
def ::: (commands: Seq[String]): State
/** Schedules `command` to be run before any remaining commands.*/
def :: (command: String): State
/** Sets the next command processing action to be to continue processing the next command.*/
def continue: State
/** Reboots sbt. A reboot restarts execution from the entry point of the launcher.
* A reboot is designed to be as close as possible to actually restarting the JVM without actually doing so.
* Because the JVM is not restarted, JVM exit hooks are not run.
* State.exitHooks should be used instead and those will be run before rebooting.
* If `full` is true, the boot directory is deleted before starting again.
* This command is currently implemented to not return, but may be implemented in the future to only reboot at the next command processing step. */
def reboot(full: Boolean): State
/** Sets the next command processing action to do.*/
def setNext(n: State.Next): State
@deprecated("Use setNext", "0.11.0") def setResult(ro: Option[xsbti.MainResult]): State
/** Restarts sbt without dropping loaded Scala classes. It is a shallower restart than `reboot`.
* This method takes a snapshot of the remaining commands and will resume executing those commands after reload.
* This means that any commands added to this State will be dropped.*/
def reload: State
/** Sets the next command processing action to be to rotate the global log and continue executing commands.*/
def clearGlobalLog: State
/** Sets the next command processing action to be to keep the previous log and continue executing commands. */
def keepLastLog: State
/** Sets the next command processing action to be to exit with a zero exit code if `ok` is true and a nonzero exit code if `ok` if false.*/
def exit(ok: Boolean): State
/** Marks the currently executing command as failing. This triggers failure handling by the command processor. See also `State.onFailure`*/
def fail: State
/** Schedules `newCommands` to be run after any remaining commands. */
def ++ (newCommands: Seq[Command]): State
/** Schedules `newCommand` to be run after any remaining commands. */
def + (newCommand: Command): State
/** Gets the value associated with `key` from the custom attributes map.*/
def get[T](key: AttributeKey[T]): Option[T]
/** Sets the value associated with `key` in the custom attributes map.*/
def put[T](key: AttributeKey[T], value: T): State
/** Removes the `key` and any associated value from the custom attributes map.*/
def remove(key: AttributeKey[_]): State
/** Sets the value associated with `key` in the custom attributes map by transforming the current value.*/
def update[T](key: AttributeKey[T])(f: Option[T] => T): State
/** Returns true if `key` exists in the custom attributes map, false if it does not exist.*/
def has(key: AttributeKey[_]): Boolean
/** The application base directory, which is not necessarily the current working directory.*/
def baseDir: File
/** The Logger used for general command logging.*/
def log: Logger
/** Evaluates the provided expression with a JVM-wide and machine-wide lock on `file`.*/
def locked[T](file: File)(t: => T): T
/** Runs any defined exitHooks and then clears them.*/
def runExitHooks(): State
/** Registers a new exit hook, which will run when sbt exits or restarts.*/
def addExitHook(f: => Unit): State
}
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.*/
object Continue extends Next
/** Indicates that the application should exit with the given result.*/
final class Return(val result: xsbti.MainResult) extends Next
/** Indicates that global logging should be rotated.*/
final object ClearGlobalLog extends Next
/** Indicates that the previous log file should be preserved instead of discarded.*/
final object KeepLastLog extends Next
/** Provides a list of recently executed commands. The commands are stored as processed instead of as entered by the user.
* @param executed the list of the most recently executed commands, with the most recent command first.
* @param maxSize the maximum number of commands to keep, or 0 to keep an unlimited number. */
final class History private[State](val executed: Seq[String], val maxSize: Int)
{
/** Adds `command` as the most recently executed command.*/
def :: (command: String): History =
{
val prependTo = if(maxSize > 0 && executed.size >= maxSize) executed.take(maxSize - 1) else executed
new History(command +: prependTo, maxSize)
}
/** Changes the maximum number of commands kept, adjusting the current history if necessary.*/
def setMaxSize(size: Int): History =
new History(if(size <= 0) executed else executed.take(size), size)
def current: String = executed.head
def previous: Option[String] = executed.drop(1).headOption
}
/** Constructs an empty command History with a default, finite command limit.*/
def newHistory = new History(Vector.empty, complete.HistoryCommands.MaxLines)
def defaultReload(state: State): Reboot =
{
val app = state.configuration.provider
new Reboot(app.scalaProvider.version, state.remainingCommands, app.id, state.configuration.baseDirectory)
}
private[sbt] implicit def newStateOps(s: State): NewStateOps = new NewStateOps {
def interactive = s.get(BasicKeys.interactive).getOrElse(false)
def setInteractive(i: Boolean) = s.put(BasicKeys.interactive, i)
def classLoaderCache: classpath.ClassLoaderCache = s get BasicKeys.classLoaderCache getOrElse newClassLoaderCache
def initializeClassLoaderCache = s.put(BasicKeys.classLoaderCache, newClassLoaderCache)
private[this] def newClassLoaderCache = new classpath.ClassLoaderCache(s.configuration.provider.scalaProvider.launcher.topLoader)
}
/** Provides operations and transformations on State. */
implicit def stateOps(s: State): StateOps = new StateOps {
def process(f: (String, State) => State): State =
s.remainingCommands match {
case Seq(x, xs @ _*) => f(x, s.copy(remainingCommands = xs, history = x :: s.history))
case Seq() => exit(true)
}
s.copy(remainingCommands = s.remainingCommands.drop(1))
def ::: (newCommands: Seq[String]): State = s.copy(remainingCommands = newCommands ++ s.remainingCommands)
def :: (command: String): State = (command :: Nil) ::: this
def ++ (newCommands: Seq[Command]): State = s.copy(definedCommands = (s.definedCommands ++ newCommands).distinct)
def + (newCommand: Command): State = this ++ (newCommand :: Nil)
def baseDir: File = s.configuration.baseDirectory
def setNext(n: Next) = s.copy(next = n)
def setResult(ro: Option[xsbti.MainResult]) = ro match { case None => continue; case Some(r) => setNext(new Return(r)) }
def continue = setNext(Continue)
def reboot(full: Boolean) ={ runExitHooks(); throw new xsbti.FullReload(s.remainingCommands.toArray, full) }
def reload = runExitHooks().setNext(new Return(defaultReload(s)))
def clearGlobalLog = setNext(ClearGlobalLog)
def keepLastLog = setNext(KeepLastLog)
def exit(ok: Boolean) = runExitHooks().setNext(new Return(Exit(if(ok) 0 else 1)))
def get[T](key: AttributeKey[T]) = s.attributes get key
def put[T](key: AttributeKey[T], value: T) = s.copy(attributes = s.attributes.put(key, value))
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 = s.globalLogging.full
def fail =
{
val remaining = s.remainingCommands.dropWhile(_ != FailureWall)
if(remaining.isEmpty)
applyOnFailure(s, Nil, exit(ok = false))
else
applyOnFailure(s, remaining, s.copy(remainingCommands = remaining))
}
private[this] def applyOnFailure(s: State, remaining: Seq[String], noHandler: => State): State =
s.onFailure match
{
case Some(c) => s.copy(remainingCommands = c +: remaining, onFailure = None)
case None => noHandler
}
def addExitHook(act: => Unit): State =
s.copy(exitHooks = s.exitHooks + ExitHook(act))
def runExitHooks(): State = {
ExitHooks.runExitHooks(s.exitHooks.toSeq)
s.copy(exitHooks = Set.empty)
}
def locked[T](file: File)(t: => T): T =
s.configuration.provider.scalaProvider.launcher.globalLock.apply(file, new Callable[T] { def call = t })
}
}
Jump to Line
Something went wrong with that request. Please try again.