sbt.internal.Act.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main_2.12 Show documentation
Show all versions of main_2.12 Show documentation
sbt is an interactive build tool
The newest version!
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
import Def.{ showRelativeKey2, ScopedKey }
import Keys.sessionSettings
import sbt.internal.util.complete.{ DefaultParsers, Parser }
import Aggregation.{ KeyValue, Values }
import DefaultParsers._
import sbt.internal.util.Types.idFun
import java.net.URI
import sbt.internal.CommandStrings.{ MultiTaskCommand, ShowCommand, PrintCommand }
import sbt.internal.util.{ AttributeEntry, AttributeKey, AttributeMap, IMap, Settings, Util }
import sbt.util.Show
import scala.collection.mutable
final class ParsedKey(val key: ScopedKey[_], val mask: ScopeMask, val separaters: Seq[String]) {
def this(key: ScopedKey[_], mask: ScopeMask) = this(key, mask, Nil)
override def equals(o: Any): Boolean =
this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: ParsedKey => (this.key == x.key) && (this.mask == x.mask)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (17 + "sbt.internal.ParsedKey".##) + this.key.##)) + this.mask.##
}
}
object Act {
val ZeroString = "*"
private[sbt] val GlobalIdent = "Global"
private[sbt] val ZeroIdent = "Zero"
private[sbt] val ThisBuildIdent = "ThisBuild"
// new separator for unified shell syntax. this allows optional whitespace around /.
private[sbt] val spacedSlash: Parser[Unit] =
token(OptSpace ~> '/' <~ OptSpace).examples("/").map(_ => ())
private[sbt] val slashSeq: Seq[String] = Seq("/")
private[sbt] val colonSeq: Seq[String] = Seq(":")
private[sbt] val colonColonSeq: Seq[String] = Seq("::")
// this does not take aggregation into account
def scopedKey(
index: KeyIndex,
current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]],
data: Settings[Scope]
): Parser[ScopedKey[_]] =
scopedKeySelected(index, current, defaultConfigs, keyMap, data).map(_.key)
// the index should be an aggregated index for proper tab completion
def scopedKeyAggregated(
current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
structure: BuildStructure
): KeysParser =
for (selected <- scopedKeySelected(
structure.index.aggregateKeyIndex,
current,
defaultConfigs,
structure.index.keyMap,
structure.data
))
yield Aggregation.aggregate(selected.key, selected.mask, structure.extra)
def scopedKeyAggregatedSep(
current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
structure: BuildStructure
): KeysParserSep =
for (selected <- scopedKeySelected(
structure.index.aggregateKeyIndex,
current,
defaultConfigs,
structure.index.keyMap,
structure.data
))
yield Aggregation
.aggregate(selected.key, selected.mask, structure.extra)
.map(k => k -> selected.separaters)
def scopedKeySelected(
index: KeyIndex,
current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]],
data: Settings[Scope]
): Parser[ParsedKey] =
scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices =>
select(choices, data)(showRelativeKey2(current))
}
def scopedKeyFull(
index: KeyIndex,
current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]]
): Parser[Seq[Parser[ParsedKey]]] = {
val confParserCache
: mutable.Map[Option[sbt.ResolvedReference], Parser[(ParsedAxis[String], Seq[String])]] =
mutable.Map.empty
def fullKey =
for {
rawProject <- optProjectRef(index, current)
proj = resolveProject(rawProject, current)
confPair <- confParserCache.getOrElseUpdate(
proj,
configIdent(
index.configs(proj),
index.configIdents(proj),
index.fromConfigIdent(proj)
)
)
(confAmb, seps) = confPair
partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false)
} yield taskKeyExtra(index, defaultConfigs, keyMap, proj, confAmb, partialMask, seps)
val globalIdent = token(GlobalIdent ~ spacedSlash) ^^^ ParsedGlobal
def globalKey =
for {
g <- globalIdent
} yield taskKeyExtra(
index,
defaultConfigs,
keyMap,
None,
ParsedZero,
ScopeMask(true, true, false, false),
Nil
)
globalKey | fullKey
}
def taskKeyExtra(
index: KeyIndex,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]],
proj: Option[ResolvedReference],
confAmb: ParsedAxis[String],
baseMask: ScopeMask,
baseSeps: Seq[String]
): Seq[Parser[ParsedKey]] =
for {
conf <- configs(confAmb, defaultConfigs, proj, index)
} yield for {
taskPair <- taskAxis(index.tasks(proj, conf), keyMap)
(taskAmb, taskSeps) = taskPair
task = resolveTask(taskAmb)
key <- key(index, proj, conf, task, keyMap)
extra <- extraAxis(keyMap, IMap.empty)
} yield {
val mask = baseMask.copy(task = taskAmb.isExplicit, extra = true)
val seps = baseSeps ++ taskSeps
new ParsedKey(makeScopedKey(proj, conf, task, extra, key), mask, seps)
}
def makeScopedKey(
proj: Option[ResolvedReference],
conf: Option[String],
task: Option[AttributeKey[_]],
extra: ScopeAxis[AttributeMap],
key: AttributeKey[_]
): ScopedKey[_] =
ScopedKey(
Scope(toAxis(proj, Zero), toAxis(conf map ConfigKey.apply, Zero), toAxis(task, Zero), extra),
key
)
def select(allKeys: Seq[Parser[ParsedKey]], data: Settings[Scope])(
implicit show: Show[ScopedKey[_]]
): Parser[ParsedKey] =
seq(allKeys) flatMap { ss =>
val default = ss.headOption match {
case None => noValidKeys
case Some(x) => success(x)
}
selectFromValid(ss filter isValid(data), default)
}
def selectFromValid(ss: Seq[ParsedKey], default: Parser[ParsedKey])(
implicit show: Show[ScopedKey[_]]
): Parser[ParsedKey] =
selectByTask(selectByConfig(ss)) match {
case Seq() => default
case Seq(single) => success(single)
case multi => failure("Ambiguous keys: " + showAmbiguous(keys(multi)))
}
private[this] def keys(ss: Seq[ParsedKey]): Seq[ScopedKey[_]] = ss.map(_.key)
def selectByConfig(ss: Seq[ParsedKey]): Seq[ParsedKey] =
ss match {
case Seq() => Nil
case Seq(x, tail @ _*) => // select the first configuration containing a valid key
tail.takeWhile(_.key.scope.config == x.key.scope.config) match {
case Seq() => x :: Nil
case xs => x +: xs
}
}
def selectByTask(ss: Seq[ParsedKey]): Seq[ParsedKey] = {
val (selects, zeros) = ss.partition(_.key.scope.task.isSelect)
if (zeros.nonEmpty) zeros else selects
}
def noValidKeys = failure("No such key.")
def showAmbiguous(keys: Seq[ScopedKey[_]])(implicit show: Show[ScopedKey[_]]): String =
keys.take(3).map(x => show.show(x)).mkString("", ", ", if (keys.size > 3) ", ..." else "")
def isValid(data: Settings[Scope])(parsed: ParsedKey): Boolean = {
val key = parsed.key
data.definingScope(key.scope, key.key) == Some(key.scope)
}
def examples(p: Parser[String], exs: Set[String], label: String): Parser[String] =
p !!! ("Expected " + label) examples exs
def examplesStrict(p: Parser[String], exs: Set[String], label: String): Parser[String] =
filterStrings(examples(p, exs, label), exs, label)
def optionalAxis[T](p: Parser[T], ifNone: ScopeAxis[T]): Parser[ScopeAxis[T]] =
p.? map { opt =>
toAxis(opt, ifNone)
}
def toAxis[T](opt: Option[T], ifNone: ScopeAxis[T]): ScopeAxis[T] =
opt match { case Some(t) => Select(t); case None => ifNone }
def config(confs: Set[String]): Parser[ParsedAxis[String]] = {
val sep = ':' !!! "Expected ':' (if selecting a configuration)"
token((ZeroString ^^^ ParsedZero | value(examples(ID, confs, "configuration"))) <~ sep) ?? Omitted
}
// New configuration parser that's able to parse configuration ident trailed by slash.
private[sbt] def configIdent(
confs: Set[String],
idents: Set[String],
fromIdent: String => String
): Parser[(ParsedAxis[String], Seq[String])] = {
val oldSep: Parser[Char] = ':'
val sep: Parser[Unit] = spacedSlash !!! "Expected '/'"
token(
((ZeroString ^^^ (ParsedZero -> colonSeq)) <~ oldSep)
| ((ZeroString ^^^ (ParsedZero -> slashSeq)) <~ sep)
| ((ZeroIdent ^^^ (ParsedZero -> slashSeq)) <~ sep)
| (value(examples(ID, confs, "configuration")).map(_ -> colonSeq) <~ oldSep)
| (value(examples(CapitalizedID, idents, "configuration ident").map(fromIdent))
.map(_ -> slashSeq) <~ sep)
) ?? (Omitted -> Nil)
}
def configs(
explicit: ParsedAxis[String],
defaultConfigs: Option[ResolvedReference] => Seq[String],
proj: Option[ResolvedReference],
index: KeyIndex
): Seq[Option[String]] =
explicit match {
case Omitted =>
None +: defaultConfigurations(proj, index, defaultConfigs).flatMap(
nonEmptyConfig(index, proj)
)
case ParsedZero | ParsedGlobal => None :: Nil
case pv: ParsedValue[x] => Some(pv.value) :: Nil
}
def defaultConfigurations(
proj: Option[ResolvedReference],
index: KeyIndex,
defaultConfigs: Option[ResolvedReference] => Seq[String]
): Seq[String] =
if (index exists proj) defaultConfigs(proj) else Nil
def nonEmptyConfig(
index: KeyIndex,
proj: Option[ResolvedReference]
): String => Seq[Option[String]] =
config => if (index.isEmpty(proj, Some(config))) Nil else Some(config) :: Nil
def key(
index: KeyIndex,
proj: Option[ResolvedReference],
conf: Option[String],
task: Option[AttributeKey[_]],
keyMap: Map[String, AttributeKey[_]]
): Parser[AttributeKey[_]] = {
def dropHyphenated(keys: Set[String]): Set[String] = keys.filterNot(Util.hasHyphen)
def keyParser(keys: Set[String]): Parser[AttributeKey[_]] =
token(ID !!! "Expected key" examples dropHyphenated(keys)) flatMap { keyString =>
getKey(keyMap, keyString, idFun)
}
// Fixes sbt/sbt#2460 and sbt/sbt#2851
// The parser already accepts build-level keys.
// This queries the key index so tab completion will list the build-level keys.
val buildKeys: Set[String] =
proj match {
case Some(ProjectRef(uri, _)) => index.keys(Some(BuildRef(uri)), conf, task)
case _ => Set()
}
val globalKeys: Set[String] =
proj match {
case Some(_) => index.keys(None, conf, task)
case _ => Set()
}
val keys: Set[String] = index.keys(proj, conf, task) ++ buildKeys ++ globalKeys
keyParser(keys)
}
def getKey[T](
keyMap: Map[String, AttributeKey[_]],
keyString: String,
f: AttributeKey[_] => T
): Parser[T] =
keyMap.get(keyString) match {
case Some(k) => success(f(k))
case None => failure(Command.invalidValue("key", keyMap.keys)(keyString))
}
val spacedComma = token(OptSpace ~ ',' ~ OptSpace)
def extraAxis(
knownKeys: Map[String, AttributeKey[_]],
knownValues: IMap[AttributeKey, Set]
): Parser[ScopeAxis[AttributeMap]] = {
val extrasP = extrasParser(knownKeys, knownValues)
val extras = token('(', hide = _ == 1 && knownValues.isEmpty) ~> extrasP <~ token(')')
optionalAxis(extras, Zero)
}
def taskAxis(
tasks: Set[AttributeKey[_]],
allKnown: Map[String, AttributeKey[_]],
): Parser[(ParsedAxis[AttributeKey[_]], Seq[String])] = {
val taskSeq = tasks.toSeq
def taskKeys(f: AttributeKey[_] => String): Seq[(String, AttributeKey[_])] =
taskSeq.map(key => (f(key), key))
val normKeys = taskKeys(_.label)
val valid = allKnown ++ normKeys
val suggested = normKeys.map(_._1).toSet
val keyP = filterStrings(examples(ID, suggested, "key"), valid.keySet, "key") map valid
((token(
value(keyP).map(_ -> slashSeq)
| ZeroString ^^^ (ParsedZero -> slashSeq)
| ZeroIdent ^^^ (ParsedZero -> slashSeq)
) <~ spacedSlash) |
(token(
value(keyP).map(_ -> colonColonSeq)
| ZeroString ^^^ (ParsedZero -> colonColonSeq)
| ZeroIdent ^^^ (ParsedZero -> colonColonSeq)
) <~ token("::".id))) ?? (Omitted -> Nil)
}
def resolveTask(task: ParsedAxis[AttributeKey[_]]): Option[AttributeKey[_]] =
task match {
case ParsedZero | ParsedGlobal | Omitted => None
case t: ParsedValue[AttributeKey[_]] @unchecked => Some(t.value)
}
def filterStrings(base: Parser[String], valid: Set[String], label: String): Parser[String] =
base.filter(valid, Command.invalidValue(label, valid))
def extrasParser(
knownKeys: Map[String, AttributeKey[_]],
knownValues: IMap[AttributeKey, Set]
): Parser[AttributeMap] = {
val validKeys = knownKeys.filter { case (_, key) => knownValues get key exists (_.nonEmpty) }
if (validKeys.isEmpty)
failure("No valid extra keys.")
else
rep1sep(extraParser(validKeys, knownValues), spacedComma) map AttributeMap.apply
}
def extraParser(
knownKeys: Map[String, AttributeKey[_]],
knownValues: IMap[AttributeKey, Set]
): Parser[AttributeEntry[_]] = {
val keyp = knownIDParser(knownKeys, "Not a valid extra key") <~ token(':' ~ OptSpace)
keyp flatMap {
case key: AttributeKey[t] =>
val valueMap: Map[String, t] = knownValues(key).map(v => (v.toString, v)).toMap
knownIDParser(valueMap, "extra value") map { value =>
AttributeEntry(key, value)
}
}
}
def knownIDParser[T](knownKeys: Map[String, T], label: String): Parser[T] =
token(examplesStrict(ID, knownKeys.keys.toSet, label)) map knownKeys
def knownPluginParser[T](knownPlugins: Map[String, T], label: String): Parser[T] = {
val pluginLabelParser = rep1sep(ID, '.').map(_.mkString("."))
token(examplesStrict(pluginLabelParser, knownPlugins.keys.toSet, label)) map knownPlugins
}
def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedAxis[ResolvedReference]] = {
val global = token(ZeroString ~ spacedSlash) ^^^ ParsedZero
val zeroIdent = token(ZeroIdent ~ spacedSlash) ^^^ ParsedZero
val thisBuildIdent = value(token(ThisBuildIdent ~ spacedSlash) ^^^ BuildRef(currentBuild))
val trailing = spacedSlash !!! "Expected '/' (if selecting a project)"
global | zeroIdent | thisBuildIdent |
value(resolvedReferenceIdent(index, currentBuild, trailing)) |
value(resolvedReference(index, currentBuild, trailing))
}
private[sbt] def resolvedReferenceIdent(
index: KeyIndex,
currentBuild: URI,
trailing: Parser[_]
): Parser[ResolvedReference] = {
def projectID(uri: URI) =
token(
DQuoteChar ~> examplesStrict(ID, index projects uri, "project ID") <~ DQuoteChar <~ OptSpace <~ ")" <~ trailing
)
def projectRef(uri: URI) = projectID(uri) map { id =>
ProjectRef(uri, id)
}
val uris = index.buildURIs
val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri))
val buildRef = token(
"ProjectRef(" ~> OptSpace ~> "uri(" ~> OptSpace ~> DQuoteChar ~>
resolvedURI <~ DQuoteChar <~ OptSpace <~ ")" <~ spacedComma
)
buildRef flatMap { uri =>
projectRef(uri)
}
}
def resolvedReference(
index: KeyIndex,
currentBuild: URI,
trailing: Parser[_]
): Parser[ResolvedReference] = {
def projectID(uri: URI) =
token(examplesStrict(ID, index projects uri, "project ID") <~ trailing)
def projectRef(uri: URI) = projectID(uri) map { id =>
ProjectRef(uri, id)
}
val uris = index.buildURIs
val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri))
val buildRef = token('{' ~> resolvedURI <~ '}').?
buildRef flatMap {
case None => projectRef(currentBuild)
case Some(uri) => projectRef(uri) | token(trailing ^^^ BuildRef(uri))
}
}
def optProjectRef(index: KeyIndex, current: ProjectRef): Parser[ParsedAxis[ResolvedReference]] =
projectRef(index, current.build) ?? Omitted
def resolveProject(
parsed: ParsedAxis[ResolvedReference],
current: ProjectRef
): Option[ResolvedReference] =
parsed match {
case Omitted => Some(current)
case ParsedZero => None
case ParsedGlobal => None
case pv: ParsedValue[rr] => Some(pv.value)
}
def actParser(s: State): Parser[() => State] = requireSession(s, actParser0(s))
private[this] def actParser0(state: State): Parser[() => State] = {
val extracted = Project extract state
import extracted.{ showKey, structure }
import Aggregation.evaluatingParser
actionParser.flatMap { action =>
val akp = aggregatedKeyParserSep(extracted)
def warnOldShellSyntax(seps: Seq[String], keyStrings: String): Unit =
if (seps.contains(":") || seps.contains("::")) {
state.log.warn(
s"sbt 0.13 shell syntax is deprecated; use slash syntax instead: $keyStrings"
)
} else ()
def evaluate(pairs: Seq[(ScopedKey[_], Seq[String])]): Parser[() => State] = {
val kvs = pairs.map(_._1)
val seps = pairs.headOption.map(_._2).getOrElse(Nil)
val preparedPairs = anyKeyValues(structure, kvs)
val showConfig = if (action == PrintAction) {
Aggregation.ShowConfig(true, true, println, false)
} else {
Aggregation.defaultShow(state, showTasks = action == ShowAction)
}
evaluatingParser(state, showConfig)(preparedPairs) map { evaluate => () =>
{
val keyStrings = preparedPairs.map(pp => showKey.show(pp.key)).mkString(", ")
state.log.debug("Evaluating tasks: " + keyStrings)
warnOldShellSyntax(seps, keyStrings)
evaluate()
}
}
}
action match {
case SingleAction => akp flatMap evaluate
case ShowAction | PrintAction | MultiAction =>
rep1sep(akp, token(Space)) flatMap { pairs =>
val flat: mutable.ListBuffer[(ScopedKey[_], Seq[String])] = mutable.ListBuffer.empty
pairs foreach { xs =>
flat ++= xs
}
evaluate(flat.toList)
}
}
}
}
private[this] final class ActAction
private[this] final val ShowAction, MultiAction, SingleAction, PrintAction = new ActAction
private[this] def actionParser: Parser[ActAction] =
token(
((ShowCommand ^^^ ShowAction) |
(PrintCommand ^^^ PrintAction) |
(MultiTaskCommand ^^^ MultiAction)) <~ Space
) ?? SingleAction
def scopedKeyParser(state: State): Parser[ScopedKey[_]] = scopedKeyParser(Project extract state)
def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] =
scopedKeyParser(extracted.structure, extracted.currentRef)
def scopedKeyParser(structure: BuildStructure, currentRef: ProjectRef): Parser[ScopedKey[_]] =
scopedKey(
structure.index.keyIndex,
currentRef,
structure.extra.configurationsForAxis,
structure.index.keyMap,
structure.data
)
type KeysParser = Parser[Seq[ScopedKey[T]] forSome { type T }]
type KeysParserSep = Parser[Seq[(ScopedKey[T], Seq[String])] forSome { type T }]
def aggregatedKeyParser(state: State): KeysParser = aggregatedKeyParser(Project extract state)
def aggregatedKeyParser(extracted: Extracted): KeysParser =
aggregatedKeyParser(extracted.structure, extracted.currentRef)
def aggregatedKeyParser(structure: BuildStructure, currentRef: ProjectRef): KeysParser =
scopedKeyAggregated(currentRef, structure.extra.configurationsForAxis, structure)
private[sbt] def aggregatedKeyParserSep(extracted: Extracted): KeysParserSep =
aggregatedKeyParserSep(extracted.structure, extracted.currentRef)
private[sbt] def aggregatedKeyParserSep(
structure: BuildStructure,
currentRef: ProjectRef
): KeysParserSep =
scopedKeyAggregatedSep(currentRef, structure.extra.configurationsForAxis, structure)
def keyValues[T](state: State)(keys: Seq[ScopedKey[T]]): Values[T] =
keyValues(Project extract state)(keys)
def keyValues[T](extracted: Extracted)(keys: Seq[ScopedKey[T]]): Values[T] =
keyValues(extracted.structure)(keys)
def keyValues[T](structure: BuildStructure)(keys: Seq[ScopedKey[T]]): Values[T] =
keys.flatMap { key =>
getValue(structure.data, key.scope, key.key) map { value =>
KeyValue(key, value)
}
}
private[this] def anyKeyValues(
structure: BuildStructure,
keys: Seq[ScopedKey[_]]
): Seq[KeyValue[_]] =
keys.flatMap { key =>
getValue(structure.data, key.scope, key.key) map { value =>
KeyValue(key, value)
}
}
private[this] def getValue[T](
data: Settings[Scope],
scope: Scope,
key: AttributeKey[T]
): Option[T] =
if (java.lang.Boolean.getBoolean("sbt.cli.nodelegation")) data.getDirect(scope, key)
else data.get(scope, key)
def requireSession[T](s: State, p: => Parser[T]): Parser[T] =
if (s get sessionSettings isEmpty) failure("No project loaded") else p
sealed trait ParsedAxis[+T] {
final def isExplicit = this != Omitted
}
final object ParsedGlobal extends ParsedAxis[Nothing]
final object ParsedZero extends ParsedAxis[Nothing]
final object Omitted extends ParsedAxis[Nothing]
final class ParsedValue[T](val value: T) extends ParsedAxis[T]
def value[T](t: Parser[T]): Parser[ParsedAxis[T]] = t map { v =>
new ParsedValue(v)
}
}