Merge pull request #3101 from dwijnand/document-and-cleanup-commands-and-misc

Document & cleanup commands (& misc cleanups)
This commit is contained in:
eugene yokota 2017-04-13 15:09:03 -04:00 committed by GitHub
commit 6834def7b8
15 changed files with 619 additions and 378 deletions

View File

@ -315,7 +315,7 @@ lazy val docProjects: ScopeFilter = ScopeFilter(
inConfigurations(Compile)
)
def fullDocSettings = Util.baseScalacOptions ++ Docs.settings ++ Sxr.settings ++ Seq(
scalacOptions += "-Ymacro-no-expand", // for both sxr and doc
scalacOptions += "-Ymacro-expand:none", // for both sxr and doc
sources in sxr := {
val allSources = (sources ?? Nil).all(docProjects).value
allSources.flatten.distinct

View File

@ -17,15 +17,15 @@ object BasicCommandStrings {
val TerminateAction: String = Exit
def helpBrief = (HelpCommand, s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand <command>').")
def helpDetailed = HelpCommand + """
def helpDetailed = s"""$HelpCommand
Prints a help summary.
""" + HelpCommand + """ <command>
$HelpCommand <command>
Prints detailed help for command <command>.
""" + HelpCommand + """ <regular expression>
$HelpCommand <regular expression>
Searches the help according to the provided regular expression.
"""
@ -50,6 +50,7 @@ object BasicCommandStrings {
val detailed = levels.map(l => (l.toString, logLevelDetail(l))).toMap
Help(brief, detailed)
}
private[this] def logLevelDetail(level: Level.Value): String =
s"""$level
@ -130,24 +131,21 @@ object BasicCommandStrings {
val AliasCommand = "alias"
def AliasDetailed =
AliasCommand + """
s"""$AliasCommand
Prints a list of defined aliases.
""" +
AliasCommand + """ name
$AliasCommand name
Prints the alias defined for `name`.
""" +
AliasCommand + """ name=value
$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=
$AliasCommand name=
Removes the alias for `name`."""
@ -194,9 +192,9 @@ object BasicCommandStrings {
def IfLast = "iflast"
def IfLastCommon = "If there are no more commands after this one, 'command' is run."
def IfLastDetailed =
IfLast + """ <command>
s"""$IfLast <command>
""" + IfLastCommon
$IfLastCommon"""
val ContinuousExecutePrefix = "~"
def continuousDetail = "Executes the specified command whenever source files change."

View File

@ -2,7 +2,15 @@ package sbt
import sbt.util.Level
import sbt.internal.util.{ AttributeKey, FullReader }
import sbt.internal.util.complete.{ Completion, Completions, DefaultParsers, History => CHistory, HistoryCommands, Parser, TokenCompletions }
import sbt.internal.util.complete.{
Completion,
Completions,
DefaultParsers,
History => CHistory,
HistoryCommands,
Parser,
TokenCompletions
}
import sbt.internal.util.Types.{ const, idFun }
import sbt.internal.inc.classpath.ClasspathUtilities.toLoader
import sbt.internal.inc.ModuleUtilities
@ -19,32 +27,34 @@ import sbt.io.IO
import scala.util.control.NonFatal
object BasicCommands {
lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure,
stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, client, read, alias) ++ compatCommands
lazy val allBasicCommands: Seq[Command] = Seq(
nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure,
stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, client,
read, alias
) ++ compatCommands
def nop = Command.custom(s => success(() => s))
def ignore = Command.command(FailureWall)(idFun)
def nop: Command = Command.custom(s => success(() => s))
def ignore: Command = Command.command(FailureWall)(idFun)
def early: Command = Command.arb(earlyParser, earlyHelp)((s, other) => other :: s)
def early = Command.arb(earlyParser, earlyHelp) { (s, other) => other :: s }
private[this] def levelParser: Parser[String] =
token(Level.Debug.toString) | token(Level.Info.toString) | token(Level.Warn.toString) | token(Level.Error.toString)
private[this] def earlyParser: State => Parser[String] = (s: State) =>
(token(EarlyCommand + "(") flatMap { _ =>
otherCommandParser(s) <~ token(")")
}) |
(token("-") flatMap { _ =>
levelParser
})
Iterator(Level.Debug, Level.Info, Level.Warn, Level.Error) map (l => token(l.toString)) reduce (_ | _)
private[this] def earlyParser: State => Parser[String] = (s: State) => {
val p1 = token(EarlyCommand + "(") flatMap (_ => otherCommandParser(s) <~ token(")"))
val p2 = token("-") flatMap (_ => levelParser)
p1 | p2
}
private[this] def earlyHelp = Help(EarlyCommand, EarlyCommandBrief, EarlyCommandDetailed)
def help = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
def help: Command = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
def helpParser(s: State) =
def helpParser(s: State): Parser[() => State] =
{
val h = (Help.empty /: s.definedCommands) { (a, b) =>
a ++
(try b.help(s) catch { case NonFatal(ex) => Help.empty })
}
val h = (Help.empty /: s.definedCommands)((a, b) =>
a ++ (try b.help(s) catch { case NonFatal(_) => Help.empty }))
val helpCommands = h.detail.keySet
val spacedArg = singleArgument(helpCommands).?
applyEffect(spacedArg)(runHelp(s, h))
@ -52,24 +62,20 @@ object BasicCommands {
def runHelp(s: State, h: Help)(arg: Option[String]): State =
{
val message = try
Help.message(h, arg)
catch {
case NonFatal(ex) =>
ex.toString
}
val message = try Help.message(h, arg) catch { case NonFatal(ex) => ex.toString }
System.out.println(message)
s
}
def completionsCommand = Command.make(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser)
def completionsParser(state: State) =
{
val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s }
val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted)
def completionsCommand: Command =
Command(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser)(runCompletions(_)(_))
def completionsParser(state: State): Parser[String] = {
val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s }
val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted)
token(quotedOrUnquotedSingleArgument ?? "" examples ("", " "))
}
applyEffect(token(quotedOrUnquotedSingleArgument ?? "" examples ("", " ")))(runCompletions(state))
}
def runCompletions(state: State)(input: String): State = {
Parser.completions(state.combinedParser, input, 9).get map {
c => if (c.isEmpty) input else input + c.append
@ -82,34 +88,40 @@ object BasicCommands {
def multiParser(s: State): Parser[List[String]] =
{
val nonSemi = token(charClass(_ != ';').+, hide = const(true))
(token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim)).+ map { _.toList }
val semi = token(';' ~> OptSpace)
val part = semi flatMap (_ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace))
(part map (_.trim)).+ map (_.toList)
}
def multiApplied(s: State): Parser[() => State] =
Command.applyEffect(multiParser(s))(_ ::: s)
def multi = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed))
def multi: Command = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed))
lazy val otherCommandParser: State => Parser[String] =
(s: State) => token(OptSpace ~> combinedLax(s, NotSpaceClass ~ any.*))
lazy val otherCommandParser = (s: State) => token(OptSpace ~> combinedLax(s, NotSpaceClass ~ any.*))
def combinedLax(s: State, any: Parser[_]): Parser[String] =
matched(s.combinedParser | token(any, hide = const(true)))
def ifLast = Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser) { (s, arg) =>
if (s.remainingCommands.isEmpty) arg :: s else s
}
def append = Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser) { (s, arg) =>
s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source))
}
def ifLast: Command = Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)((s, arg) =>
if (s.remainingCommands.isEmpty) arg :: s else s)
def append: Command =
Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser)((s, arg) =>
s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source)))
def setOnFailure: Command =
Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser)((s, arg) =>
s.copy(onFailure = Some(Exec(arg, s.source))))
def setOnFailure = Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser) { (s, arg) =>
s.copy(onFailure = Some(Exec(arg, s.source)))
}
private[sbt] def compatCommands = Seq(
Command.command(Compat.ClearOnFailure) { s =>
s.log.warn(Compat.ClearOnFailureDeprecated)
s.copy(onFailure = None)
},
Command.arb(s => token(Compat.OnFailure, hide = const(true)).flatMap(x => otherCommandParser(s))) { (s, arg) =>
Command.arb(s => token(Compat.OnFailure, hide = const(true))
.flatMap(_ => otherCommandParser(s))) { (s, arg) =>
s.log.warn(Compat.OnFailureDeprecated)
s.copy(onFailure = Some(Exec(arg, s.source)))
},
@ -119,43 +131,55 @@ object BasicCommands {
}
)
def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
def stashOnFailure = Command.command(StashOnFailure)(s => s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten))
def popOnFailure = Command.command(PopOnFailure) { s =>
def clearOnFailure: Command = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
def stashOnFailure: Command = Command.command(StashOnFailure)(s =>
s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten))
def popOnFailure: Command = Command.command(PopOnFailure) { s =>
val stack = s.get(OnFailureStack).getOrElse(Nil)
val updated = if (stack.isEmpty) s.remove(OnFailureStack) else s.put(OnFailureStack, stack.tail)
updated.copy(onFailure = stack.headOption.flatten)
}
def reboot = Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser) { (s, full) =>
s.reboot(full)
}
def rebootParser(s: State) = token(Space ~> "full" ^^^ true) ?? false
def reboot: Command =
Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser)((s, full) => s reboot full)
def call = Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) {
def rebootParser(s: State): Parser[Boolean] = token(Space ~> "full" ^^^ true) ?? false
def call: Command = Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) {
case (state, (cp, args)) =>
val parentLoader = getClass.getClassLoader
state.log.info("Applying State transformations " + args.mkString(", ") + (if (cp.isEmpty) "" else " from " + cp.mkString(File.pathSeparator)))
def argsStr = args mkString ", "
def cpStr = cp mkString File.pathSeparator
def fromCpStr = if (cp.isEmpty) "" else s" from $cpStr"
state.log info s"Applying State transformations $argsStr$fromCpStr"
val loader = if (cp.isEmpty) parentLoader else toLoader(cp.map(f => new File(f)), parentLoader)
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader).asInstanceOf[State => State])
(state /: loaded)((s, obj) => obj(s))
}
def callParser: Parser[(Seq[String], Seq[String])] = token(Space) ~> ((classpathOptionParser ?? Nil) ~ rep1sep(className, token(Space)))
def callParser: Parser[(Seq[String], Seq[String])] =
token(Space) ~> ((classpathOptionParser ?? Nil) ~ rep1sep(className, token(Space)))
private[this] def className: Parser[String] =
{
val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.")
def single(s: String) = Completions.single(Completion.displayOnly(s))
val compl = TokenCompletions.fixed((seen, level) => if (seen.startsWith("-")) Completions.nil else single("<class name>"))
val compl = TokenCompletions.fixed((seen, _) =>
if (seen.startsWith("-")) Completions.nil else single("<class name>"))
token(base, compl)
}
private[this] def classpathOptionParser: Parser[Seq[String]] =
token(("-cp" | "-classpath") ~> Space) ~> classpathStrings <~ token(Space)
private[this] def classpathStrings: Parser[Seq[String]] =
token(StringBasic.map(s => IO.pathSplit(s).toSeq), "<classpath>")
def exit = Command.command(TerminateAction, exitBrief, exitBrief)(_ exit true)
def exit: Command = Command.command(TerminateAction, exitBrief, exitBrief)(_ exit true)
def continuous =
def continuous: Command =
Command(ContinuousExecutePrefix, continuousBriefHelp, continuousDetail)(otherCommandParser) { (s, arg) =>
withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w =>
val repeat = ContinuousExecutePrefix + (if (arg.startsWith(" ")) arg else " " + arg)
@ -163,7 +187,8 @@ object BasicCommands {
}
}
def history = Command.custom(historyParser, BasicCommandStrings.historyHelp)
def history: Command = Command.custom(historyParser, BasicCommandStrings.historyHelp)
def historyParser(s: State): Parser[() => State] =
Command.applyEffect(HistoryCommands.actionParser) { histFun =>
val logError = (msg: String) => s.log.error(msg)
@ -177,48 +202,56 @@ object BasicCommands {
}
}
def shell = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s =>
def shell: Command = Command.command(Shell, Help.more(Shell, 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(Exec(Shell, None)), remainingCommands = Exec(line, s.source) +: Exec(Shell, None) +: s.remainingCommands).setInteractive(true)
val newState = s.copy(
onFailure = Some(Exec(Shell, None)),
remainingCommands = Exec(line, s.source) +: Exec(Shell, None) +: s.remainingCommands
).setInteractive(true)
if (line.trim.isEmpty) newState else newState.clearGlobalLog
case None => s.setInteractive(false)
}
}
def client = Command.make(Client, Help.more(Client, ClientDetailed))(clientParser)
def clientParser(s0: State) =
{
val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map { case _ => Nil })
applyEffect(p)({ inputArg =>
val arguments = inputArg.toList ++
(s0.remainingCommands match {
case e :: Nil if e.commandLine == "shell" => Nil
case xs => xs map { _.commandLine }
})
NetworkClient.run(arguments)
"exit" :: s0.copy(remainingCommands = Nil)
})
}
def client: Command = Command(Client, Help.more(Client, ClientDetailed))(_ => clientParser)(runClient)
def read = Command.make(ReadCommand, Help.more(ReadCommand, ReadDetailed))(s => applyEffect(readParser(s))(doRead(s)))
def readParser(s: State) =
def clientParser: Parser[Seq[String]] =
(token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil))
def runClient(s0: State, inputArg: Seq[String]): State = {
val arguments = inputArg.toList ++
(s0.remainingCommands match {
case e :: Nil if e.commandLine == "shell" => Nil
case xs => xs map (_.commandLine)
})
NetworkClient.run(arguments)
"exit" :: s0.copy(remainingCommands = Nil)
}
def read: Command = Command(ReadCommand, Help.more(ReadCommand, ReadDetailed))(readParser)(doRead(_)(_))
def readParser(s: State): Parser[Either[Int, Seq[File]]] =
{
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(Exec(ReadCommand + " " + (-port), s.source)))
case Some(message) =>
(message :: (ReadCommand + " " + port) :: s).copy(
onFailure = Some(Exec(ReadCommand + " " + (-port), s.source))
)
case None =>
System.err.println("Connection closed.")
s.fail
@ -226,12 +259,14 @@ object BasicCommands {
case Right(from) =>
val notFound = notReadable(from)
if (notFound.isEmpty)
readLines(from).toList ::: s // this means that all commands from all files are loaded, parsed, and inserted before any are executed
// this means that all commands from all files are loaded, parsed, & inserted before any are executed
readLines(from).toList ::: s
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
@ -243,14 +278,13 @@ object BasicCommands {
}
}
def alias = Command.make(AliasCommand, Help.more(AliasCommand, AliasDetailed)) { s =>
def alias: Command = Command(AliasCommand, Help.more(AliasCommand, 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).failOnException | any.+.string
val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?)
applyEffect(base)(t => runAlias(s, t))
}
OptSpace ~> (name ~ (assign ~> to.?).?).?
}(runAlias)
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
args match {
@ -273,34 +307,45 @@ object BasicCommands {
s.copy(definedCommands = newAlias(name, value) +: s.definedCommands)
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 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 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 printAlias(s: State, name: String): Unit = printAliases(aliases(s, (n, _) => 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 allAliases(s: State): Seq[(String, String)] = aliases(s, (_, _) => 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))
Command.make(name, (name, s"'$value'"), s"Alias of '$value'")(aliasBody(name, value))
.tag(CommandAliasKey, (name, value))
def aliasBody(name: String, value: String)(state: State): Parser[() => State] = {
val aliasRemoved = removeAlias(state, name)
// apply the alias value to the commands of `state` except for the alias to avoid recursion (#933)
val partiallyApplied = Parser(Command.combine(aliasRemoved.definedCommands)(aliasRemoved))(value)
val arg = matched(partiallyApplied & (success(()) | (SpaceClass ~ any.*)))
// by scheduling the expanded alias instead of directly executing, we get errors on the expanded string (#598)
// by scheduling the expanded alias instead of directly executing,
// we get errors on the expanded string (#598)
arg.map(str => () => (value + str) :: state)
}
@ -310,5 +355,9 @@ object BasicCommands {
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.")
val CommandAliasKey: AttributeKey[(String, String)] =
AttributeKey[(String, String)](
"is-command-alias",
"Internal: marker for Commands created as aliases for another command."
)
}

View File

@ -8,98 +8,155 @@ import sbt.internal.util.complete.{ DefaultParsers, EditDistance, Parser }
import sbt.internal.util.Types.const
import sbt.internal.util.{ AttributeKey, AttributeMap, Util }
/**
* An operation that can be executed from the sbt console.
*
* <p>The operation takes a [[sbt.State State]] as a parameter and returns a [[sbt.State State]].
* This means that a command can look at or modify other sbt settings, for example.
* Typically you would resort to a command when you need to do something that's impossible in a regular task.
*/
sealed trait Command {
def help: State => Help
def parser: State => Parser[() => State]
def tags: AttributeMap
def tag[T](key: AttributeKey[T], value: T): Command
}
private[sbt] final class SimpleCommand(val name: String, private[sbt] val help0: Help, val parser: State => Parser[() => State], val tags: AttributeMap) extends Command {
assert(Command validID name, "'" + name + "' is not a valid command name.")
def tag[T](key: AttributeKey[T], value: T): SimpleCommand = new SimpleCommand(name, help0, parser, tags.put(key, value))
private[sbt] final class SimpleCommand(
val name: String,
private[sbt] val help0: Help,
val parser: State => Parser[() => State],
val tags: AttributeMap
) extends Command {
assert(Command validID name, s"'$name' is not a valid command name.")
def help = const(help0)
def tag[T](key: AttributeKey[T], value: T): SimpleCommand =
new SimpleCommand(name, help0, parser, tags.put(key, value))
override def toString = s"SimpleCommand($name)"
}
private[sbt] final class ArbitraryCommand(val parser: State => Parser[() => State], val help: State => Help, val tags: AttributeMap) extends Command {
def tag[T](key: AttributeKey[T], value: T): ArbitraryCommand = new ArbitraryCommand(parser, help, tags.put(key, value))
private[sbt] final class ArbitraryCommand(
val parser: State => Parser[() => State], val help: State => Help, val tags: AttributeMap
) extends Command {
def tag[T](key: AttributeKey[T], value: T): ArbitraryCommand =
new ArbitraryCommand(parser, help, tags.put(key, value))
}
object Command {
import DefaultParsers._
def command(name: String, briefHelp: String, detail: String)(f: State => State): Command = command(name, Help(name, (name, briefHelp), detail))(f)
def command(name: String, help: Help = Help.empty)(f: State => State): Command = make(name, help)(state => success(() => f(state)))
// Lowest-level command construction
def make(name: String, help: Help = Help.empty)(parser: State => Parser[() => State]): Command =
new SimpleCommand(name, help, parser, AttributeMap.empty)
def make(name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[() => State]): Command =
make(name, Help(name, briefHelp, detail))(parser)
def make(name: String, help: Help = Help.empty)(parser: State => Parser[() => State]): Command = new SimpleCommand(name, help, parser, AttributeMap.empty)
// General command construction
/** Construct a command with the given name, parser and effect. */
def apply[T](name: String, help: Help = Help.empty)(parser: State => Parser[T])(effect: (State, T) => State): Command =
make(name, help)(applyEffect(parser)(effect))
def apply[T](name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[T])(effect: (State, T) => State): Command =
apply(name, Help(name, briefHelp, detail))(parser)(effect)
def apply[T](name: String, help: Help = Help.empty)(parser: State => Parser[T])(effect: (State, T) => State): Command =
make(name, help)(applyEffect(parser)(effect))
// No-argument command construction
/** Construct a no-argument command with the given name and effect. */
def command(name: String, help: Help = Help.empty)(f: State => State): Command =
make(name, help)(state => success(() => f(state)))
def command(name: String, briefHelp: String, detail: String)(f: State => State): Command =
command(name, Help(name, (name, briefHelp), detail))(f)
// Single-argument command construction
/** Construct a single-argument command with the given name and effect. */
def single(name: String, help: Help = Help.empty)(f: (State, String) => State): Command =
make(name, help)(state => token(trimmed(spacedAny(name)) map apply1(f, state)))
def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command =
single(name, Help(name, briefHelp, detail))(f)
// Multi-argument command construction
/** Construct a multi-argument command with the given name, tab completion display and effect. */
def args(name: String, display: String, help: Help = Help.empty)(f: (State, Seq[String]) => State): Command =
make(name, help)(state => spaceDelimited(display) map apply1(f, state))
def args(name: String, briefHelp: (String, String), detail: String, display: String)(f: (State, Seq[String]) => State): Command =
args(name, display, Help(name, briefHelp, detail))(f)
def args(name: String, display: String, help: Help = Help.empty)(f: (State, Seq[String]) => State): Command =
make(name, help)(state => spaceDelimited(display) map apply1(f, state))
// create ArbitraryCommand
def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command =
single(name, Help(name, briefHelp, detail))(f)
def single(name: String, help: Help = Help.empty)(f: (State, String) => State): Command =
make(name, help)(state => token(trimmed(spacedAny(name)) map apply1(f, state)))
def customHelp(parser: State => Parser[() => State], help: State => Help): Command =
new ArbitraryCommand(parser, help, AttributeMap.empty)
def custom(parser: State => Parser[() => State], help: Help = Help.empty): Command = customHelp(parser, const(help))
def customHelp(parser: State => Parser[() => State], help: State => Help): Command = new ArbitraryCommand(parser, help, AttributeMap.empty)
def arb[T](parser: State => Parser[T], help: Help = Help.empty)(effect: (State, T) => State): Command = custom(applyEffect(parser)(effect), help)
def custom(parser: State => Parser[() => State], help: Help = Help.empty): Command =
customHelp(parser, const(help))
def validID(name: String) = DefaultParsers.matches(OpOrID, name)
def arb[T](parser: State => Parser[T], help: Help = Help.empty)(effect: (State, T) => State): Command =
custom(applyEffect(parser)(effect), help)
// misc Command object utilities
def validID(name: String): Boolean = DefaultParsers.matches(OpOrID, name)
def applyEffect[T](p: Parser[T])(f: T => State): Parser[() => State] = p map (t => () => f(t))
def applyEffect[T](parser: State => Parser[T])(effect: (State, T) => State): State => Parser[() => State] =
s => applyEffect(parser(s))(t => effect(s, t))
def applyEffect[T](p: Parser[T])(f: T => State): Parser[() => State] =
p map { t => () => f(t) }
def combine(cmds: Seq[Command]): State => Parser[() => State] =
{
val (simple, arbs) = separateCommands(cmds)
state => (simpleParser(simple)(state) /: arbs.map(_ parser state)) { _ | _ }
}
def combine(cmds: Seq[Command]): State => Parser[() => State] = {
val (simple, arbs) = separateCommands(cmds)
state => (simpleParser(simple)(state) /: arbs.map(_ parser state))(_ | _)
}
private[this] def separateCommands(cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) =
Util.separate(cmds) { case s: SimpleCommand => Left(s); case a: ArbitraryCommand => Right(a) }
private[this] def apply1[A, B, C](f: (A, B) => C, a: A): B => () => C =
b => () => f(a, b)
private[this] def apply1[A, B, C](f: (A, B) => C, a: A): B => () => C = b => () => f(a, b)
def simpleParser(cmds: Seq[SimpleCommand]): State => Parser[() => State] =
simpleParser(cmds.map(sc => (sc.name, argParser(sc))).toMap)
private[this] def argParser(sc: SimpleCommand): State => Parser[() => State] =
{
def usageError = s"${sc.name} usage:" + Help.message(sc.help0, None)
s => (Parser.softFailure(usageError, definitive = true): Parser[() => State]) | sc.parser(s)
}
private[this] def argParser(sc: SimpleCommand): State => Parser[() => State] = {
def usageError = s"${sc.name} usage:" + Help.message(sc.help0, None)
s => (Parser.softFailure(usageError, definitive = true): Parser[() => State]) | sc.parser(s)
}
def simpleParser(commandMap: Map[String, State => Parser[() => State]]): State => Parser[() => State] =
(state: State) => token(OpOrID examples commandMap.keys.toSet) flatMap { id =>
state => token(OpOrID examples commandMap.keys.toSet) flatMap (id =>
(commandMap get id) match {
case None => failure(invalidValue("command", commandMap.keys)(id))
case Some(c) => c(state)
}
}
})
def invalidValue(label: String, allowed: Iterable[String])(value: String): String =
"Not a valid " + label + ": " + value + similar(value, allowed)
def similar(value: String, allowed: Iterable[String]): String =
{
val suggested = if (value.length > 2) suggestions(value, allowed.toSeq) else Nil
if (suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")")
}
s"Not a valid $label: $value" + similar(value, allowed)
def similar(value: String, allowed: Iterable[String]): String = {
val suggested = if (value.length > 2) suggestions(value, allowed.toSeq) else Nil
if (suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")")
}
def suggestions(a: String, bs: Seq[String], maxDistance: Int = 3, maxSuggestions: Int = 3): Seq[String] =
bs.map { b => (b, distance(a, b)) } filter (_._2 <= maxDistance) sortBy (_._2) take (maxSuggestions) map (_._1)
bs map (b => (b, distance(a, b))) filter (_._2 <= maxDistance) sortBy (_._2) take (maxSuggestions) map (_._1)
def distance(a: String, b: String): Int =
EditDistance.levenshtein(a, b, insertCost = 1, deleteCost = 1, subCost = 2, transposeCost = 1, matchCost = -1, caseCost = 1, true)
EditDistance.levenshtein(a, b, insertCost = 1, deleteCost = 1, subCost = 2, transposeCost = 1,
matchCost = -1, caseCost = 1, transpositions = true)
def spacedAny(name: String): Parser[String] = spacedC(name, any)
def spacedC(name: String, c: Parser[Char]): Parser[String] =
((c & opOrIDSpaced(name)) ~ c.+) map { case (f, rem) => (f +: rem).mkString }
}
@ -110,19 +167,25 @@ trait Help {
def more: Set[String]
def ++(o: Help): Help
}
private final class Help0(val brief: Seq[(String, String)], val detail: Map[String, String], val more: Set[String]) extends Help {
private final class Help0(
val brief: Seq[(String, String)], val detail: Map[String, String], val more: Set[String]
) extends Help {
def ++(h: Help): Help = new Help0(Help0.this.brief ++ h.brief, Help0.this.detail ++ h.detail, more ++ h.more)
}
object Help {
val empty: Help = briefDetail(Nil)
def apply(name: String, briefHelp: (String, String), detail: String): Help = apply(briefHelp, Map((name, detail)))
def apply(name: String, briefHelp: (String, String), detail: String): Help =
apply(briefHelp, Map((name, detail)))
def apply(briefHelp: (String, String), detailedHelp: Map[String, String] = Map.empty): Help =
apply(briefHelp :: Nil, detailedHelp)
def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String]): Help =
apply(briefHelp, detailedHelp, Set.empty[String])
def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String], more: Set[String]): Help =
new Help0(briefHelp, detailedHelp, more)
@ -138,15 +201,17 @@ object Help {
case Some(x) => detail(x, h.detail)
case None =>
val brief = aligned(" ", " ", h.brief).mkString("\n", "\n", "\n")
val more = h.more.toSeq.sorted
val more = h.more
if (more.isEmpty)
brief
else
brief + "\n" + moreMessage(more)
brief + "\n" + moreMessage(more.toSeq.sorted)
}
def moreMessage(more: Seq[String]): String =
more.mkString("More command help available using 'help <command>' for:\n ", ", ", "\n")
}
trait CommandDefinitions extends (State => State) {
def commands: Seq[Command] = ReflectUtilities.allVals[Command](this).values.toSeq
def apply(s: State): State = s ++ commands

View File

@ -10,9 +10,9 @@ import sbt.internal.util.complete.DefaultParsers._
import sbt.io.IO
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("#")
def readLines(files: Seq[File]): Seq[String] = files flatMap (IO.readLines(_)) flatMap processLine
def processLine(s: String): Option[String] = { val s2 = s.trim; if (ignoreLine(s2)) None else Some(s2) }
def ignoreLine(s: String): Boolean = s.isEmpty || s.startsWith("#")
private def canRead = (_: File).canRead
def notReadable(files: Seq[File]): Seq[File] = files filterNot canRead
@ -20,17 +20,18 @@ object CommandUtil {
// 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 }
try state.configuration.provider.scalaProvider.launcher.bootDirectory
catch { case _: NoSuchMethodError => new File(".").getAbsoluteFile }
def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] = if (in.isEmpty) Nil else {
val width = in.map(_._1.length).max
in.map { case (a, b) => (pre + fill(a, width) + sep + b) }
val width = in.iterator.map(_._1.length).max
for ((a, b) <- in) yield pre + fill(a, width) + sep + b
}
def fill(s: String, size: Int) = s + " " * math.max(size - s.length, 0)
def fill(s: String, size: Int): String = 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 {
s get key match {
case None =>
s.log.error(ifMissing); s.fail
case Some(nav) => f(nav)
@ -41,6 +42,7 @@ object CommandUtil {
val arg = (NotSpaceClass ~ any.*) map { case (ns, s) => (ns +: s).mkString }
token(Space) ~> token(arg examples exampleStrings)
}
def detail(selected: String, detailMap: Map[String, String]): String =
detailMap.get(selected) match {
case Some(exactDetail) => exactDetail
@ -51,9 +53,11 @@ object CommandUtil {
else
layoutDetails(details)
} catch {
case pse: PatternSyntaxException => sys.error("Invalid regular expression (java.util.regex syntax).\n" + pse.getMessage)
case pse: PatternSyntaxException =>
sys.error("Invalid regular expression (java.util.regex syntax).\n" + pse.getMessage)
}
}
def searchHelp(selected: String, detailMap: Map[String, String]): Map[String, String] =
{
val pattern = Pattern.compile(selected, HelpPatternFlags)
@ -69,9 +73,9 @@ object CommandUtil {
Nil
}
}
def layoutDetails(details: Map[String, String]): String =
details.map { case (k, v) => k + "\n\n " + v } mkString ("\n", "\n\n", "\n")
final val HelpPatternFlags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
}

View File

@ -4,28 +4,14 @@
package sbt
import sbt.internal.{
Act,
Aggregation,
BuildStructure,
BuildUnit,
CommandExchange,
CommandStrings,
EvaluateConfigurations,
Inspect,
IvyConsole,
Load,
LoadedBuildUnit,
Output,
PluginsDebug,
ProjectNavigation,
Script,
SessionSettings,
SetResult,
SettingCompletions,
LogManager,
DefaultBackgroundJobService
Act, Aggregation, BuildStructure, BuildUnit, CommandExchange, CommandStrings,
EvaluateConfigurations, Inspect, IvyConsole, Load, LoadedBuildUnit, Output,
PluginsDebug, ProjectNavigation, Script, SessionSettings, SetResult,
SettingCompletions, LogManager, DefaultBackgroundJobService
}
import sbt.internal.util.{
AttributeKey, AttributeMap, ConsoleOut, GlobalLogging, LineRange, MainAppender, SimpleReader, Types
}
import sbt.internal.util.{ AttributeKey, AttributeMap, ConsoleOut, GlobalLogging, LineRange, MainAppender, SimpleReader, Types }
import sbt.util.{ Level, Logger }
import sbt.internal.util.complete.{ DefaultParsers, Parser }
@ -124,6 +110,7 @@ final class ScriptMain extends xsbti.AppMain {
))
}
}
final class ConsoleMain extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
runManaged(initialState(
@ -175,11 +162,17 @@ object BuiltinCommands {
def ConsoleCommands: Seq[Command] = Seq(ignore, exit, IvyConsole.command, setLogLevel, early, act, nop)
def ScriptCommands: Seq[Command] = Seq(ignore, exit, Script.command, setLogLevel, early, act, nop)
def DefaultCommands: Seq[Command] = Seq(ignore, help, completionsCommand, about, tasks, settingsCommand, loadProject, templateCommand,
projects, project, reboot, read, history, set, sessionCommand, inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion,
Cross.crossRestoreSession, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure, setLogLevel, plugin, plugins,
ifLast, multi, shell, server, BasicCommands.client, continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit, early, initialize, act) ++
compatCommands
def DefaultCommands: Seq[Command] = Seq(
ignore, help, completionsCommand, about, tasks, settingsCommand, loadProject,
templateCommand, projects, project, reboot, read, history, set, sessionCommand,
inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion,
Cross.crossRestoreSession, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure,
setLogLevel, plugin, plugins, ifLast, multi, shell, server, BasicCommands.client,
continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit,
early, initialize, act
) ++ compatCommands
def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil
def boot = Command.make(BootCommand)(bootParser)
@ -215,6 +208,7 @@ object BuiltinCommands {
val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct
if (allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "")
}
def aboutScala(s: State, e: Extracted): String =
{
val scalaVersion = e.getOpt(Keys.scalaVersion)
@ -228,6 +222,7 @@ object BuiltinCommands {
case (None, None, None) => ""
}
}
def aboutString(s: State): String =
{
val (name, ver, scalaVer, about) = (sbtName(s), sbtVersion(s), scalaVersion(s), aboutProject(s))
@ -236,14 +231,22 @@ object BuiltinCommands {
|%s, %s plugins, and build definitions are using Scala %s
|""".stripMargin.format(name, ver, about, name, name, scalaVer)
}
private[this] def selectScalaVersion(sv: Option[String], si: ScalaInstance): String = sv match { case Some(si.version) => si.version; case _ => si.actualVersion }
private[this] def selectScalaVersion(sv: Option[String], si: ScalaInstance): String =
sv match {
case Some(si.version) => si.version
case _ => si.actualVersion
}
private[this] def quiet[T](t: => T): Option[T] = try { Some(t) } catch { case e: Exception => None }
def settingsCommand = showSettingLike(SettingsCommand, settingsPreamble, KeyRanks.MainSettingCutoff, key => !isTask(key.manifest))
def settingsCommand: Command =
showSettingLike(SettingsCommand, settingsPreamble, KeyRanks.MainSettingCutoff, key => !isTask(key.manifest))
def tasks = showSettingLike(TasksCommand, tasksPreamble, KeyRanks.MainTaskCutoff, key => isTask(key.manifest))
def tasks: Command =
showSettingLike(TasksCommand, tasksPreamble, KeyRanks.MainTaskCutoff, key => isTask(key.manifest))
def showSettingLike(command: String, preamble: String, cutoff: Int, keep: AttributeKey[_] => Boolean) =
def showSettingLike(command: String, preamble: String, cutoff: Int, keep: AttributeKey[_] => Boolean): Command =
Command(command, settingsBrief(command), settingsDetailed(command))(showSettingParser(keep)) {
case (s: State, (verbosity: Int, selected: Option[String])) =>
if (selected.isEmpty) System.out.println(preamble)
@ -304,20 +307,22 @@ object BuiltinCommands {
s.copy(definedCommands = DefaultCommands)
}
def initialize = Command.command(InitCommand) { s =>
def initialize: Command = Command.command(InitCommand) { s =>
/*"load-commands -base ~/.sbt/commands" :: */ readLines(readable(sbtRCs(s))).toList ::: s
}
def eval = Command.single(EvalCommand, Help.more(EvalCommand, evalDetailed)) { (s, arg) =>
def eval: Command = Command.single(EvalCommand, Help.more(EvalCommand, evalDetailed)) { (s, arg) =>
if (Project.isProjectLoaded(s)) loadedEval(s, arg) else rawEval(s, arg)
s
}
private[this] def loadedEval(s: State, arg: String): Unit = {
val extracted = Project extract s
import extracted._
val result = session.currentEval().eval(arg, srcName = "<eval>", imports = autoImports(extracted))
s.log.info(s"ans: ${result.tpe} = ${result.getValue(currentLoader)}")
}
private[this] def rawEval(s: State, arg: String): Unit = {
val app = s.configuration.provider
val classpath = app.mainClasspath ++ app.scalaProvider.jars
@ -325,15 +330,17 @@ object BuiltinCommands {
s.log.info(s"ans: ${result.tpe} = ${result.getValue(app.loader)}")
}
def sessionCommand: Command = Command.make(SessionCommand, sessionBrief, SessionSettings.Help)(SessionSettings.command)
def sessionCommand: Command =
Command.make(SessionCommand, sessionBrief, SessionSettings.Help)(SessionSettings.command)
def reapply(newSession: SessionSettings, structure: BuildStructure, s: State): State =
{
s.log.info("Reapplying settings...")
// Here, for correct behavior, we also need to re-inject a settings logger, as we'll be re-evaluating settings.
// For correct behavior, we also need to re-inject a settings logger, as we'll be re-evaluating settings
val loggerInject = LogManager.settingsLogger(s)
val withLogger = newSession.appendRaw(loggerInject :: Nil)
val newStructure = Load.reapply(withLogger.mergeSettings, structure)(Project.showContextKey(newSession, structure))
val show = Project.showContextKey(newSession, structure)
val newStructure = Load.reapply(withLogger.mergeSettings, structure)(show)
Project.setProject(newSession, newStructure, s)
}
@ -354,7 +361,9 @@ object BuiltinCommands {
arg,
LineRange(0, 0)
)(cl)
val setResult = if (all) SettingCompletions.setAll(extracted, settings) else SettingCompletions.setThis(s, extracted, settings, arg)
val setResult =
if (all) SettingCompletions.setAll(extracted, settings)
else SettingCompletions.setThis(s, extracted, settings, arg)
s.log.info(setResult.quietSummary)
s.log.debug(setResult.verboseSummary)
reapply(setResult.session, structure, s)
@ -369,7 +378,7 @@ object BuiltinCommands {
s
}
def lastGrep = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) {
def lastGrep: Command = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) {
case (s, (pattern, Some(sks))) =>
val (str, _, display) = extractLast(s)
Output.lastGrep(sks, str.streams(s), pattern, printLast(s))(display)
@ -469,32 +478,38 @@ object BuiltinCommands {
for (id <- build.defined.keys.toSeq.sorted) log.info("\t" + prefix(id) + id)
}
def act = Command.customHelp(Act.actParser, actHelp)
def actHelp = (s: State) => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(s)
def act: Command = Command.customHelp(Act.actParser, actHelp)
def actHelp: State => Help = s => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(s)
def keysHelp(s: State): Help =
if (Project.isProjectLoaded(s))
Help.detailOnly(taskDetail(allTaskAndSettingKeys(s)))
else
Help.empty
def plugins = Command.command(PluginsCommand, pluginsBrief, pluginsDetailed) { s =>
def plugins: Command = Command.command(PluginsCommand, pluginsBrief, pluginsDetailed) { s =>
val helpString = PluginsDebug.helpAll(s)
System.out.println(helpString)
s
}
val pluginParser: State => Parser[AutoPlugin] = s => {
val autoPlugins: Map[String, AutoPlugin] = PluginsDebug.autoPluginMap(s)
token(Space) ~> Act.knownPluginParser(autoPlugins, "plugin")
}
def plugin = Command(PluginCommand)(pluginParser) { (s, plugin) =>
def plugin: Command = Command(PluginCommand)(pluginParser) { (s, plugin) =>
val helpString = PluginsDebug.help(plugin, s)
System.out.println(helpString)
s
}
def projects = Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s => projectsParser(s).?) {
case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds)
case (s, None) => showProjects(s); s
}
def projects: Command =
Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s => projectsParser(s).?) {
case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds)
case (s, None) => showProjects(s); s
}
def showProjects(s: State): Unit = {
val extracted = Project extract s
import extracted._
@ -502,6 +517,7 @@ object BuiltinCommands {
listBuild(curi, structure.units(curi), true, cid, s.log)
for ((uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, s.log)
}
def transformExtraBuilds(s: State, f: List[URI] => List[URI]): State =
{
val original = Project.extraBuilds(s)
@ -553,11 +569,11 @@ object BuiltinCommands {
Nil
def loadProject: Command =
Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser) { (s, arg) =>
Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser)((s, arg) =>
loadProjectCommands(arg) ::: s
}
)
private[this] def loadProjectParser = (s: State) => matched(Project.loadActionParser)
private[this] def loadProjectParser: State => Parser[String] = _ => matched(Project.loadActionParser)
private[this] def loadProjectCommand(command: String, arg: String): String = s"$command $arg".trim
def loadProjectImpl: Command = Command(LoadProjectImpl)(_ => Project.loadActionParser)(doLoadProject)

View File

@ -361,8 +361,10 @@ object Project extends ProjectExtra {
def getProjectForReference(ref: Reference, structure: BuildStructure): Option[ResolvedProject] =
ref match { case pr: ProjectRef => getProject(pr, structure); case _ => None }
def getProject(ref: ProjectRef, structure: BuildStructure): Option[ResolvedProject] = getProject(ref, structure.units)
def getProject(ref: ProjectRef, structure: LoadedBuild): Option[ResolvedProject] = getProject(ref, structure.units)
def getProject(ref: ProjectRef, units: Map[URI, LoadedBuildUnit]): Option[ResolvedProject] =
(units get ref.build).flatMap(_.defined get ref.project)
@ -371,6 +373,7 @@ object Project extends ProjectExtra {
val previousOnUnload = orIdentity(s get Keys.onUnload.key)
previousOnUnload(s.runExitHooks())
}
def setProject(session: SessionSettings, structure: BuildStructure, s: State): State =
{
val unloaded = runUnloadHooks(s)
@ -386,6 +389,7 @@ object Project extends ProjectExtra {
def getHooks(data: Settings[Scope]): (State => State, State => State) = (getHook(Keys.onLoad, data), getHook(Keys.onUnload, data))
def current(state: State): ProjectRef = session(state).current
def updateCurrent(s: State): State =
{
val structure = Project.structure(s)
@ -410,6 +414,7 @@ object Project extends ProjectExtra {
.put(templateResolverInfos.key, trs)
s.copy(attributes = setCond(shellPrompt.key, prompt, newAttrs), definedCommands = newDefinedCommands)
}
def setCond[T](key: AttributeKey[T], vopt: Option[T], attributes: AttributeMap): AttributeMap =
vopt match { case Some(v) => attributes.put(key, v); case None => attributes.remove(key) }
@ -528,20 +533,21 @@ object Project extends ProjectExtra {
def relation(structure: BuildStructure, actual: Boolean)(implicit display: Show[ScopedKey[_]]): Relation[ScopedKey[_], ScopedKey[_]] =
relation(structure.settings, actual)(structure.delegates, structure.scopeLocal, display)
private[sbt] def relation(settings: Seq[Def.Setting[_]], actual: Boolean)(implicit delegates: Scope => Seq[Scope], scopeLocal: Def.ScopeLocal, display: Show[ScopedKey[_]]): Relation[ScopedKey[_], ScopedKey[_]] =
private[sbt] def relation(settings: Seq[Def.Setting[_]], actual: Boolean)(
implicit delegates: Scope => Seq[Scope], scopeLocal: Def.ScopeLocal, display: Show[ScopedKey[_]]
): Relation[ScopedKey[_], ScopedKey[_]] =
{
type Rel = Relation[ScopedKey[_], ScopedKey[_]]
val cMap = Def.flattenLocals(Def.compiled(settings, actual))
((Relation.empty: Rel) /: cMap) {
case (r, (key, value)) =>
r + (key, value.dependencies)
}
val emptyRelation = Relation.empty[ScopedKey[_], ScopedKey[_]]
(emptyRelation /: cMap) { case (r, (key, value)) => r + (key, value.dependencies) }
}
def showDefinitions(key: AttributeKey[_], defs: Seq[Scope])(implicit display: Show[ScopedKey[_]]): String =
showKeys(defs.map(scope => ScopedKey(scope, key)))
def showUses(defs: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String =
showKeys(defs)
private[this] def showKeys(s: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String =
s.map(display.show).sorted.mkString("\n\t", "\n\t", "\n\n")
@ -575,20 +581,24 @@ object Project extends ProjectExtra {
def projectReturn(s: State): List[File] = getOrNil(s, ProjectReturn)
def inPluginProject(s: State): Boolean = projectReturn(s).length > 1
def setProjectReturn(s: State, pr: List[File]): State = s.copy(attributes = s.attributes.put(ProjectReturn, pr))
def loadAction(s: State, action: LoadAction.Value) = action match {
def loadAction(s: State, action: LoadAction.Value): (State, File) = action match {
case Return =>
projectReturn(s) match {
case current :: returnTo :: rest => (setProjectReturn(s, returnTo :: rest), returnTo)
case _ => sys.error("Not currently in a plugin definition")
case _ /* current */ :: returnTo :: rest => (setProjectReturn(s, returnTo :: rest), returnTo)
case _ => sys.error("Not currently in a plugin definition")
}
case Current =>
val base = s.configuration.baseDirectory
projectReturn(s) match { case Nil => (setProjectReturn(s, base :: Nil), base); case x :: xs => (s, x) }
case Plugins =>
val (newBase, oldStack) = if (Project.isProjectLoaded(s))
(Project.extract(s).currentUnit.unit.plugins.base, projectReturn(s))
else // support changing to the definition project if it fails to load
(BuildPaths.projectStandard(s.baseDir), s.baseDir :: Nil)
val (newBase, oldStack) =
if (Project.isProjectLoaded(s))
(Project.extract(s).currentUnit.unit.plugins.base, projectReturn(s))
else // support changing to the definition project if it fails to load
(BuildPaths.projectStandard(s.baseDir), s.baseDir :: Nil)
val newS = setProjectReturn(s, newBase :: oldStack)
(newS, newBase)
}
@ -603,6 +613,7 @@ object Project extends ProjectExtra {
val mfi = EvaluateTask.minForcegcInterval(extracted, extracted.structure)
runTask(taskKey, state, EvaluateTaskConfig(r, checkCycles, p, ch, fgc, mfi))
}
def runTask[T](taskKey: ScopedKey[Task[T]], state: State, config: EvaluateTaskConfig): Option[(State, Result[T])] = {
val extracted = Project.extract(state)
EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, config)

View File

@ -2,47 +2,43 @@ package sbt
import java.lang.reflect.InvocationTargetException
import java.io.File
import sbt.io._, syntax._
import sbt.util._
import sbt.internal.util._
import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._
import xsbti.AppConfiguration
import sbt.internal.inc.classpath.ClasspathUtilities
import BasicCommandStrings._
import BasicKeys._
import complete.DefaultParsers
import DefaultParsers._
import Command.applyEffect
import sbt.io._
import sbt.io.syntax._
import sbt.librarymanagement._
import sbt.internal.librarymanagement.IvyConfiguration
import sbt.internal.inc.classpath.ClasspathUtilities
import BasicCommandStrings._, BasicKeys._
private[sbt] object TemplateCommandUtil {
def templateCommand = Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser)
def templateCommandParser(state: State) =
{
val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map { case _ => Nil })
val infos = (state get templateResolverInfos) match {
case Some(infos) => infos.toList
case None => Nil
}
val log = state.globalLogging.full
val extracted = (Project extract state)
val (s2, ivyConf) = extracted.runTask(Keys.ivyConfiguration, state)
val globalBase = BuildPaths.getGlobalBase(state)
val ivyScala = extracted.get(Keys.ivyScala in Keys.updateSbtClassifiers)
applyEffect(p)({ inputArg =>
val arguments = inputArg.toList ++
(state.remainingCommands.toList match {
case exec :: Nil if exec.commandLine == "shell" => Nil
case xs => xs map { _.commandLine }
})
run(infos, arguments, state.configuration, ivyConf, globalBase, ivyScala, log)
"exit" :: s2.copy(remainingCommands = Nil)
})
}
def templateCommand: Command =
Command(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser)(runTemplate)
private def run(infos: List[TemplateResolverInfo], arguments: List[String], config: AppConfiguration,
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): Unit =
private def templateCommandParser(state: State): Parser[Seq[String]] =
(token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil))
private def runTemplate(state: State, inputArg: Seq[String]): State = {
val infos = (state get templateResolverInfos getOrElse Nil).toList
val log = state.globalLogging.full
val extracted = (Project extract state)
val (s2, ivyConf) = extracted.runTask(Keys.ivyConfiguration, state)
val globalBase = BuildPaths.getGlobalBase(state)
val ivyScala = extracted.get(Keys.ivyScala in Keys.updateSbtClassifiers)
val arguments = inputArg.toList ++
(state.remainingCommands match {
case exec :: Nil if exec.commandLine == "shell" => Nil
case xs => xs map (_.commandLine)
})
run(infos, arguments, state.configuration, ivyConf, globalBase, ivyScala, log)
"exit" :: s2.copy(remainingCommands = Nil)
}
private def run(
infos: List[TemplateResolverInfo], arguments: List[String], config: AppConfiguration,
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger
): Unit =
infos find { info =>
val loader = infoLoader(info, config, ivyConf, globalBase, ivyScala, log)
val hit = tryTemplate(info, arguments, loader)
@ -54,6 +50,7 @@ private[sbt] object TemplateCommandUtil {
case Some(_) => // do nothing
case None => System.err.println("Template not found for: " + arguments.mkString(" "))
}
private def tryTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Boolean =
{
val resultObj = call(info.implementationClass, "isDefined", loader)(
@ -61,12 +58,21 @@ private[sbt] object TemplateCommandUtil {
)(arguments.toArray)
resultObj.asInstanceOf[Boolean]
}
private def runTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Unit =
call(info.implementationClass, "run", loader)(classOf[Array[String]])(arguments.toArray)
private def infoLoader(info: TemplateResolverInfo, config: AppConfiguration,
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): ClassLoader =
ClasspathUtilities.toLoader(classpathForInfo(info, ivyConf, globalBase, ivyScala, log), config.provider.loader)
private def call(interfaceClassName: String, methodName: String, loader: ClassLoader)(argTypes: Class[_]*)(args: AnyRef*): AnyRef =
private def infoLoader(
info: TemplateResolverInfo, config: AppConfiguration, ivyConf: IvyConfiguration, globalBase: File,
ivyScala: Option[IvyScala], log: Logger
): ClassLoader = {
val cp = classpathForInfo(info, ivyConf, globalBase, ivyScala, log)
ClasspathUtilities.toLoader(cp, config.provider.loader)
}
private def call(
interfaceClassName: String, methodName: String, loader: ClassLoader
)(argTypes: Class[_]*)(args: AnyRef*): AnyRef =
{
val interfaceClass = getInterfaceClass(interfaceClassName, loader)
val interface = interfaceClass.getDeclaredConstructor().newInstance().asInstanceOf[AnyRef]
@ -76,10 +82,14 @@ private[sbt] object TemplateCommandUtil {
case e: InvocationTargetException => throw e.getCause
}
}
private def getInterfaceClass(name: String, loader: ClassLoader) = Class.forName(name, true, loader)
// Cache files under ~/.sbt/0.13/templates/org_name_version
private def classpathForInfo(info: TemplateResolverInfo, ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): List[File] =
private def classpathForInfo(
info: TemplateResolverInfo, ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala],
log: Logger
): List[File] =
{
val lm = new DefaultLibraryManagement(ivyConf, log)
val templatesBaseDirectory = new File(globalBase, "templates")

View File

@ -18,9 +18,9 @@ object CommandStrings {
val BootCommand = "boot"
val EvalCommand = "eval"
val evalBrief = (EvalCommand + " <expression>", "Evaluates a Scala expression and prints the result and type.")
val evalBrief = (s"$EvalCommand <expression>", "Evaluates a Scala expression and prints the result and type.")
val evalDetailed =
EvalCommand + """ <expression>
s"""$EvalCommand <expression>
Evaluates the given Scala expression and prints the result and type."""
@ -31,8 +31,8 @@ object CommandStrings {
s"""$multiTaskSyntax
$multiTaskBrief"""
def multiTaskSyntax = s"""$MultiTaskCommand <task>+"""
def multiTaskBrief = """Executes all of the specified tasks concurrently."""
def multiTaskSyntax = s"$MultiTaskCommand <task>+"
def multiTaskBrief = "Executes all of the specified tasks concurrently."
def showHelp = Help(ShowCommand, (s"$ShowCommand <key>", showBrief), showDetailed)
def showBrief = "Displays the result of evaluating the setting or task associated with 'key'."
@ -57,26 +57,26 @@ $ShowCommand <task>
val lastGrepBrief = (LastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.")
val lastGrepDetailed =
LastGrepCommand + """ <pattern>
s"""$LastGrepCommand <pattern>
Displays lines from the logging of previous commands that match `pattern`.
""" + LastGrepCommand + """ <pattern> [key]
$LastGrepCommand <pattern> [key]
Displays lines from logging associated with `key` that match `pattern`. The key typically refers to a task (for example, test:compile). The logging that is displayed is restricted to the logging for that particular task.
<pattern> is a regular expression interpreted by java.util.Pattern. Matching text is highlighted (when highlighting is supported and enabled).
See also '""" + LastCommand + "'."
See also '$LastCommand'."""
val lastBrief = (LastCommand, "Displays output from a previous command or the output from a specific task.")
val lastDetailed =
LastCommand + """
s"""$LastCommand
Prints the logging for the previous command, typically at a more verbose level.
""" + LastCommand + """ <key>
$LastCommand <key>
Prints the logging associated with the provided key. The key typically refers to a task (for example, test:compile). The logging that is displayed is restricted to the logging for that particular task.
See also '""" + LastGrepCommand + "'."
See also '$LastGrepCommand'."""
val exportBrief = (ExportCommand + " <tasks>+", "Executes tasks and displays the equivalent command lines.")
val exportBrief = (s"$ExportCommand <tasks>+", "Executes tasks and displays the equivalent command lines.")
val exportDetailed =
s"""$ExportCommand [--last] <task>+
Runs the specified tasks and prints the equivalent command lines or other exportable information for those runs.
@ -92,7 +92,7 @@ $ShowCommand <task>
"""
val InspectCommand = "inspect"
val inspectBrief = (InspectCommand + " [uses|tree|definitions] <key>", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.")
val inspectBrief = (s"$InspectCommand [uses|tree|definitions] <key>", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.")
val inspectDetailed = s"""
|$InspectCommand <key>
|
@ -133,7 +133,7 @@ $ShowCommand <task>
val SetCommand = "set"
val setBrief = (s"$SetCommand [every] <setting>", "Evaluates a Setting and applies it to the current project.")
val setDetailed =
SetCommand + """ [every] <setting-expression>
s"""$SetCommand [every] <setting-expression>
Applies the given setting to the current project:
1) Constructs the expression provided as an argument by compiling and loading it.
@ -150,68 +150,82 @@ $ShowCommand <task>
"""
def SessionCommand = "session"
def sessionBrief = (SessionCommand, "Manipulates session settings. For details, run 'help " + SessionCommand + "'.")
def sessionBrief =
(SessionCommand, s"Manipulates session settings. For details, run 'help $SessionCommand'.")
def settingsPreamble = commonPreamble("settings")
def tasksPreamble = commonPreamble("tasks") + """
Tasks produce values. Use the 'show' command to run the task and print the resulting value."""
def commonPreamble(label: String) = """
This is a list of %s defined for the current project.
It does not list the scopes the %<s are defined in; use the 'inspect' command for that.""".format(label)
def commonPreamble(label: String) = s"""
This is a list of $label defined for the current project.
It does not list the scopes the $label are defined in; use the 'inspect' command for that."""
def settingsBrief(label: String) = (label, s"Lists the $label defined for the current project.")
import BasicCommandStrings.HelpCommand
def settingsBrief(label: String) = (label, "Lists the " + label + " defined for the current project.")
def settingsDetailed(label: String) =
"""
s"""
Syntax summary
%s [-(v|-vv|...|-V)] [<filter>]
$label [-(v|-vv|...|-V)] [<filter>]
%<s
Displays the main %<s defined directly or indirectly for the current project.
$label
Displays the main $label defined directly or indirectly for the current project.
-v
Displays additional %<s. More 'v's increase the number of %<s displayed.
Displays additional $label. More 'v's increase the number of $label displayed.
-V
displays all %<s
displays all $label
<filter>
Restricts the %<s that are displayed. The names of %<s are searched for an exact match against the filter, in which case only the description of the exact match is displayed. Otherwise, the filter is interpreted as a regular expression and all %<s whose name or description match the regular expression are displayed. Note that this is an additional filter on top of the %<s selected by the -v style switches, so you must specify -V to search all %<s. Use the %s command to search all commands, tasks, and settings at once.
""".format(label, BasicCommandStrings.HelpCommand)
Restricts the $label that are displayed. The names of $label are searched for
an exact match against the filter, in which case only the description of the
exact match is displayed. Otherwise, the filter is interpreted as a regular
expression and all $label whose name or description match the regular
expression are displayed. Note that this is an additional filter on top of
the $label selected by the -v style switches, so you must specify -V to search
all $label. Use the $HelpCommand command to search all commands, tasks, and
settings at once.
"""
def moreAvailableMessage(label: String, search: Boolean) =
"More %s may be %s by increasing verbosity. See '%s %s'.\n".format(label, if (search) "searched" else "viewed", BasicCommandStrings.HelpCommand, label)
def moreAvailableMessage(label: String, search: Boolean) = {
val verb = if (search) "searched" else "viewed"
s"More $label may be $verb by increasing verbosity. See '$HelpCommand $label'\n"
}
def aboutBrief = "Displays basic information about sbt and the build."
def aboutDetailed = aboutBrief
def projectBrief = (ProjectCommand, "Displays the current project or changes to the provided `project`.")
def projectDetailed =
ProjectCommand +
"""
s"""$ProjectCommand
Displays the name of the current project.
""" + ProjectCommand + """ name
$ProjectCommand name
Changes to the project with the provided name.
This command fails if there is no project with the given name.
""" + ProjectCommand + """ {uri}
$ProjectCommand {uri}
Changes to the root project in the build defined by `uri`.
`uri` must have already been declared as part of the build, such as with Project.dependsOn.
""" + ProjectCommand + """ {uri}name
$ProjectCommand {uri}name
Changes to the project `name` in the build defined by `uri`.
`uri` must have already been declared as part of the build, such as with Project.dependsOn.
""" + ProjectCommand + """ /
$ProjectCommand /
Changes to the initial project.
""" + ProjectCommand + """ ..
$ProjectCommand ..
Changes to the parent project of the current project.
If there is no parent project, the current project is unchanged.
@ -221,15 +235,15 @@ Syntax summary
def projectsBrief = "Lists the names of available projects or temporarily adds/removes extra builds to the session."
def projectsDetailed =
ProjectsCommand + """
s"""$ProjectsCommand
List the names of available builds and the projects defined in those builds.
""" + ProjectsCommand + """ add <URI>+
$ProjectsCommand add <URI>+
Adds the builds at the provided URIs to this session.
These builds may be selected using the """ + ProjectCommand + """ command.
These builds may be selected using the sProjectCommand command.
Alternatively, tasks from these builds may be run using the explicit syntax {URI}project/task
""" + ProjectsCommand + """ remove <URI>+
$ProjectsCommand remove <URI>+
Removes extra builds from this session.
Builds explicitly listed in the build definition are not affected by this command.
"""
@ -250,8 +264,8 @@ Syntax summary
def LoadProjectImpl = "loadp"
def LoadProject = "reload"
def LoadProjectBrief = (LoadProject, "(Re)loads the current project or changes to plugins project or returns from it.")
def LoadProjectDetailed = LoadProject +
s"""
def LoadProjectDetailed =
s"""$LoadProject
\t(Re)loads the project in the current directory.
@ -266,7 +280,7 @@ $LoadProject return
def InitCommand = "initialize"
def InitBrief = (InitCommand, "Initializes command processing.")
def InitDetailed =
InitCommand + """
s"""$InitCommand
Initializes command processing.
Runs the following commands.

View File

@ -242,16 +242,26 @@ private[sbt] object Load {
}
// Reevaluates settings after modifying them. Does not recompile or reload any build components.
def reapply(newSettings: Seq[Setting[_]], structure: BuildStructure)(implicit display: Show[ScopedKey[_]]): BuildStructure =
def reapply(
newSettings: Seq[Setting[_]], structure: BuildStructure
)(implicit display: Show[ScopedKey[_]]): BuildStructure =
{
val transformed = finalTransforms(newSettings)
val newData = Def.make(transformed)(structure.delegates, structure.scopeLocal, display)
val newIndex = structureIndex(newData, transformed, index => BuildUtil(structure.root, structure.units, index, newData), structure.units)
def extra(index: KeyIndex) = BuildUtil(structure.root, structure.units, index, newData)
val newIndex = structureIndex(newData, transformed, extra, structure.units)
val newStreams = mkStreams(structure.units, structure.root, newData)
new BuildStructure(units = structure.units, root = structure.root, settings = transformed, data = newData, index = newIndex, streams = newStreams, delegates = structure.delegates, scopeLocal = structure.scopeLocal)
new BuildStructure(
units = structure.units, root = structure.root, settings = transformed, data = newData,
index = newIndex, streams = newStreams, delegates = structure.delegates,
scopeLocal = structure.scopeLocal)
}
def isProjectThis(s: Setting[_]) = s.key.scope.project match { case This | Select(ThisProject) => true; case _ => false }
def isProjectThis(s: Setting[_]): Boolean =
s.key.scope.project match {
case This | Select(ThisProject) => true
case _ => false
}
def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, injectSettings: InjectSettings): Seq[Setting[_]] =
{
@ -291,7 +301,8 @@ private[sbt] object Load {
() => eval
}
def mkEval(unit: BuildUnit): Eval = mkEval(unit.definitions, unit.plugins, unit.plugins.pluginData.scalacOptions)
def mkEval(unit: BuildUnit): Eval =
mkEval(unit.definitions, unit.plugins, unit.plugins.pluginData.scalacOptions)
def mkEval(defs: LoadedDefinitions, plugs: LoadedPlugins, options: Seq[String]): Eval =
mkEval(defs.target ++ plugs.classpath, defs.base, options)
@ -335,7 +346,8 @@ private[sbt] object Load {
BuildLoader(components, fail, s, config)
}
def load(file: File, loaders: BuildLoader, extra: List[URI]): PartBuild = loadURI(IO.directoryURI(file), loaders, extra)
def load(file: File, loaders: BuildLoader, extra: List[URI]): PartBuild =
loadURI(IO.directoryURI(file), loaders, extra)
def loadURI(uri: URI, loaders: BuildLoader, extra: List[URI]): PartBuild =
{

View File

@ -9,7 +9,11 @@ import Plugins._
import PluginsDebug._
import java.net.URI
private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey: Map[String, AttributeKey[_]], val provided: Relation[AutoPlugin, AttributeKey[_]]) {
private[sbt] class PluginsDebug(
val available: List[AutoPlugin],
val nameToKey: Map[String, AttributeKey[_]],
val provided: Relation[AutoPlugin, AttributeKey[_]]
) {
/**
* The set of [[AutoPlugin]]s that might define a key named `keyName`.
* Because plugins can define keys in different scopes, this should only be used as a guideline.
@ -18,20 +22,23 @@ private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey:
case None => Set.empty
case Some(key) => provided.reverse(key)
}
/** Describes alternative approaches for defining key [[keyName]] in [[context]].*/
/** Describes alternative approaches for defining key `keyName` in [[Context]]. */
def toEnable(keyName: String, context: Context): List[PluginEnable] =
providers(keyName).toList.map(plugin => pluginEnable(context, plugin))
/** Provides text to suggest how [[notFoundKey]] can be defined in [[context]]. */
/** Provides text to suggest how `notFoundKey` can be defined in [[Context]]. */
def debug(notFoundKey: String, context: Context): String =
{
val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) {
case pa: PluginActivated => Left(pa)
case pd: EnableDeactivated => Right(pd)
}
val activePrefix = if (activated.nonEmpty) s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n" else ""
val activePrefix = if (activated.isEmpty) "" else
s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n"
activePrefix + debugDeactivated(notFoundKey, deactivated)
}
private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String =
{
val (impossible, possible) = Util.separate(deactivated) {
@ -41,23 +48,27 @@ private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey:
if (possible.nonEmpty) {
val explained = possible.map(explainPluginEnable)
val possibleString =
if (explained.size > 1) explained.zipWithIndex.map { case (s, i) => s"$i. $s" }.mkString(s"Multiple plugins are available that can provide $notFoundKey:\n", "\n", "")
if (explained.size > 1) explained.zipWithIndex.map { case (s, i) => s"$i. $s" }
.mkString(s"Multiple plugins are available that can provide $notFoundKey:\n", "\n", "")
else s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}"
def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ")
val imPostfix = if (impossible.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are impossible to add: $impossiblePlugins"
val imPostfix = if (impossible
.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are " +
s"impossible to add: $impossiblePlugins"
possibleString + imPostfix
} else if (impossible.isEmpty)
s"No available plugin provides key $notFoundKey."
else {
val explanations = impossible.map(explainPluginEnable)
explanations.mkString(s"Plugins are available that could provide $notFoundKey, but they are impossible to add:\n\t", "\n\t", "")
val preamble = s"Plugins are available that could provide $notFoundKey"
explanations.mkString(s"$preamble, but they are impossible to add:\n\t", "\n\t", "")
}
}
/** Text that suggests how to activate [[plugin]] in [[context]] if possible and if it is not already activated.*/
/** Text that suggests how to activate [[AutoPlugin]] in [[Context]] if possible and if it is not already activated. */
def help(plugin: AutoPlugin, context: Context): String =
if (context.enabled.contains(plugin)) activatedHelp(plugin)
else deactivatedHelp(plugin, context)
if (context.enabled.contains(plugin)) activatedHelp(plugin) else deactivatedHelp(plugin, context)
private def activatedHelp(plugin: AutoPlugin): String =
{
val prefix = s"${plugin.label} is activated."
@ -67,6 +78,7 @@ private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey:
val confsString = if (configs.isEmpty) "" else s"\nIt defines these configurations: ${multi(configs.map(_.name))}"
prefix + keysString + confsString
}
private def deactivatedHelp(plugin: AutoPlugin, context: Context): String =
{
val prefix = s"${plugin.label} is NOT activated."
@ -105,6 +117,7 @@ private[sbt] object PluginsDebug {
import extracted._
structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap
}
private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] =
build.unit.plugins.detected.autoPlugins map { _.value }
@ -120,7 +133,10 @@ private[sbt] object PluginsDebug {
lazy val debug = PluginsDebug(context.available)
if (!pluginsThisBuild.contains(plugin)) {
val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1)
s"Plugin ${plugin.label} is only available in builds:\n\t${availableInBuilds.mkString("\n\t")}\nSwitch to a project in one of those builds using `project` and rerun this command for more information."
val s1 = s"Plugin ${plugin.label} is only available in builds:"
val s2 = availableInBuilds.mkString("\n\t")
val s3 = s"Switch to a project in one of those builds using `project` and rerun this command for more information."
s"$s1\n\t$s2\n$s3"
} else if (definesPlugin(currentProject))
debug.activatedHelp(plugin)
else {
@ -128,7 +144,8 @@ private[sbt] object PluginsDebug {
val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref)))
if (definedInAggregated.nonEmpty) {
val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified
s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t${projectNames.mkString("\n\t")}"
val s2 = projectNames.mkString("\n\t")
s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t$s2"
} else {
val base = debug.deactivatedHelp(plugin, context)
val aggNote = if (thisAggregated.nonEmpty) "Note: This project aggregates other projects and this" else "Note: This"
@ -139,7 +156,7 @@ private[sbt] object PluginsDebug {
}
}
/** Precomputes information for debugging plugins. */
/** Pre-computes information for debugging plugins. */
def apply(available: List[AutoPlugin]): PluginsDebug =
{
val keyR = definedKeys(available)
@ -154,36 +171,58 @@ private[sbt] object PluginsDebug {
* @param deducePlugin The function used to compute the model.
* @param available All [[AutoPlugin]]s available for consideration.
*/
final case class Context(initial: Plugins, enabled: Seq[AutoPlugin], deducePlugin: (Plugins, Logger) => Seq[AutoPlugin], available: List[AutoPlugin], log: Logger)
final case class Context(
initial: Plugins,
enabled: Seq[AutoPlugin],
deducePlugin: (Plugins, Logger) => Seq[AutoPlugin],
available: List[AutoPlugin],
log: Logger
)
/** Describes the steps to activate a plugin in some context. */
sealed abstract class PluginEnable
/** Describes a [[plugin]] that is already activated in the [[context]].*/
final case class PluginActivated(plugin: AutoPlugin, context: Context) extends PluginEnable
sealed abstract class EnableDeactivated extends PluginEnable
/** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */
final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin]) extends EnableDeactivated
final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin])
extends EnableDeactivated
/**
* Describes the requirements for activating [[plugin]] in [[context]].
* @param context The base plugins, exclusions, and ultimately activated plugins
* @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped
* @param enablingPlugins [[AutoPlugin]]s that are not currently enabled, but need to be enabled for [[plugin]] to activate
* @param extraEnabledPlugins Plugins that will be enabled as a result of [[plugin]] activating, but are not required for [[plugin]] to activate
* @param enablingPlugins [[AutoPlugin]]s that are not currently enabled,
* but need to be enabled for [[plugin]] to activate
* @param extraEnabledPlugins Plugins that will be enabled as a result of [[plugin]] activating,
* but are not required for [[plugin]] to activate
* @param willRemove Plugins that will be deactivated as a result of [[plugin]] activating
* @param deactivate Describes plugins that must be deactivated for [[plugin]] to activate. These require an explicit exclusion or dropping a transitive [[AutoPlugin]].
* @param deactivate Describes plugins that must be deactivated for [[plugin]] to activate.
* These require an explicit exclusion or dropping a transitive [[AutoPlugin]].
*/
final case class PluginRequirements(plugin: AutoPlugin, context: Context, blockingExcludes: Set[AutoPlugin], enablingPlugins: Set[AutoPlugin], extraEnabledPlugins: Set[AutoPlugin], willRemove: Set[AutoPlugin], deactivate: List[DeactivatePlugin]) extends EnableDeactivated
final case class PluginRequirements(
plugin: AutoPlugin,
context: Context,
blockingExcludes: Set[AutoPlugin],
enablingPlugins: Set[AutoPlugin],
extraEnabledPlugins: Set[AutoPlugin],
willRemove: Set[AutoPlugin],
deactivate: List[DeactivatePlugin]
) extends EnableDeactivated
/**
* Describes a [[plugin]] that must be removed in order to activate another plugin in some context.
* The [[plugin]] can always be directly, explicitly excluded.
* @param removeOneOf If non-empty, removing one of these [[AutoPlugin]]s will deactivate [[plugin]] without affecting the other plugin. If empty, a direct exclusion is required.
* @param removeOneOf If non-empty, removing one of these [[AutoPlugin]]s will deactivate [[plugin]] without
* affecting the other plugin. If empty, a direct exclusion is required.
* @param newlySelected If false, this plugin was selected in the original context.
*/
final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean)
/** Determines how to enable [[plugin]] in [[context]]. */
/** Determines how to enable [[AutoPlugin]] in [[Context]]. */
def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable =
if (context.enabled.contains(plugin))
PluginActivated(plugin, context)
@ -237,12 +276,15 @@ private[sbt] object PluginsDebug {
// The model that results when the minimal plugins are enabled and the minimal plugins are excluded.
// This can include more plugins than just `minRequiredPlugins` because the plugins required for `plugin`
// might activate other plugins as well.
val incrementalInputs = and(includeAll(minRequiredPlugins ++ initialPlugins), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins))
val incrementalInputs = and(
includeAll(minRequiredPlugins ++ initialPlugins),
excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins)
)
val incrementalModel = context.deducePlugin(incrementalInputs, context.log).toSet
// Plugins that are newly enabled as a result of selecting the plugins needed for `plugin`, but aren't strictly required for `plugin`.
// These could be excluded and `plugin` and the user's current plugins would still be activated.
val extraPlugins = incrementalModel.toSet -- minRequiredPlugins -- initialModel
val extraPlugins = incrementalModel -- minRequiredPlugins -- initialModel
// Plugins that will no longer be enabled as a result of enabling `plugin`.
val willRemove = initialModel -- incrementalModel
@ -318,8 +360,10 @@ private[sbt] object PluginsDebug {
private[this] def excludedPluginError(transitive: Boolean)(dependency: AutoPlugin) =
s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded."
private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) =
s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}"
private[this] def transitiveString(transitive: Boolean) =
if (transitive) "(transitive) " else ""
@ -328,6 +372,7 @@ private[sbt] object PluginsDebug {
private[this] def requiredPlugin(plugin: AutoPlugin) =
s"Required plugin ${plugin.label} not present."
private[this] def requiredPlugins(plugins: List[AutoPlugin]) =
s"Required plugins not present:\n\t${plugins.map(_.label).mkString("\n\t")}"
@ -343,6 +388,7 @@ private[sbt] object PluginsDebug {
private[this] def willAddPlugin(base: AutoPlugin)(plugin: AutoPlugin) =
s"Enabling ${base.label} will also enable ${plugin.label}"
private[this] def willAddPlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) =
s"Enabling ${base.label} will also enable:\n\t${labels(plugins).mkString("\n\t")}"
@ -351,6 +397,7 @@ private[sbt] object PluginsDebug {
private[this] def willRemovePlugin(base: AutoPlugin)(plugin: AutoPlugin) =
s"Enabling ${base.label} will disable ${plugin.label}"
private[this] def willRemovePlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) =
s"Enabling ${base.label} will disable:\n\t${labels(plugins).mkString("\n\t")}"
@ -359,10 +406,13 @@ private[sbt] object PluginsDebug {
private[this] def needToDeactivate(deactivate: List[DeactivatePlugin]): String =
str(deactivate)(deactivate1, deactivateN)
private[this] def deactivateN(plugins: List[DeactivatePlugin]): String =
plugins.map(deactivateString).mkString("These plugins need to be deactivated:\n\t", "\n\t", "")
private[this] def deactivate1(deactivate: DeactivatePlugin): String =
s"Need to deactivate ${deactivateString(deactivate)}"
private[this] def deactivateString(d: DeactivatePlugin): String =
{
val removePluginsString: String =
@ -377,8 +427,17 @@ private[sbt] object PluginsDebug {
private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String =
str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin))
private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String =
s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent. Please report the problem to the plugin's author."
private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String =
s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires these plugins to be both present and absent:\n\t${labels(contradictions).mkString("\n\t")}\nPlease report the problem to the plugin's author."
private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String = {
val s1 = s"There is no way to enable plugin ${plugin.label}."
val s2 = s"It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent."
val s3 = s"Please report the problem to the plugin's author."
s"$s1 $s2 $s3"
}
private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String = {
val s1 = s"There is no way to enable plugin ${plugin.label}."
val s2 = s"It (or its dependencies) requires these plugins to be both present and absent:"
val s3 = s"Please report the problem to the plugin's author."
s"$s1 $s2:\n\t${labels(contradictions).mkString("\n\t")}\n$s3"
}
}

View File

@ -4,29 +4,29 @@
package sbt
package internal
import sbt.internal.util.complete
import Project.updateCurrent
import Keys.sessionSettings
import complete.{ DefaultParsers, Parser }
import DefaultParsers._
import java.net.URI
import sbt.internal.util.complete, complete.{ DefaultParsers, Parser }, DefaultParsers._
import sbt.compiler.Eval
import Keys.sessionSettings
import Project.updateCurrent
object ProjectNavigation {
def command(s: State): Parser[() => State] =
if (s get sessionSettings isEmpty) failure("No project loaded") else (new ProjectNavigation(s)).command
}
final class ProjectNavigation(s: State) {
val extracted = Project extract s
val extracted: Extracted = Project extract s
import extracted.{ currentRef, structure, session }
def setProject(nuri: URI, nid: String) =
def setProject(nuri: URI, nid: String): State =
{
val neval = if (currentRef.build == nuri) session.currentEval else mkEval(nuri)
updateCurrent(s.put(sessionSettings, session.setCurrent(nuri, nid, neval)))
}
def mkEval(nuri: URI) = Load.lazyEval(structure.units(nuri).unit)
def getRoot(uri: URI) = Load.getRootProject(structure.units)(uri)
def mkEval(nuri: URI): () => Eval = Load.lazyEval(structure.units(nuri).unit)
def getRoot(uri: URI): String = Load.getRootProject(structure.units)(uri)
def apply(action: Option[ResolvedReference]): State =
action match {
@ -38,12 +38,13 @@ final class ProjectNavigation(s: State) {
if(to.length > 1) gotoParent(to.length - 1, nav, s) else s */ // semantics currently undefined
}
def show(): Unit = s.log.info(currentRef.project + " (in build " + currentRef.build + ")")
def show(): Unit = s.log.info(s"${currentRef.project} (in build ${currentRef.build})")
def selectProject(uri: URI, to: String): State =
if (structure.units(uri).defined.contains(to))
setProject(uri, to)
else
fail("Invalid project name '" + to + "' in build " + uri + " (type 'projects' to list available projects).")
fail(s"Invalid project name '$to' in build $uri (type 'projects' to list available projects).")
def changeBuild(newBuild: URI): State =
if (structure.units contains newBuild)
@ -51,14 +52,9 @@ final class ProjectNavigation(s: State) {
else
fail("Invalid build unit '" + newBuild + "' (type 'projects' to list available builds).")
def fail(msg: String): State =
{
s.log.error(msg)
s.fail
}
def fail(msg: String): State = { s.log.error(msg); s.fail }
import complete.Parser._
import complete.Parsers._
import Parser._, complete.Parsers._
val parser: Parser[Option[ResolvedReference]] =
{
@ -66,6 +62,8 @@ final class ProjectNavigation(s: State) {
val root = token('/' ^^^ rootRef)
success(None) | some(token(Space) ~> (root | reference))
}
def rootRef = ProjectRef(currentRef.build, getRoot(currentRef.build))
val command: Parser[() => State] = Command.applyEffect(parser)(apply)
}

View File

@ -20,21 +20,24 @@ import sbt.io.IO
/**
* Represents (potentially) transient settings added into a build via commands/user.
*
* @param currentBuild
* The current sbt build with which we scope new settings
* @param currentProject
* The current project with which we scope new settings.
* @param original
* The original list of settings for this build.
* @param append
* Settings which have been defined and appended that may ALSO be saved to disk.
* @param rawAppend
* Settings which have been defined and appended which CANNOT be saved to disk
* @param currentEval
* A compiler we can use to compile new setting strings.
* @param currentBuild The current sbt build with which we scope new settings
* @param currentProject The current project with which we scope new settings.
* @param original The original list of settings for this build.
* @param append Settings which have been defined and appended that may ALSO be saved to disk.
* @param rawAppend Settings which have been defined and appended which CANNOT be saved to disk
* @param currentEval A compiler we can use to compile new setting strings.
*/
final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, String], original: Seq[Setting[_]], append: SessionMap, rawAppend: Seq[Setting[_]], currentEval: () => Eval) {
assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.")
final case class SessionSettings(
currentBuild: URI,
currentProject: Map[URI, String],
original: Seq[Setting[_]],
append: SessionMap,
rawAppend: Seq[Setting[_]],
currentEval: () => Eval
) {
assert(currentProject contains currentBuild,
s"Current build ($currentBuild) not associated with a current project.")
/**
* Modifiy the current state.
@ -44,7 +47,8 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str
* @param eval The mechanism to compile new settings.
* @return A new SessionSettings object
*/
def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings = copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval)
def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings =
copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval)
/**
* @return The current ProjectRef with which we scope settings.
@ -86,6 +90,7 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str
object SessionSettings {
/** A session setting is simply a tuple of a Setting[_] and the strings which define it. */
type SessionSetting = (Setting[_], Seq[String])
type SessionMap = Map[ProjectRef, Seq[SessionSetting]]
type SbtConfigFile = (File, Seq[String])
@ -305,7 +310,7 @@ save, save-all
def range: Parser[(Int, Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map { case lo ~ hi => (lo, hi getOrElse lo) }
/** The raw implementation of the session command. */
def command(s: State) = Command.applyEffect(parser) {
def command(s: State): Parser[() => State] = Command.applyEffect(parser) {
case p: Print => if (p.all) printAllSettings(s) else printSettings(s)
case v: Save => if (v.all) saveAllSettings(s) else saveSettings(s)
case c: Clear => if (c.all) clearAllSettings(s) else clearSettings(s)

View File

@ -116,7 +116,7 @@ private[sbt] object SettingCompletions {
val full = for {
defineKey <- scopedKeyParser(keyMap, settings, context)
a <- assign(defineKey)
deps <- valueParser(defineKey, a, inputScopedKey(keyFilter(defineKey.key)))
_ <- valueParser(defineKey, a, inputScopedKey(keyFilter(defineKey.key)))
} yield () // parser is currently only for completion and the parsed data structures are not used
matched(full) | any.+.string

View File

@ -125,7 +125,7 @@ private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends Parsed
case _ => false
}
/**
/*
* See BugInParser
* @param t - tree
* @param originalStatement - original