TaskGroups, Context in tasks, new tasks

add syncTask task constructor and copy-resources/copy-test-resources instances
add console-quick, test-console, console, test-run
add IntegrationTest trait
make Context available through 'context' task
update 'last' and 'show' to use Context to retrieve task by name
drop SingleProject (superseded by Project)
add TaskGroup to be able to inject groups of named tasks
fix watchPaths missing flat sources
proper logging in a few more places, such as compile
This commit is contained in:
Mark Harrah 2010-11-24 14:18:59 -05:00
parent 16e2b71ccc
commit 6d2bbbe0c1
8 changed files with 337 additions and 119 deletions

View File

@ -22,25 +22,24 @@ object Compile
case _ => Nil
}
final class Inputs(val compilers: Compilers, val config: Options, val incSetup: IncSetup, val log: Logger)
final class Inputs(val compilers: Compilers, val config: Options, val incSetup: IncSetup)
final class Options(val classpath: Seq[File], val sources: Seq[File], val classesDirectory: File, val options: Seq[String], val javacOptions: Seq[String], val maxErrors: Int)
final class IncSetup(val javaSrcBases: Seq[File], val analysisMap: Map[File, Analysis], val cacheDirectory: File)
final class IncSetup(val srcBases: Seq[File], val analysisMap: Map[File, Analysis], val cacheDirectory: File)
final class Compilers(val scalac: AnalyzingCompiler, val javac: JavaCompiler)
def inputs(classpath: Seq[File], sources: Seq[File], outputDirectory: File, options: Seq[String], javacOptions: Seq[String], javaSrcBases: Seq[File], maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
def inputs(classpath: Seq[File], sources: Seq[File], outputDirectory: File, options: Seq[String], javacOptions: Seq[String], srcBases: Seq[File], maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
{
import Path._
val classesDirectory = outputDirectory / "classes"
val cacheDirectory = outputDirectory / "cache"
val augClasspath = classesDirectory.asFile +: classpath
inputs(augClasspath, sources, classesDirectory, options, javacOptions, javaSrcBases, Map.empty, cacheDirectory, maxErrors)
inputs(augClasspath, sources, classesDirectory, options, javacOptions, srcBases, Map.empty, cacheDirectory, maxErrors)
}
def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], javaSrcBases: Seq[File], analysisMap: Map[File, Analysis], cacheDirectory: File, maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], srcBases: Seq[File], analysisMap: Map[File, Analysis], cacheDirectory: File, maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
new Inputs(
compilers,
new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors),
new IncSetup(javaSrcBases, analysisMap, cacheDirectory),
log
new IncSetup(srcBases, analysisMap, cacheDirectory)
)
def compilers(implicit app: AppConfiguration, log: Logger): Compilers =
@ -77,13 +76,13 @@ object Compile
new AnalyzingCompiler(instance, componentManager, cpOptions, log)
}
def apply(in: Inputs): Analysis =
def apply(in: Inputs, log: Logger): Analysis =
{
import in.compilers._
import in.config._
import in.incSetup._
val agg = new build.AggressiveCompile(cacheDirectory)
agg(scalac, javac, sources, classpath, classesDirectory, javaSrcBases, options, javacOptions, analysisMap, maxErrors)(in.log)
agg(scalac, javac, sources, classpath, classesDirectory, srcBases, options, javacOptions, analysisMap, maxErrors)(log)
}
}

View File

@ -7,7 +7,8 @@ package sbt
import compile.{Discovered,Discovery}
import inc.Analysis
import TaskExtra._
import Configurations.{Compile => CompileConfig, Test => TestConfig, Runtime => RunConfig, Default => DefaultConfig}
import Path.fileToPath
import Configurations.{Compile => CompileConfig, Test => TestConfig, Runtime => RunConfig, Default => DefaultConfig, IntegrationTest => ITestConfig}
import ClasspathProject._
import Types._
import xsbti.api.Definition
@ -19,101 +20,93 @@ class DefaultProject(val info: ProjectInfo) extends BasicProject
{
override def name = "test"
}
trait IntegrationTest extends BasicProject
{
override def productsTask(conf: Configuration): Task[Seq[Attributed[File]]] =
conf match {
case ITestConfig => makeProducts(integrationTestCompile.compile, integrationTestCompile.compileInputs, name, "it-")
case _ => super.productsTask(conf)
}
override def configurations: Seq[Configuration] = super.configurations :+ Configurations.IntegrationTest
lazy val integrationTestOptions: Task[Seq[TestOption]] = testOptions
lazy val integrationTest = testTasks(Some("it"), Configurations.IntegrationTest, integrationTestOptions, integrationTestCompile.compile, buildScalaInstance)
lazy val integrationTestCompile = compileTasks(Some("it"), Configurations.IntegrationTest, "src" / "it", Path.emptyPathFinder, buildScalaInstance)
}
abstract class BasicProject extends TestProject with MultiClasspathProject with ReflectiveClasspathProject
{
// easier to demo for now
override def organization = "org.example"
override def version = "1.0"
override def watchPaths: PathFinder = descendents("src","*")
override def watchPaths: PathFinder = (info.projectDirectory: Path) * sourceFilter +++ descendents("src","*")
def javacOptions: Seq[String] = Nil
def scalacOptions: Seq[String] = Nil
def consoleOptions: Seq[String] = scalacOptions
def initialCommands: String = ""
def outputDirectory = "target": Path
def cacheDirectory = outputDirectory / "cache"
def mainResources = descendents("src" / "main" / "resources" ###, "*")
def testResources = descendents("src" / "test" / "resources" ###, "*")
def classesDirectory(configuration: Configuration): File =
configuration match {
case CompileConfig => outputDirectory / "classes"
case x => outputDirectory / (x.toString + "-classes")
case c => outputDirectory / (c.name + "-classes")
}
lazy val products: Classpath = TaskMap(productsTask)
// TODO: it config, include resources, perhaps handle jars v. directories
def productsTask(conf: Configuration) =
// TODO: include resources, perhaps handle jars v. directories
def productsTask(conf: Configuration): Task[Seq[Attributed[File]]] =
conf match {
case CompileConfig | DefaultConfig => makeProducts(compile, compileInputs, name, "")
case TestConfig => makeProducts(testCompile, testCompileInputs, name, "test-")
case CompileConfig | DefaultConfig => makeProducts(compile.compile, compile.compileInputs, name, "")
case TestConfig => makeProducts(testCompile.compile, testCompile.compileInputs, name, "test-")
case x => task { Nil }
}
lazy val buildScalaInstance: Task[ScalaInstance] = task {
val provider = info.app.scalaProvider
ScalaInstance(provider.version, provider)
}
lazy val buildScalaVersions: Task[String] = task { info.app.scalaProvider.version }//cross(MultiProject.ScalaVersion)(info.app.scalaProvider.version)
lazy val buildScalaInstance: Task[ScalaInstance] =
buildScalaVersions map { version => ScalaInstance(version, info.app.scalaProvider) }
lazy val discoverMain: Task[Seq[(Definition,Discovered)]] =
compile map { analysis => Discovery.applications(Test.allDefs(analysis)) }
compile.compile map { analysis => Discovery.applications(Test.allDefs(analysis)) }
lazy val discoveredMainClasses: Task[Seq[String]] =
discoverMain map { _ collect { case (definition, discovered) if(discovered.hasMain) => definition.name } }
lazy val runMainClass: Task[Option[String]] =
discoveredMainClasses map { classes => SelectMainClass(Some(SimpleReader readLine _), classes) }
lazy val pkgMainClass: Task[Option[String]] =
discoveredMainClasses map { classes => SelectMainClass(None, classes) }
lazy val runner: Task[ScalaRun] =
buildScalaInstance map { si => new Run(si) }
lazy val run = (input :^: fullClasspath(RunConfig) :^: runMainClass :^: streams :^: runner :^: KNil) map { case in :+: cp :+: main :+: s :+: r :+: HNil =>
val mainClass = main getOrElse error("No main class detected.")
val classpath = cp.map(x => Path.fromFile(x.data))
r.run(mainClass, classpath, in.splitArgs, s.log) foreach error
}
lazy val run = runTasks(None, RunConfig, discoveredMainClasses)
lazy val testRun = runTasks(Some("test"), TestConfig, test.testDiscoveredMainClasses)
lazy val testFrameworks: Task[Seq[TestFramework]] = task {
import TestFrameworks._
Seq(ScalaCheck, Specs, ScalaTest, ScalaCheckCompat, ScalaTestCompat, SpecsCompat, JUnit)
}
lazy val testLoader: Task[ClassLoader] =
fullClasspath(TestConfig) :^: buildScalaInstance :^: KNil map { case classpath :+: instance :+: HNil =>
TestFramework.createTestLoader(data(classpath), instance)
}
lazy val loadedTestFrameworks: Task[Map[TestFramework,Framework]] =
testFrameworks :^: streams :^: testLoader :^: KNil map { case frameworks :+: s :+: loader :+: HNil =>
frameworks.flatMap( f => f.create(loader, s.log).map( x => (f, x)).toIterable ).toMap
}
lazy val discoverTest: Task[(Seq[TestDefinition], Set[String])] =
loadedTestFrameworks :^: testCompile :^: KNil map { case frameworkMap :+: analysis :+: HNil =>
Test.discover(frameworkMap.values.toSeq, analysis)
}
lazy val definedTests: Task[Seq[TestDefinition]] = discoverTest.map(_._1)
lazy val testOptions: Task[Seq[TestOption]] = task { Nil }
lazy val test = (loadedTestFrameworks :^: testOptions :^: testLoader :^: definedTests :^: streams :^: KNil) flatMap {
case frameworkMap :+: options :+: loader :+: discovered :+: s :+: HNil =>
lazy val test = testTasks(None, TestConfig, testOptions, testCompile.compile, buildScalaInstance)
val toTask = (testTask: NamedTestTask) => task { testTask.run() } named(testTask.name)
def dependsOn(on: Iterable[Task[_]]): Task[Unit] = task { () } dependsOn(on.toSeq : _*)
lazy val compile = compileTasks(None, CompileConfig, "src" / "main", info.projectDirectory : Path, buildScalaInstance)
lazy val testCompile = compileTasks(Some("test"), TestConfig, "src" / "test", Path.emptyPathFinder, buildScalaInstance)
val (begin, work, end) = Test(frameworkMap, loader, discovered, options, s.log)
val beginTasks = dependsOn( begin.map(toTask) ) // test setup tasks
val workTasks = work.map(w => toTask(w) dependsOn(beginTasks) ) // the actual tests
val endTasks = dependsOn( end.map(toTask) ) // tasks that perform test cleanup and are run regardless of success of tests
dependsOn( workTasks ) doFinally { endTasks }
}
lazy val consoleQuick = consoleTask(dependencyClasspath(CompileConfig), consoleOptions, initialCommands, compile.compileInputs)
lazy val console = consoleTask(CompileConfig, consoleOptions, initialCommands, compile.compileInputs)
lazy val testConsole = consoleTask(TestConfig, consoleOptions, initialCommands, testCompile.compileInputs)
lazy val clean = task { IO.delete(outputDirectory) }
// lazy val doc, test-only, test-quick, test-failed, publish(-local), deliver(-local), make-pom, package-*, javap, copy-resources
lazy val clean = task {
IO.delete(outputDirectory)
}
lazy val set = input map { in =>
val Seq(name, value) = in.splitArgs.take(2)
println(name + "=" + value)
@ -122,9 +115,10 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with
def sourceFilter: FileFilter = "*.java" | "*.scala"
def compileTask(inputs: Task[Compile.Inputs]): Task[Analysis] = inputs map Compile.apply
def compileTask(inputs: Task[Compile.Inputs]): Task[Analysis] =
inputs :^: streams :^: KNil map { case i :+: s :+: HNil => Compile(i, s.log) }
def compileInputsTask(configuration: Configuration, base: PathFinder, scalaInstance: Task[ScalaInstance]): Task[Compile.Inputs] =
def compileInputsTask(configuration: Configuration, bases: PathFinder, shallow: PathFinder, scalaInstance: Task[ScalaInstance]): Task[Compile.Inputs] =
{
val dep = dependencyClasspath(configuration)
(dep, scalaInstance) map { case (cp :+: si :+: HNil) =>
@ -134,18 +128,105 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with
val scalaSrc = base / "scala"
val out = "target" / si.actualVersion
import Path._
val sources = descendents((javaSrc +++ scalaSrc), sourceFilter) +++ (if(configuration == CompileConfig) info.projectDirectory * (sourceFilter -- defaultExcludes) else Path.emptyPathFinder)
val deepBases = (bases / "java") +++ (bases / "scala")
val allBases = deepBases +++ shallow
val sources = descendents(deepBases, sourceFilter) +++ shallow * (sourceFilter -- defaultExcludes)
val classes = classesDirectory(configuration)
val classpath = classes +: data(cp)
val analysis = analysisMap(cp)
val cache = cacheDirectory / "compile" / configuration.toString
Compile.inputs(classpath, sources.getFiles.toSeq, classes, scalacOptions, javacOptions, javaSrc.getFiles.toSeq, analysis, cache, 100)(compilers, log)
Compile.inputs(classpath, sources.getFiles.toSeq, classes, scalacOptions, javacOptions, allBases.getFiles.toSeq, analysis, cache, 100)(compilers, log)
}
}
lazy val compileInputs: Task[Compile.Inputs] = compileInputsTask(Configurations.Compile, "src" / "main", buildScalaInstance) named(name + "/compile-inputs")
lazy val compile: Task[Analysis] = compileTask(compileInputs) named(name + "/compile")
def copyResourcesTask(resources: PathFinder, config: Configuration): Task[Relation[File,File]] =
streams map { s =>
val target = classesDirectory(config)
val cacheFile = cacheDirectory / config.name / "copy-resources"
val mappings = resources.get.map(path => (path.asFile, new File(target, path.relativePath)))
s.log.debug("Copy resource (" + config.name + ") mappings: " + mappings.mkString("\n\t"))
Sync(cacheFile)( mappings )
}
lazy val testCompileInputs: Task[Compile.Inputs] = compileInputsTask(Configurations.Test, "src" / "test", buildScalaInstance) named(name + "/test-inputs")
lazy val testCompile: Task[Analysis] = compileTask(testCompileInputs) named(name + "/test")
lazy val copyResources = copyResourcesTask(mainResources, CompileConfig)
lazy val copyTestResources = copyResourcesTask(testResources, TestConfig)
def syncTask(cacheFile: File, mappings: Iterable[(File, File)]): Task[Relation[File,File]] =
task { Sync(cacheFile)(mappings) }
def consoleTask(config: Configuration, options: Seq[String], initialCommands: String, compilers: Task[Compile.Inputs]): Task[Unit] =
consoleTask(fullClasspath(config), options, initialCommands, compilers)
def consoleTask(classpath: Task[Seq[Attributed[File]]], options: Seq[String], initialCommands: String, compilers: Task[Compile.Inputs]): Task[Unit] =
consoleTask(compilers.map(_.compilers), classpath, options, initialCommands)
def consoleTask(compilers: Task[Compile.Compilers], classpath: Task[Seq[Attributed[File]]], options: Seq[String], initialCommands: String): Task[Unit] =
compilers :^: classpath :^: streams :^: KNil map { case cs :+: cp :+: s :+: HNil =>
(new Console(cs.scalac))(data(cp), options, initialCommands, s.log)
}
def compileTasks(prefix: Option[String], config: Configuration, base: PathFinder, shallow: PathFinder, si: Task[ScalaInstance]): CompileTasks =
{
val confStr = if(config == Configurations.Compile) "" else config.name + "-"
val compileInputs: Task[Compile.Inputs] = compileInputsTask(config, base, shallow, buildScalaInstance) named(confStr + "compile-inputs")
val compile: Task[Analysis] = compileTask(compileInputs) named(confStr + "compile")
new CompileTasks(prefix, compile, compileInputs)
}
def testTasks(prefix: Option[String], config: Configuration, options: Task[Seq[TestOption]], compile: Task[Analysis], instance: Task[ScalaInstance]): TestTasks =
new TestTasks(prefix, this, testFrameworks, config, options, compile, instance)
def runTasks(prefix: Option[String], config: Configuration, discoveredMainClasses: Task[Seq[String]]): RunTasks =
new RunTasks(prefix, this, config, discoveredMainClasses, runner)
}
// TODO: move these to separate file. The main problem with this approach is modifying dependencies and otherwise overriding a task.
final class CompileTasks(val prefix: Option[String], val compile: Task[Analysis], val compileInputs: Task[Compile.Inputs]) extends TaskGroup
class RunTasks(val prefix: Option[String], val project: ClasspathProject with Project, val config: Configuration, discoveredMainClasses: Task[Seq[String]], runner: Task[ScalaRun]) extends TaskGroup
{
def selectRunMain(allMainClasses: Task[Seq[String]]): Task[Option[String]] =
allMainClasses map { classes => SelectMainClass(Some(SimpleReader readLine _), classes) }
def runTask(fullcp: Task[Seq[Attributed[File]]], mainClass: Task[Option[String]]): Task[Unit] =
runTask(project.input, fullcp, mainClass, project.streams, runner)
def runTask(input: Task[Input], fullcp: Task[Seq[Attributed[File]]], mainClass: Task[Option[String]], streams: Task[TaskStreams], runner: Task[ScalaRun]): Task[Unit] =
(input :^: fullcp :^: mainClass :^: streams :^: runner :^: KNil) map { case in :+: cp :+: main :+: s :+: r :+: HNil => run0(in.splitArgs, cp, main, s.log, r) }
def run0(args: Seq[String], cp: Seq[Attributed[File]], main: Option[String], log: Logger, r: ScalaRun)
{
val mainClass = main getOrElse error("No main class detected.")
val classpath = cp.map(x => Path.fromFile(x.data))
r.run(mainClass, classpath, args, log) foreach error
}
lazy val runMainClass: Task[Option[String]] = selectRunMain(discoveredMainClasses)
lazy val run = runTask(project.fullClasspath(config), runMainClass)
}
class TestTasks(val prefix: Option[String], val project: ClasspathProject with Project, testFrameworks: Task[Seq[TestFramework]], val config: Configuration, options: Task[Seq[TestOption]], compile: Task[Analysis], scalaInstance: Task[ScalaInstance]) extends TaskGroup
{
lazy val testLoader: Task[ClassLoader] =
project.fullClasspath(config) :^: scalaInstance :^: KNil map { case classpath :+: instance :+: HNil =>
TestFramework.createTestLoader(data(classpath), instance)
}
lazy val loadedTestFrameworks: Task[Map[TestFramework,Framework]] =
testFrameworks :^: project.streams :^: testLoader :^: KNil map { case frameworks :+: s :+: loader :+: HNil =>
frameworks.flatMap( f => f.create(loader, s.log).map( x => (f, x)).toIterable ).toMap
}
lazy val discoverTest: Task[(Seq[TestDefinition], Set[String])] =
loadedTestFrameworks :^: compile :^: KNil map { case frameworkMap :+: analysis :+: HNil =>
Test.discover(frameworkMap.values.toSeq, analysis)
}
lazy val definedTests: Task[Seq[TestDefinition]] = discoverTest.map(_._1)
lazy val testDiscoveredMainClasses: Task[Seq[String]] = discoverTest.map(_._2.toSeq)
lazy val executeTests = (loadedTestFrameworks :^: options :^: testLoader :^: definedTests :^: project.streams :^: KNil) flatMap {
case frameworkMap :+: options :+: loader :+: discovered :+: s :+: HNil =>
Test(frameworkMap, loader, discovered, options, s.log)
}
lazy val test = (project.streams, executeTests) map { case s :+: results :+: HNil => Test.showResults(s.log, results) }
}

View File

@ -19,6 +19,7 @@ import annotation.tailrec
object MultiProject
{
val InitialProject = AttributeKey[Project]("initial-project")
val ScalaVersion = AttributeKey[String]("scala-version")
val defaultExcludes: FileFilter = (".*" - ".") || HiddenFileFilter
def descendents(base: PathFinder, select: FileFilter) = base.descendentsExcept(select, defaultExcludes)
@ -56,7 +57,7 @@ object MultiProject
val target = crossPath(buildDir / "target", compilers.scalac.scalaInstance)
val inputs = Compile.inputs(classpath, sources.getFiles.toSeq, target, Nil, Nil, javaSrcBase :: Nil, Compile.DefaultMaxErrors)(compilers, log)
val analysis = Compile(inputs)
val analysis = Compile(inputs, log)
val info = ProjectInfo(None, base, projectDir, Nil, None)(configuration, analysis, inputs, load(configuration, log, externals), externals)
val discovered = Build.discover(analysis, Some(false), Auto.Subclass, projectClassName)
@ -106,11 +107,11 @@ object MultiProject
def makeContext(root: Project) =
{
val contexts = topologicalSort(root) map { p => (p, ReflectiveContext(p, p.name)) }
val contexts = topologicalSort(root) map { p => (p, ReflectiveContext(p, p.name, root)) }
val externals = root.info.externals
def subs(f: Project => Seq[ProjectDependency]): Project => Seq[Project] = p =>
f(p) map( _.project match { case Left(path) => externals(path); case Right(proj) => proj } )
MultiContext(contexts)(subs(_.aggregate), subs(_.dependencies) )
MultiContext(contexts, root)(subs(_.aggregate), subs(_.dependencies) )
}
def lefts[A,B](e: Seq[Either[A,B]]):Seq[A] = e collect { case Left(l) => l }
@ -132,8 +133,9 @@ object MultiContext
for( (a,b) <- in) map(a) = b
map
}
def fun[A,B](map: collection.Map[A,B]): A => Option[B] = map get _
def apply[Owner <: AnyRef](contexts: Iterable[(Owner, Context[Owner])])(agg: Owner => Iterable[Owner], deps: Owner => Iterable[Owner]): Context[Owner] = new Context[Owner]
def apply[Owner <: AnyRef](contexts: Iterable[(Owner, Context[Owner])], root: Owner)(agg: Owner => Iterable[Owner], deps: Owner => Iterable[Owner]): Context[Owner] = new Context[Owner]
{
val ownerReverse = (for( (owner, context) <- contexts; name <- context.ownerName(owner).toList) yield (name, owner) ).toMap
val ownerMap = identityMap[Task[_],Owner]( for((owner, context) <- contexts; task <- context.allTasks(owner) ) yield (task, owner) )
@ -142,6 +144,7 @@ object MultiContext
val aggMap = subMap(agg)
val depMap = subMap(deps)
def rootOwner: Owner = root
def allTasks(owner: Owner): Iterable[Task[_]] = context(owner).allTasks(owner)
def ownerForName(name: String): Option[Owner] = ownerReverse get name
val staticName: Task[_] => Option[String] = t => owner(t) flatMap { o => context(o).staticName(t) }
@ -175,6 +178,7 @@ trait Project extends Tasked with HistoryEnabled with Member[Project] with Named
implicit def streams = Dummy.Streams
def input = Dummy.In
def state = Dummy.State
def context = Dummy.Context
def aggregate: Seq[ProjectDependency.Execution] = info.dependencies collect { case ex: ProjectDependency.Execution => ex }
def dependencies: Seq[ProjectDependency.Classpath] = info.dependencies collect { case cp: ProjectDependency.Classpath => cp }
@ -184,10 +188,11 @@ trait Project extends Tasked with HistoryEnabled with Member[Project] with Named
{
import Dummy._
val context = MultiProject.makeContext(this)
val dummies = new Transform.Dummies(In, State, Streams)
val dummies = new Transform.Dummies(In, State, Streams, Context)
def name(t: Task[_]): String = context.staticName(t.original) getOrElse std.Streams.name(t)
val mklog = LogManager.construct(context, settings)
val actualStreams = std.Streams(t => context.owner(t.original).get.streamBase / name(t), mklog )
def getOwner(t: Task[_]) = context.owner(t.original).getOrElse(error("No owner for " + name(t.original) + "\n\t" + t.original))
val actualStreams = std.Streams(t => getOwner(t).streamBase / name(t), mklog )
val injected = new Transform.Injected( input, state, actualStreams )
context.static(this, transformName(input.name)) map { t => (t.merge.map(_ => state), Transform(dummies, injected, context) ) }
}

View File

@ -6,14 +6,17 @@ package sbt
import std._
import Path._
import TaskExtra._
import Types._
import OutputUtil._
trait PrintTask
{
def input: Task[Input]
lazy val show = input flatMap { in =>
val m = ReflectUtilities.allVals[Task[_]](this)
def context: Task[Transform.Context[Project]]
lazy val show = (input, context) flatMap { case in :+: ctx :+: HNil =>
val taskStrings = in.splitArgs map { name =>
m(name).merge.map {
val selected = taskForName(ctx, name)
selected.merge.map {
case Seq() => "No result for " + name
case Seq( (conf, v) ) => name + ": " + v.toString
case confs => confs map { case (conf, v) => conf + ": " + v } mkString(name + ":\n\t", "\n\t", "\n")
@ -27,20 +30,26 @@ trait LastOutput
{
def input: Task[Input]
def streams: Task[TaskStreams]
lazy val last = (streams, input) flatMap { (s: TaskStreams, i: Input) =>
val tasks = ReflectUtilities.allVals[Task[_]](this)
(s.readText( tasks(i.arguments) , update = false ) map { reader =>
def context: Task[Transform.Context[Project]]
lazy val last = (streams, input, context) flatMap { case s :+: i :+: ctx :+: HNil =>
val task = taskForName(ctx, i.arguments)
(s.readText( task, update = false ) map { reader =>
val out = s.text()
def readL()
{
val line = reader.readLine()
if(line ne null) {
readL()
out.println(line)
println(line)
readL()
}
}
readL()
}).merge
}
}
object OutputUtil
{
def taskForName(ctx: Transform.Context[Project], name: String): Task[_] =
ctx.static(ctx.rootOwner, MultiProject.transformName(name)).getOrElse(error("No task '" + name + "'"))
}

View File

@ -10,50 +10,68 @@ package sbt
import java.io.File
trait SingleProject extends Tasked with PrintTask with TaskExtra with Types
{
def base = new File(".")
def streamBase = base / "streams"
implicit def streams = Dummy.Streams
def input = Dummy.In
def state = Dummy.State
type Task[T] = sbt.Task[T]
def act(input: Input, state: State): Option[(Task[State], Execute.NodeView[Task])] =
{
import Dummy._
val context = ReflectiveContext(this, "project")
val dummies = new Transform.Dummies(In, State, Streams)
def name(t: Task[_]): String = context.staticName(t) getOrElse std.Streams.name(t)
val injected = new Transform.Injected( input, state, std.Streams(t => streamBase / name(t), (t, writer) => ConsoleLogger() ) )
context.static(this, input.name) map { t => (t.merge.map(_ => state), Transform(dummies, injected, context) ) }
}
def help: Seq[Help] = Nil
}
object Dummy
{
val InName = "command-line-input"
val StateName = "command-state"
val StreamsName = "task-streams"
val ContextName = "task-context"
def dummy[T](name: String): Task[T] = task( error("Dummy task '" + name + "' did not get converted to a full task.") ) named name
val In = dummy[Input](InName)
val State = dummy[State](StateName)
val Streams = dummy[TaskStreams](StreamsName)
val Context = dummy[Transform.Context[Project]](ContextName)
}
/** A group of tasks, often related in some way.
* This trait should be used like:
* def testTasks(prefix: Option[String]) = new TestTasks(prefix)
* final class TestTasks(val prefix: Option[String]) extends TaskGroup {
* lazy val test = ...
* lazy val discover = ...
* }
*
* This allows a method to return multiple tasks that can still be referenced uniformly and directly:
* val test = testTasks(None)
* val it = testTasks(Some("integration"))
*
* > test
* ... run test.test ...
* > integration-test
* ... run it.test ...
*/
trait TaskGroup
{
def prefix: Option[String]
def tasks: immutable.SortedMap[String, Task[_]] = ReflectiveContext.deepTasks(this)
}
object ReflectiveContext
{
import Transform.Context
def apply[Owner <: AnyRef : Manifest](context: Owner, name: String): Context[Owner] = new Context[Owner]
def deepTasks(context: AnyRef): immutable.SortedMap[String, Task[_]] =
{
private[sbt] lazy val tasks: immutable.SortedMap[String, Task[_]] = ReflectUtilities.allVals[Task[_]](context)
val direct = ReflectUtilities.allVals[Task[_]](context)
val groups = ReflectUtilities.allVals[TaskGroup](context)
val nested = groups.flatMap { case (name, group) => prefixAll(group.prefix, group.tasks) }
nested ++ direct // this order necessary so that direct shadows nested
}
def prefixAll(pre: Option[String], tasks: immutable.SortedMap[String, Task[_]]): immutable.SortedMap[String, Task[_]] =
pre match
{
case Some(p) if !p.isEmpty => prefix(p, tasks)
case _ => tasks
}
def prefix(p: String, tasks: Iterable[(String, Task[_])]): immutable.SortedMap[String, Task[_]] =
immutable.TreeMap[String, Task[_]]() ++ (tasks map { case (name, task) => (p + name.capitalize, task) })
import Transform.Context
def apply[Owner <: AnyRef : Manifest](context: Owner, name: String, root: Owner): Context[Owner] = new Context[Owner]
{
private[sbt] lazy val tasks: immutable.SortedMap[String, Task[_]] = deepTasks(context)
private[sbt] lazy val reverseName: collection.Map[Task[_], String] = reverseMap(tasks)
private[sbt] lazy val sub: Map[String, Owner] = ReflectUtilities.allVals[Owner](context)
private[sbt] lazy val reverseSub: collection.Map[Owner, String] = reverseMap(sub)
def rootOwner = root
val staticName: Task[_] => Option[String] = reverseName.get _
val ownerName = (o: Owner) => if(o eq context) Some(name) else None
val owner = (t: Task[_]) => if(reverseName contains t) Some(context) else None

92
main/Sync.scala Normal file
View File

@ -0,0 +1,92 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import java.io.File
/**
Maintains a set of mappings so that they are uptodate.
Specifically, 'apply' applies the mappings by creating target directories and copying source files to their destination.
For each mapping no longer present, the old target is removed.
Caution: Existing files are overwritten.
Caution: The removal of old targets assumes that nothing else has written to or modified those files.
It tries not to obliterate large amounts of data by only removing previously tracked files and empty directories.
That is, it won't remove a directory with unknown (untracked) files in it.
Warning: It is therefore inappropriate to use this with anything other than an automatically managed destination or a dedicated target directory.
Warning: Specifically, don't mix this with a directory containing manually created files, like sources.
It is safe to use for its intended purpose: copying resources to a class output directory.
*/
object Sync
{
def apply(cacheFile: File, inStyle: FileInfo.Style = FileInfo.lastModified, outStyle: FileInfo.Style = FileInfo.exists): Iterable[(File,File)] => Relation[File,File] =
mappings =>
{
val relation = Relation.empty ++ mappings
noDuplicateTargets(relation)
val currentInfo = relation._1s.map(s => (s, inStyle(s)) ).toMap
val (previousRelation, previousInfo) = readInfo(cacheFile)(inStyle.format)
val removeTargets = previousRelation._2s -- relation._2s
def outofdate(source: File, target: File): Boolean =
!previousRelation.contains(source, target) ||
(previousInfo get source) != (currentInfo get source) ||
!target.exists ||
target.isDirectory != source.isDirectory
val updates = relation filter outofdate
val (cleanDirs, cleanFiles) = (updates._2s ++ removeTargets).partition(_.isDirectory)
IO.delete(cleanFiles)
IO.deleteIfEmpty(cleanDirs)
updates.all.foreach((copy _).tupled)
writeInfo(cacheFile, relation, currentInfo)(inStyle.format)
relation
}
def copy(source: File, target: File): Unit =
if(source.isFile)
IO.copyFile(source, target, true)
else if(!target.exists) // we don't want to update the last modified time of an existing directory
{
IO.createDirectory(target)
IO.copyLastModified(source, target)
}
def noDuplicateTargets(relation: Relation[File, File])
{
val dups = relation.reverseMap.collect {
case (target, srcs) if srcs.size >= 2 =>
"\n\t" + target + "\nfrom\n\t" + srcs.mkString("\n\t")
}
if(!dups.isEmpty)
error("Duplicate mappings:" + dups.mkString)
}
import java.io.{File, IOException}
import sbinary._
import Operations.{read, write}
import DefaultProtocol.{FileFormat => _, _}
import JavaIO._
import inc.AnalysisFormats._
def writeInfo[F <: FileInfo](file: File, relation: Relation[File, File], info: Map[File, F])(implicit infoFormat: Format[F]): Unit =
IO.gzipFileOut(file) { out =>
write(out, (relation, info) )
}
type RelationInfo[F] = (Relation[File,File], Map[File, F])
def readInfo[F <: FileInfo](file: File)(implicit infoFormat: Format[F]): RelationInfo[F] =
try { readUncaught(file)(infoFormat) }
catch { case e: IOException => (Relation.empty, Map.empty) }
def readUncaught[F <: FileInfo](file: File)(implicit infoFormat: Format[F]): RelationInfo[F] =
IO.gzipFileIn(file) { in =>
read[RelationInfo[F]](in)
}
}

View File

@ -15,13 +15,17 @@ object System
implicit def to_~>| [K[_], V[_]](map: RMap[K,V]) : K ~>| V = new (K ~>| V) { def apply[T](k: K[T]): Option[V[T]] = map.get(k) }
def dummyMap[Input, State](dummyIn: Task[Input], dummyState: Task[State])(in: Input, state: State): Task ~>| Task =
def dummyMap[Input, State, Owner](dummyIn: Task[Input], dummyState: Task[State], dummyCtx: Task[Transform.Context[Owner]])(
in: Input, state: State, context: Transform.Context[Owner]): Task ~>| Task =
{
// helps ensure that the same Task[Nothing] can't be passed for dummyIn and dummyState
assert(dummyIn ne dummyState, "Dummy tasks for Input and State must be distinct.")
assert((dummyIn ne dummyState)
&& (dummyState ne dummyCtx), "Dummy tasks for Input, State, and Context must be distinct.")
val pmap = new DelegatingPMap[Task, Task](new collection.mutable.ListMap)
pmap(dummyIn) = fromDummyStrict(dummyIn, in)
pmap(dummyState) = fromDummyStrict(dummyState, state)
pmap(dummyCtx) = fromDummyStrict(dummyCtx, context)
pmap
}
@ -103,10 +107,11 @@ object System
}
object Transform
{
final class Dummies[Input, State](val dummyIn: Task[Input], val dummyState: Task[State], val dummyStreams: Task[TaskStreams])
final class Dummies[Input, State, Owner](val dummyIn: Task[Input], val dummyState: Task[State], val dummyStreams: Task[TaskStreams], val dummyContext: Task[Context[Owner]])
final class Injected[Input, State](val in: Input, val state: State, val streams: Streams)
trait Context[Owner]
{
def rootOwner: Owner
def staticName: Task[_] => Option[String]
def owner: Task[_] => Option[Owner]
def ownerName: Owner => Option[String]
@ -127,14 +132,14 @@ object Transform
}
}
def apply[Input, State, Owner](dummies: Dummies[Input, State], injected: Injected[Input, State], context: Context[Owner]) =
def apply[Input, State, Owner](dummies: Dummies[Input, State, Owner], injected: Injected[Input, State], context: Context[Owner]) =
{
import dummies._
import injected._
import context._
import System._
import Convert._
val inputs = dummyMap(dummyIn, dummyState)(in, state)
val inputs = dummyMap(dummyIn, dummyState, dummyContext)(in, state, context)
Convert.taskToNode setOriginal(streamed(streams, dummyStreams)) implied(owner, aggregate, static) setOriginal(name(staticName)) getOrId(inputs)
}
}

View File

@ -29,7 +29,7 @@ object Relation
private[sbt] def combine[X,Y](a: M[X,Y], b: M[X,Y]): M[X,Y] =
(a /: b) { (map, mapping) => add(map, mapping._1, mapping._2) }
private[sbt] def add[X,Y](map: M[X,Y], from: X, to: Iterable[Y]): M[X,Y] =
private[sbt] def add[X,Y](map: M[X,Y], from: X, to: Traversable[Y]): M[X,Y] =
map.updated(from, get(map, from) ++ to)
private[sbt] def get[X,Y](map: M[X,Y], t: X): Set[Y] = map.getOrElse(t, Set.empty[Y])
@ -49,15 +49,15 @@ trait Relation[A,B]
/** Includes the relation (a, b). */
def +(a: A, b: B): Relation[A,B]
/** Includes the relations (a, b) for all b in bs. */
def +(a: A, bs: Iterable[B]): Relation[A,B]
def +(a: A, bs: Traversable[B]): Relation[A,B]
/** Returns the union of the relation r with this relation. */
def ++(r: Relation[A,B]): Relation[A,B]
/** Includes the given relations. */
def ++(rs: Iterable[(A,B)]): Relation[A,B]
def ++(rs: Traversable[(A,B)]): Relation[A,B]
/** Removes all relations (_1, _2) for all _1 in _1s. */
def --(_1s: Iterable[A]): Relation[A,B]
def --(_1s: Traversable[A]): Relation[A,B]
/** Removes all `pairs` from this relation. */
def --(pairs: Traversable[(A,B)]): Relation[A,B]
def --(pairs: TraversableOnce[(A,B)]): Relation[A,B]
/** Removes all pairs (_1, _2) from this relation. */
def -(_1: A): Relation[A,B]
/** Removes `pair` from this relation. */
@ -69,6 +69,11 @@ trait Relation[A,B]
/** Returns the number of pairs in this relation */
def size: Int
/** Returns true iff (a,b) is in this relation*/
def contains(a: A, b: B): Boolean
/** Returns a relation with only pairs (a,b) for which f(a,b) is true.*/
def filter(f: (A,B) => Boolean): Relation[A,B]
/** Returns all pairs in this relation.*/
def all: Traversable[(A,B)]
@ -92,14 +97,14 @@ private final class MRelation[A,B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) ext
def +(pair: (A,B)) = this + (pair._1, Set(pair._2))
def +(from: A, to: B) = this + (from, to :: Nil)
def +(from: A, to: Iterable[B]) =
def +(from: A, to: Traversable[B]) =
new MRelation( add(fwd, from, to), (rev /: to) { (map, t) => add(map, t, from :: Nil) })
def ++(rs: Iterable[(A,B)]) = ((this: Relation[A,B]) /: rs) { _ + _ }
def ++(rs: Traversable[(A,B)]) = ((this: Relation[A,B]) /: rs) { _ + _ }
def ++(other: Relation[A,B]) = new MRelation[A,B]( combine(fwd, other.forwardMap), combine(rev, other.reverseMap) )
def --(ts: Iterable[A]): Relation[A,B] = ((this: Relation[A,B]) /: ts) { _ - _ }
def --(pairs: Traversable[(A,B)]): Relation[A,B] = ((this: Relation[A,B]) /: pairs) { _ - _ }
def --(ts: Traversable[A]): Relation[A,B] = ((this: Relation[A,B]) /: ts) { _ - _ }
def --(pairs: TraversableOnce[(A,B)]): Relation[A,B] = ((this: Relation[A,B]) /: pairs) { _ - _ }
def -(pair: (A,B)): Relation[A,B] =
new MRelation( remove(fwd, pair._1, pair._2), remove(rev, pair._2, pair._1) )
def -(t: A): Relation[A,B] =
@ -110,5 +115,9 @@ private final class MRelation[A,B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) ext
case None => this
}
def filter(f: (A,B) => Boolean): Relation[A,B] = Relation.empty[A,B] ++ all.filter(f.tupled)
def contains(a: A, b: B): Boolean = forward(a)(b)
override def toString = all.map { case (a,b) => a + " -> " + b }.mkString("Relation [", ", ", "]")
}