sbt/main/DefaultProject.scala

233 lines
11 KiB
Scala

/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import std._
import compile.{Discovered,Discovery}
import inc.Analysis
import TaskExtra._
import Path.fileToPath
import Configurations.{Compile => CompileConfig, Test => TestConfig, Runtime => RunConfig, Default => DefaultConfig, IntegrationTest => ITestConfig}
import ClasspathProject._
import Types._
import xsbti.api.Definition
import org.scalatools.testing.Framework
import java.io.File
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 = (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 c => outputDirectory / (c.name + "-classes")
}
lazy val products: Classpath = TaskMap(productsTask)
// TODO: include resources, perhaps handle jars v. directories
def productsTask(conf: Configuration): Task[Seq[Attributed[File]]] =
conf match {
case CompileConfig | DefaultConfig => makeProducts(compile.compile, compile.compileInputs, name, "")
case TestConfig => makeProducts(testCompile.compile, testCompile.compileInputs, name, "test-")
case x => task { Nil }
}
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.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 pkgMainClass: Task[Option[String]] =
discoveredMainClasses map { classes => SelectMainClass(None, classes) }
lazy val runner: Task[ScalaRun] =
buildScalaInstance map { si => new Run(si) }
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 testOptions: Task[Seq[TestOption]] = task { Nil }
lazy val test = testTasks(None, TestConfig, testOptions, testCompile.compile, buildScalaInstance)
lazy val compile = compileTasks(None, CompileConfig, "src" / "main", info.projectDirectory : Path, buildScalaInstance)
lazy val testCompile = compileTasks(Some("test"), TestConfig, "src" / "test", Path.emptyPathFinder, buildScalaInstance)
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 set = input map { in =>
val Seq(name, value) = in.splitArgs.take(2)
println(name + "=" + value)
java.lang.System.setProperty(name, value)
}
def sourceFilter: FileFilter = "*.java" | "*.scala"
def compileTask(inputs: Task[Compile.Inputs]): Task[Analysis] =
inputs :^: streams :^: KNil map { case i :+: s :+: HNil => Compile(i, s.log) }
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) =>
val log = ConsoleLogger()
val compilers = Compile.compilers(si)(info.configuration, log)
val javaSrc = base / "java"
val scalaSrc = base / "scala"
val out = "target" / si.actualVersion
import Path._
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, allBases.getFiles.toSeq, analysis, cache, 100)(compilers, log)
}
}
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 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) }
}