package sbt
import java.io.File
import java.net.URI
import compiler.{ Eval, EvalImports }
import complete.DefaultParsers.validID
import Def.{ ScopedKey, Setting, SettingsDefinition }
import Scope.GlobalScope
import scala.annotation.tailrec
object EvaluateConfigurations {
type LazyClassLoaded[T] = ClassLoader => T
private[sbt] case class TrackedEvalResult[T](generated: Seq[File], result: LazyClassLoaded[T])
private[this] final class ParsedFile(val imports: Seq[(String, Int)], val definitions: Seq[(String, LineRange)], val settings: Seq[(String, LineRange)])
private[this] val DefinitionKeywords = Seq("lazy val ", "def ", "val ")
@deprecated("We no longer merge build.sbt files together unless they are in the same directory.", "0.13.6")
def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): LazyClassLoaded[LoadedSbtFile] =
{
val loadFiles = srcs.sortBy(_.getName) map { src => evaluateSbtFile(eval, src, IO.readLines(src), imports, 0) }
loader => (LoadedSbtFile.empty /: loadFiles) { (loaded, load) => loaded merge load(loader) }
}
def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): LazyClassLoaded[Seq[Setting[_]]] =
evaluateConfiguration(eval, src, IO.readLines(src), imports, 0)
private[this] def parseConfiguration(lines: Seq[String], builtinImports: Seq[String], offset: Int): ParsedFile =
{
val (importStatements, settingsAndDefinitions) = splitExpressions(lines)
val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements)
val (definitions, settings) = splitSettingsDefinitions(addOffsetToRange(offset, settingsAndDefinitions))
new ParsedFile(allImports, definitions, settings)
}
def evaluateConfiguration(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): LazyClassLoaded[Seq[Setting[_]]] =
{
val l = evaluateSbtFile(eval, file, lines, imports, offset)
loader => l(loader).settings
}
private[sbt] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): LazyClassLoaded[LoadedSbtFile] =
{
val name = file.getPath
val parsed = parseConfiguration(lines, imports, offset)
val (importDefs, definitions) =
if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty) else {
val definitions = evaluateDefinitions(eval, name, parsed.imports, parsed.definitions, Some(file))
val imp = BuildUtil.importAllRoot(definitions.enclosingModule :: Nil)
val projs = (loader: ClassLoader) => definitions.values(loader).map(p => resolveBase(file.getParentFile, p.asInstanceOf[Project]))
(imp, DefinedSbtValues(definitions))
}
val allImports = importDefs.map(s => (s, -1)) ++ parsed.imports
val dslEntries = parsed.settings map {
case (dslExpression, range) =>
evaluateDslEntry(eval, name, allImports, dslExpression, range)
}
eval.unlinkDeferred()
val allGeneratedFiles = (definitions.generated ++ dslEntries.flatMap(_.generated))
loader => {
val projects =
definitions.values(loader).collect {
case p: Project => resolveBase(file.getParentFile, p)
}
val (settingsRaw, manipulationsRaw) =
dslEntries map (_.result apply loader) partition {
case internals.ProjectSettings(_) => true
case _ => false
}
val settings = settingsRaw flatMap {
case internals.ProjectSettings(settings) => settings
case _ => Nil
}
val manipulations = manipulationsRaw map {
case internals.ProjectManipulation(f) => f
}
new LoadedSbtFile(settings, projects, importDefs, manipulations, definitions, allGeneratedFiles)
}
}
private[this] def resolveBase(f: File, p: Project) = p.copy(base = IO.resolve(f, p.base))
@deprecated("Will no longer be public.", "0.13.6")
def flatten(mksettings: Seq[ClassLoader => Seq[Setting[_]]]): ClassLoader => Seq[Setting[_]] =
loader => mksettings.flatMap(_ apply loader)
def addOffset(offset: Int, lines: Seq[(String, Int)]): Seq[(String, Int)] =
lines.map { case (s, i) => (s, i + offset) }
def addOffsetToRange(offset: Int, ranges: Seq[(String, LineRange)]): Seq[(String, LineRange)] =
ranges.map { case (s, r) => (s, r shift offset) }
val SettingsDefinitionName = {
val _ = classOf[sbt.internals.DslEntry]
"sbt.internals.DslEntry"
}
private[sbt] def evaluateDslEntry(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): TrackedEvalResult[internals.DslEntry] = {
val result = try {
eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some(SettingsDefinitionName), line = range.start)
} catch {
case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage)
}
TrackedEvalResult(result.generated,
loader => {
val pos = RangePosition(name, range shift 1)
result.getValue(loader).asInstanceOf[internals.DslEntry].withPos(pos)
})
}
@deprecated("Build DSL now includes non-Setting[_] type settings.", "0.13.6")
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): LazyClassLoaded[Seq[Setting[_]]] =
{
evaluateDslEntry(eval, name, imports, expression, range).result andThen {
case internals.ProjectSettings(values) => values
case _ => Nil
}
}
private[this] def isSpace = (c: Char) => Character isWhitespace c
private[this] def fstS(f: String => Boolean): ((String, Int)) => Boolean = { case (s, i) => f(s) }
private[this] def firstNonSpaceIs(lit: String) = (_: String).view.dropWhile(isSpace).startsWith(lit)
private[this] def or[A](a: A => Boolean, b: A => Boolean): A => Boolean = in => a(in) || b(in)
def splitExpressions(lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) =
{
val blank = (_: String).forall(isSpace)
val isImport = firstNonSpaceIs("import ")
val = firstNonSpaceIs("//")
val = or(blank, comment)
val importOrBlank = fstS(or(blankOrComment, isImport))
val (imports, settings) = lines.zipWithIndex span importOrBlank
(imports filterNot fstS(blankOrComment), groupedLines(settings, blank, blankOrComment))
}
def groupedLines(lines: Seq[(String, Int)], delimiter: String => Boolean, skipInitial: String => Boolean): Seq[(String, LineRange)] =
{
val fdelim = fstS(delimiter)
@tailrec def group0(lines: Seq[(String, Int)], accum: Seq[(String, LineRange)]): Seq[(String, LineRange)] =
if (lines.isEmpty) accum.reverse
else {
val start = lines dropWhile fstS(skipInitial)
val (next, tail) = start.span { case (s, _) => !delimiter(s) }
val grouped = if (next.isEmpty) accum else (next.map(_._1).mkString("\n"), LineRange(next.head._2, next.last._2 + 1)) +: accum
group0(tail, grouped)
}
group0(lines, Nil)
}
private[this] def splitSettingsDefinitions(lines: Seq[(String, LineRange)]): (Seq[(String, LineRange)], Seq[(String, LineRange)]) =
lines partition { case (line, range) => isDefinition(line) }
private[this] def isDefinition(line: String): Boolean =
{
val trimmed = line.trim
DefinitionKeywords.exists(trimmed startsWith _)
}
private[this] def : Seq[String] =
Seq(classOf[Project], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]).map(_.getName)
private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String, Int)], definitions: Seq[(String, LineRange)], file: Option[File]): compiler.EvalDefinitions =
{
val convertedRanges = definitions.map { case (s, r) => (s, r.start to r.end) }
eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name, file, extractedValTypes)
}
}
object Index {
def taskToKeyMap(data: Settings[Scope]): Map[Task[_], ScopedKey[Task[_]]] =
{
val pairs = for (scope <- data.scopes; AttributeEntry(key, value: Task[_]) <- data.data(scope).entries) yield (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[_]]]))
pairs.toMap[Task[_], ScopedKey[Task[_]]]
}
def allKeys(settings: Seq[Setting[_]]): Set[ScopedKey[_]] =
settings.flatMap(s => if (s.key.key.isLocal) Nil else s.key +: s.dependencies).filter(!_.key.isLocal).toSet
def attributeKeys(settings: Settings[Scope]): Set[AttributeKey[_]] =
settings.data.values.flatMap(_.keys).toSet[AttributeKey[_]]
def stringToKeyMap(settings: Set[AttributeKey[_]]): Map[String, AttributeKey[_]] =
stringToKeyMap0(settings)(_.rawLabel) ++ stringToKeyMap0(settings)(_.label)
private[this] def stringToKeyMap0(settings: Set[AttributeKey[_]])(label: AttributeKey[_] => String): Map[String, AttributeKey[_]] =
{
val multiMap = settings.groupBy(label)
val duplicates = multiMap collect { case (k, xs) if xs.size > 1 => (k, xs.map(_.manifest)) } collect { case (k, xs) if xs.size > 1 => (k, xs) }
if (duplicates.isEmpty)
multiMap.collect { case (k, v) if validID(k) => (k, v.head) } toMap;
else
sys.error(duplicates map { case (k, tps) => "'" + k + "' (" + tps.mkString(", ") + ")" } mkString ("Some keys were defined with the same name but different types: ", ", ", ""))
}
private[this]type TriggerMap = collection.mutable.HashMap[Task[_], Seq[Task[_]]]
def triggers(ss: Settings[Scope]): Triggers[Task] =
{
val runBefore = new TriggerMap
val triggeredBy = new TriggerMap
for ((_, amap) <- ss.data; AttributeEntry(_, value: Task[_]) <- amap.entries) {
val as = value.info.attributes
update(runBefore, value, as get Keys.runBefore)
update(triggeredBy, value, as get Keys.triggeredBy)
}
val onComplete = Keys.onComplete in GlobalScope get ss getOrElse { () => () }
new Triggers[Task](runBefore, triggeredBy, map => { onComplete(); map })
}
private[this] def update(map: TriggerMap, base: Task[_], tasksOpt: Option[Seq[Task[_]]]): Unit =
for (tasks <- tasksOpt; task <- tasks)
map(task) = base +: map.getOrElse(task, Nil)
}