2011-01-19 00:24:11 +01:00
|
|
|
/* sbt -- Simple Build Tool
|
|
|
|
|
* Copyright 2011 Mark Harrah
|
|
|
|
|
*/
|
|
|
|
|
package sbt
|
|
|
|
|
|
|
|
|
|
import java.io.File
|
|
|
|
|
import java.net.URI
|
2011-04-09 01:15:13 +02:00
|
|
|
import compiler.{Eval, EvalImports}
|
2011-03-25 02:25:57 +01:00
|
|
|
import complete.DefaultParsers.validID
|
2011-04-09 01:15:13 +02:00
|
|
|
import Compiler.Compilers
|
|
|
|
|
import Project.{ScopedKey, Setting}
|
|
|
|
|
import Keys.Streams
|
|
|
|
|
import scala.annotation.tailrec
|
2011-01-19 00:24:11 +01:00
|
|
|
|
|
|
|
|
// name is more like BuildDefinition, but that is too long
|
|
|
|
|
trait Build
|
|
|
|
|
{
|
|
|
|
|
def projects: Seq[Project]
|
2011-03-05 14:25:17 +01:00
|
|
|
def settings: Seq[Setting[_]] = Defaults.buildCore
|
2011-01-19 00:24:11 +01:00
|
|
|
}
|
|
|
|
|
trait Plugin
|
|
|
|
|
{
|
2011-03-14 02:33:28 +01:00
|
|
|
def settings: Seq[Project.Setting[_]] = Nil
|
2011-01-19 00:24:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object Build
|
|
|
|
|
{
|
|
|
|
|
def default(base: File): Build = new Build { def projects = defaultProject("default", base) :: Nil }
|
|
|
|
|
def defaultProject(id: String, base: File): Project = Project(id, base)
|
2011-02-25 05:30:06 +01:00
|
|
|
|
|
|
|
|
def data[T](in: Seq[Attributed[T]]): Seq[T] = in.map(_.data)
|
2011-03-02 12:46:28 +01:00
|
|
|
def analyzed(in: Seq[Attributed[_]]): Seq[inc.Analysis] = in.flatMap{ _.metadata.get(Keys.analysis) }
|
2011-01-19 00:24:11 +01:00
|
|
|
}
|
|
|
|
|
object RetrieveUnit
|
|
|
|
|
{
|
|
|
|
|
def apply(tempDir: File, base: URI): File =
|
|
|
|
|
{
|
|
|
|
|
lazy val tmp = temporary(tempDir, base)
|
|
|
|
|
base.getScheme match
|
|
|
|
|
{
|
|
|
|
|
case "file" => val f = new File(base); if(f.isDirectory) f else error("Not a directory: '" + base + "'")
|
|
|
|
|
case "git" => gitClone(base, tmp); tmp
|
|
|
|
|
case "http" | "https" => downloadAndExtract(base, tmp); tmp
|
|
|
|
|
case _ => error("Unknown scheme in '" + base + "'")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
def downloadAndExtract(base: URI, tempDir: File): Unit = if(!tempDir.exists) IO.unzipURL(base.toURL, tempDir)
|
|
|
|
|
def temporary(tempDir: File, uri: URI): File = new File(tempDir, hash(uri))
|
|
|
|
|
def hash(uri: URI): String = Hash.toHex(Hash(uri.toASCIIString))
|
|
|
|
|
|
|
|
|
|
import Process._
|
|
|
|
|
def gitClone(base: URI, tempDir: File): Unit =
|
|
|
|
|
if(!tempDir.exists) ("git" :: "clone" :: base.toASCIIString :: tempDir.getAbsolutePath :: Nil) ! ;
|
|
|
|
|
}
|
|
|
|
|
object EvaluateConfigurations
|
|
|
|
|
{
|
2011-01-27 01:49:54 +01:00
|
|
|
def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): Seq[Setting[_]] =
|
|
|
|
|
srcs flatMap { src => evaluateConfiguration(eval, src, imports) }
|
|
|
|
|
def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): Seq[Setting[_]] =
|
|
|
|
|
evaluateConfiguration(eval, src.getPath, IO.readLines(src), imports)
|
|
|
|
|
def evaluateConfiguration(eval: Eval, name: String, lines: Seq[String], imports: Seq[String]): Seq[Setting[_]] =
|
2011-01-19 00:24:11 +01:00
|
|
|
{
|
|
|
|
|
val (importExpressions, settingExpressions) = splitExpressions(name, lines)
|
2011-01-27 01:49:54 +01:00
|
|
|
for((settingExpression,line) <- settingExpressions) yield
|
2011-02-03 01:25:18 +01:00
|
|
|
evaluateSetting(eval, name, (imports.map(s => (s, -1)) ++ importExpressions), settingExpression, line)
|
2011-01-19 00:24:11 +01:00
|
|
|
}
|
|
|
|
|
|
2011-01-27 01:49:54 +01:00
|
|
|
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, line: Int): Setting[_] =
|
2011-01-19 00:24:11 +01:00
|
|
|
{
|
2011-04-04 03:08:06 +02:00
|
|
|
val result = try {
|
|
|
|
|
eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some("sbt.Project.Setting[_]"), line = line)
|
|
|
|
|
} catch {
|
|
|
|
|
case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage)
|
|
|
|
|
}
|
2011-01-27 01:49:54 +01:00
|
|
|
result.value.asInstanceOf[Setting[_]]
|
2011-01-19 00:24:11 +01:00
|
|
|
}
|
2011-01-27 01:49:54 +01:00
|
|
|
private[this] def fstS(f: String => Boolean): ((String,Int)) => Boolean = { case (s,i) => f(s) }
|
|
|
|
|
def splitExpressions(name: String, lines: Seq[String]): (Seq[(String,Int)], Seq[(String,Int)]) =
|
2011-01-19 00:24:11 +01:00
|
|
|
{
|
|
|
|
|
val blank = (_: String).trim.isEmpty
|
2011-01-27 01:49:54 +01:00
|
|
|
val importOrBlank = fstS(t => blank(t) || (t.trim startsWith "import "))
|
2011-01-19 00:24:11 +01:00
|
|
|
|
2011-01-27 01:49:54 +01:00
|
|
|
val (imports, settings) = lines.zipWithIndex span importOrBlank
|
|
|
|
|
(imports filterNot fstS(blank), groupedLines(settings, blank))
|
2011-01-19 00:24:11 +01:00
|
|
|
}
|
2011-01-27 01:49:54 +01:00
|
|
|
def groupedLines(lines: Seq[(String,Int)], delimiter: String => Boolean): Seq[(String,Int)] =
|
2011-01-19 00:24:11 +01:00
|
|
|
{
|
2011-01-27 01:49:54 +01:00
|
|
|
val fdelim = fstS(delimiter)
|
|
|
|
|
@tailrec def group0(lines: Seq[(String,Int)], accum: Seq[(String,Int)]): Seq[(String,Int)] =
|
2011-01-25 00:08:43 +01:00
|
|
|
if(lines.isEmpty) accum.reverse
|
|
|
|
|
else
|
|
|
|
|
{
|
2011-01-27 01:49:54 +01:00
|
|
|
val start = lines dropWhile fstS(delimiter)
|
|
|
|
|
val (next, tail) = start.span { case (s,_) => !delimiter(s) }
|
|
|
|
|
val grouped = if(next.isEmpty) accum else (next.map(_._1).mkString("\n"), next.head._2) +: accum
|
|
|
|
|
group0(tail, grouped)
|
2011-01-25 00:08:43 +01:00
|
|
|
}
|
2011-01-27 01:49:54 +01:00
|
|
|
group0(lines, Nil)
|
2011-01-19 00:24:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
object Index
|
|
|
|
|
{
|
|
|
|
|
def taskToKeyMap(data: Settings[Scope]): Map[Task[_], ScopedKey[Task[_]]] =
|
|
|
|
|
{
|
|
|
|
|
// AttributeEntry + the checked type test 'value: Task[_]' ensures that the cast is correct.
|
2011-03-25 02:25:57 +01:00
|
|
|
// (scalac couldn't determine that 'key' is of type AttributeKey[Task[_]] on its own and a type match still required the cast)
|
2011-01-19 00:24:11 +01:00
|
|
|
val pairs = for( scope <- data.scopes; AttributeEntry(key, value: Task[_]) <- data.data(scope).entries ) yield
|
|
|
|
|
(value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[_]]])) // unclear why this cast is needed even with a type test in the above filter
|
|
|
|
|
pairs.toMap[Task[_], ScopedKey[Task[_]]]
|
|
|
|
|
}
|
|
|
|
|
def stringToKeyMap(settings: Settings[Scope]): Map[String, AttributeKey[_]] =
|
|
|
|
|
{
|
2011-03-01 14:44:41 +01:00
|
|
|
val multiMap = settings.data.values.flatMap(_.keys).toList.distinct.groupBy(_.label)
|
2011-01-27 01:49:54 +01:00
|
|
|
val duplicates = multiMap collect { case (k, x1 :: x2 :: _) => k }
|
2011-01-19 00:24:11 +01:00
|
|
|
if(duplicates.isEmpty)
|
2011-03-25 02:25:57 +01:00
|
|
|
multiMap.collect { case (k, v) if validID(k) => (k, v.head) } toMap;
|
2011-01-19 00:24:11 +01:00
|
|
|
else
|
|
|
|
|
error(duplicates.mkString("AttributeKey ID collisions detected for '", "', '", "'"))
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-01-21 23:22:18 +01:00
|
|
|
object BuildStreams
|
|
|
|
|
{
|
|
|
|
|
import Load.{BuildStructure, LoadedBuildUnit}
|
|
|
|
|
import Project.display
|
|
|
|
|
import std.{TaskExtra,Transform}
|
|
|
|
|
|
|
|
|
|
val GlobalPath = "$global"
|
2011-03-03 12:44:19 +01:00
|
|
|
val BuildUnitPath = "$build"
|
2011-01-21 23:22:18 +01:00
|
|
|
|
|
|
|
|
def mkStreams(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope], logRelativePath: Seq[String] = defaultLogPath): Streams =
|
|
|
|
|
std.Streams( path(units, root, logRelativePath), display, LogManager.construct(data) )
|
|
|
|
|
|
|
|
|
|
def defaultLogPath = "target" :: "streams" :: Nil
|
|
|
|
|
|
|
|
|
|
def path(units: Map[URI, LoadedBuildUnit], root: URI, sep: Seq[String])(scoped: ScopedKey[_]): File =
|
|
|
|
|
{
|
|
|
|
|
val (base, sub) = projectPath(units, root, scoped)
|
|
|
|
|
resolvePath(base, sep ++ sub ++ nonProjectPath(scoped) )
|
|
|
|
|
}
|
|
|
|
|
def resolvePath(base: File, components: Seq[String]): File =
|
|
|
|
|
(base /: components)( (b,p) => new File(b,p) )
|
|
|
|
|
|
|
|
|
|
def pathComponent[T](axis: ScopeAxis[T], scoped: ScopedKey[_], label: String)(show: T => String): String =
|
|
|
|
|
axis match
|
|
|
|
|
{
|
|
|
|
|
case Global => GlobalPath
|
|
|
|
|
case This => error("Unresolved This reference for " + label + " in " + display(scoped))
|
|
|
|
|
case Select(t) => show(t)
|
|
|
|
|
}
|
|
|
|
|
def nonProjectPath[T](scoped: ScopedKey[T]): Seq[String] =
|
|
|
|
|
{
|
|
|
|
|
val scope = scoped.scope
|
|
|
|
|
pathComponent(scope.config, scoped, "config")(_.name) ::
|
|
|
|
|
pathComponent(scope.task, scoped, "task")(_.label) ::
|
|
|
|
|
pathComponent(scope.extra, scoped, "extra")(_ => error("Unimplemented")) ::
|
|
|
|
|
Nil
|
|
|
|
|
}
|
|
|
|
|
def projectPath(units: Map[URI, LoadedBuildUnit], root: URI, scoped: ScopedKey[_]): (File, Seq[String]) =
|
|
|
|
|
scoped.scope.project match
|
|
|
|
|
{
|
2011-01-24 04:34:17 +01:00
|
|
|
case Global => (units(root).localBase, GlobalPath :: Nil)
|
2011-03-03 12:44:19 +01:00
|
|
|
case Select(BuildRef(uri)) => (units(uri).localBase, BuildUnitPath :: Nil)
|
|
|
|
|
case Select(ProjectRef(uri, id)) => (units(uri).defined(id).base, Nil)
|
2011-01-21 23:22:18 +01:00
|
|
|
case Select(pr) => error("Unresolved project reference (" + pr + ") in " + display(scoped))
|
|
|
|
|
case This => error("Unresolved project reference (This) in " + display(scoped))
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-01-19 00:24:11 +01:00
|
|
|
object BuildPaths
|
|
|
|
|
{
|
|
|
|
|
import Path._
|
|
|
|
|
import GlobFilter._
|
|
|
|
|
|
2011-03-12 16:28:53 +01:00
|
|
|
def defaultStaging = Path.userHome / ".sbt" / "staging"
|
2011-03-17 03:22:46 +01:00
|
|
|
def defaultGlobalPlugins = Path.userHome / ".sbt" / "plugins"
|
2011-01-19 00:24:11 +01:00
|
|
|
|
2011-03-17 03:22:46 +01:00
|
|
|
def definitionSources(base: File): Seq[File] = (base * "*.scala").getFiles
|
|
|
|
|
def configurationSources(base: File): Seq[File] = (base * "*.sbt").getFiles
|
2011-01-19 00:24:11 +01:00
|
|
|
def pluginDirectory(definitionBase: Path) = definitionBase / "plugins"
|
|
|
|
|
|
2011-02-03 01:34:52 +01:00
|
|
|
def evalOutputDirectory(base: Path) = outputDirectory(base) / "config-classes"
|
2011-01-19 00:24:11 +01:00
|
|
|
def outputDirectory(base: Path) = base / "target"
|
2011-02-03 01:34:52 +01:00
|
|
|
def buildOutputDirectory(base: Path, compilers: Compilers) = crossPath(outputDirectory(base), compilers.scalac.scalaInstance)
|
|
|
|
|
|
2011-01-19 00:24:11 +01:00
|
|
|
def projectStandard(base: Path) = base / "project"
|
|
|
|
|
def projectHidden(base: Path) = base / ".sbt"
|
|
|
|
|
def selectProjectDir(base: Path) =
|
|
|
|
|
{
|
|
|
|
|
val a = projectHidden(base)
|
|
|
|
|
val b = projectStandard(base)
|
|
|
|
|
if(a.exists) a else b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def crossPath(base: File, instance: ScalaInstance): File = base / ("scala_" + instance.version)
|
|
|
|
|
}
|