/* sbt -- Simple Build Tool * Copyright 2011 Mark Harrah */ package sbt import Build.data import Scope.{GlobalScope, ThisScope} import compiler.Discovery import Project.{inConfig, Initialize, inScope, inTask, ScopedKey, Setting} import Configurations.{Compile => CompileConf, Test => TestConf} import EvaluateTask.resolvedScoped import complete._ import std.TaskExtra._ import scala.xml.{Node => XNode,NodeSeq} import org.apache.ivy.core.module.{descriptor, id} import descriptor.ModuleDescriptor, id.ModuleRevisionId import java.io.File import java.net.URL import Types._ import Path._ import GlobFilter._ import Keys._ object Defaults { implicit def richFileSetting(s: ScopedSetting[File]): RichFileSetting = new RichFileSetting(s) implicit def richFilesSetting(s: ScopedSetting[Seq[File]]): RichFilesSetting = new RichFilesSetting(s) final class RichFileSetting(s: ScopedSetting[File]) extends RichFileBase { def /(c: String): Initialize[File] = s { _ / c } protected[this] def map0(f: PathFinder => PathFinder) = s(file => finder(f)(file :: Nil)) } final class RichFilesSetting(s: ScopedSetting[Seq[File]]) extends RichFileBase { def /(s: String): Initialize[Seq[File]] = map0 { _ / s } protected[this] def map0(f: PathFinder => PathFinder) = s(finder(f)) } sealed abstract class RichFileBase { def *(filter: FileFilter): Initialize[Seq[File]] = map0 { _ * filter } def **(filter: FileFilter): Initialize[Seq[File]] = map0 { _ ** filter } protected[this] def map0(f: PathFinder => PathFinder): Initialize[Seq[File]] protected[this] def finder(f: PathFinder => PathFinder): Seq[File] => Seq[File] = in => f(in).getFiles } def configSrcSub(key: ScopedSetting[File]): Initialize[File] = (key, configuration) { (src, conf) => src / nameForSrc(conf.name) } def nameForSrc(config: String) = if(config == "compile") "main" else config def prefix(config: String) = if(config == "compile") "" else config + "-" def toSeq[T](key: ScopedSetting[T]): Initialize[Seq[T]] = key( _ :: Nil) def extractAnalysis[T](a: Attributed[T]): (T, inc.Analysis) = (a.data, a.metadata get Keys.analysis getOrElse inc.Analysis.Empty) def analysisMap[T](cp: Seq[Attributed[T]]): Map[T, inc.Analysis] = (cp map extractAnalysis).toMap def buildCore: Seq[Setting[_]] = thisBuildCore ++ globalCore def thisBuildCore: Seq[Setting[_]] = inScope(GlobalScope.copy(project = Select(ThisBuild)))(Seq( managedDirectory <<= baseDirectory(_ / "lib_managed") )) def globalCore: Seq[Setting[_]] = inScope(GlobalScope)(Seq( pollInterval :== 500, scalaHome :== None, javaHome :== None, outputStrategy :== None, fork :== false, javaOptions :== Nil, sbtPlugin :== false, crossPaths :== true, generatedResources :== Nil, classpathTypes := Set("jar", "bundle"), // shellPrompt :== (_ => "> "), aggregate :== Aggregation.Enabled, maxErrors :== 100, showTiming :== true, timingFormat :== Aggregation.defaultFormat, showSuccess :== true, commands :== Nil, retrieveManaged :== false, settings <<= EvaluateTask.state map { state => Project.structure(state).data } )) def projectCore: Seq[Setting[_]] = Seq( name <<= thisProject(_.id), version :== "0.1" ) def paths = Seq( baseDirectory <<= thisProject(_.base), target <<= baseDirectory / "target", defaultExcludes in GlobalScope :== (".*" - ".") || HiddenFileFilter, historyPath <<= target(t => Some(t / ".history")), cacheDirectory <<= target / "cache", sourceDirectory <<= baseDirectory / "src", sourceFilter in GlobalScope :== ("*.java" | "*.scala"), sourceManaged <<= baseDirectory / "src_managed" ) lazy val configPaths = Seq( sourceDirectory <<= configSrcSub( sourceDirectory in Scope(This,Global,This,This) ), sourceManaged <<= configSrcSub(sourceManaged), cacheDirectory <<= (cacheDirectory, configuration) { _ / _.name }, classDirectory <<= (target, configuration) { (outDir, conf) => outDir / (prefix(conf.name) + "classes") }, docDirectory <<= (target, configuration) { (outDir, conf) => outDir / (prefix(conf.name) + "api") }, sources <<= (sourceDirectories, sourceFilter, defaultExcludes) map { (d,f,excl) => d.descendentsExcept(f,excl).getFiles }, scalaSource <<= sourceDirectory / "scala", javaSource <<= sourceDirectory / "java", resourceDirectory <<= sourceDirectory / "resources", generatedResourceDirectory <<= target / "res_managed", sourceDirectories <<= (scalaSource, javaSource) { _ :: _ :: Nil }, resourceDirectories <<= (resourceDirectory, generatedResourceDirectory) { _ :: _ :: Nil }, resources <<= (resourceDirectories, defaultExcludes, generatedResources, generatedResourceDirectory) map resourcesTask, generatedResources <<= (definedSbtPlugins, generatedResourceDirectory) map writePluginsDescriptor ) def addBaseSources = Seq( sources <<= (sources, baseDirectory, sourceFilter, defaultExcludes) map { (srcs,b,f,excl) => (srcs +++ b * (f -- excl)).getFiles } ) def compileBase = Seq( classpathOptions in GlobalScope :== ClasspathOptions.auto, compilers <<= (scalaInstance, appConfiguration, streams, classpathOptions, javaHome) map { (si, app, s, co, jh) => Compiler.compilers(si, co, jh)(app, s.log) }, javacOptions in GlobalScope :== Nil, scalacOptions in GlobalScope :== Nil, scalaInstance <<= scalaInstanceSetting, scalaVersion <<= appConfiguration( _.provider.scalaProvider.version), target <<= (target, scalaInstance, crossPaths)( (t,si,cross) => if(cross) t / ("scala-" + si.actualVersion) else t ) ) lazy val configTasks = Seq( initialCommands in GlobalScope :== "", compile <<= compileTask, compileInputs <<= compileInputsTask, console <<= consoleTask, consoleQuick <<= consoleQuickTask, discoveredMainClasses <<= compile map discoverMainClasses, definedSbtPlugins <<= discoverPlugins, inTask(run)(runnerSetting :: Nil).head, selectMainClass <<= discoveredMainClasses map selectRunMain, mainClass in run :== selectMainClass, mainClass <<= discoveredMainClasses map selectPackageMain, run <<= runTask(fullClasspath, mainClass in run, runner in run), scaladocOptions <<= scalacOptions(identity), doc <<= docTask, copyResources <<= copyResourcesTask ) lazy val projectTasks: Seq[Setting[_]] = Seq( cleanFiles <<= (target, sourceManaged) { _ :: _ :: Nil }, clean <<= cleanFiles map IO.delete, consoleProject <<= consoleProjectTask, watchSources <<= watchSourcesTask, watchTransitiveSources <<= watchTransitiveSourcesTask, watch <<= watchSetting ) def inAllConfigurations[T](key: ScopedTask[T]): Initialize[Task[Seq[T]]] = (EvaluateTask.state, thisProjectRef) flatMap { (state, ref) => val structure = Project structure state val configurations = Project.getProject(ref, structure).toList.flatMap(_.configurations) configurations.flatMap { conf => key in (ref, conf) get structure.data } join } def watchTransitiveSourcesTask: Initialize[Task[Seq[File]]] = (EvaluateTask.state, thisProjectRef) flatMap { (s, base) => inAllDependencies(base, watchSources.setting, Project structure s).join.map(_.flatten) } def watchSourcesTask: Initialize[Task[Seq[File]]] = Seq(sources, resources).map(inAllConfigurations).join { _.join.map(_.flatten.flatten) } def watchSetting: Initialize[Watched] = (pollInterval, thisProjectRef) { (interval, base) => new Watched { val scoped = watchTransitiveSources in base val key = ScopedKey(scoped.scope, scoped.key) override def pollInterval = interval override def watchPaths(s: State) = EvaluateTask.evaluateTask(Project structure s, key, s, base) match { case Some(Value(ps)) => ps; case _ => Nil } } } def scalaInstanceSetting = (appConfiguration, scalaVersion, scalaHome){ (app, version, home) => val launcher = app.provider.scalaProvider.launcher home match { case None => ScalaInstance(version, launcher) case Some(h) => ScalaInstance(h, launcher) } } def resourcesTask(dirs: Seq[File], excl: FileFilter, gen: Seq[File], genDir: File) = gen ++ (dirs --- genDir).descendentsExcept("*",excl).getFiles lazy val testTasks = Seq( testLoader <<= (fullClasspath, scalaInstance) map { (cp, si) => TestFramework.createTestLoader(data(cp), si) }, testFrameworks in GlobalScope :== { import sbt.TestFrameworks._ Seq(ScalaCheck, Specs, ScalaTest, ScalaCheckCompat, ScalaTestCompat, SpecsCompat, JUnit) }, loadedTestFrameworks <<= (testFrameworks, streams, testLoader) map { (frameworks, s, loader) => frameworks.flatMap(f => f.create(loader, s.log).map( x => (f,x)).toIterable).toMap }, definedTests <<= (loadedTestFrameworks, compile, streams) map { (frameworkMap, analysis, s) => val tests = Tests.discover(frameworkMap.values.toSeq, analysis, s.log)._1 IO.writeLines(s.text(CompletionsID), tests.map(_.name).distinct) tests }, testListeners <<= (streams in test) map ( s => TestLogger(s.log) :: Nil ), testOptions <<= testListeners map { listeners => Tests.Listeners(listeners) :: Nil }, executeTests <<= (streams in test, loadedTestFrameworks, testOptions, testLoader, definedTests) flatMap { (s, frameworkMap, options, loader, discovered) => Tests(frameworkMap, loader, discovered, options, s.log) }, test <<= (executeTests, streams) map { (results, s) => Tests.showResults(s.log, results) }, testOnly <<= testOnlyTask ) def testOnlyTask = InputTask(resolvedScoped(testOnlyParser)) ( result => (streams, loadedTestFrameworks, testOptions, testLoader, definedTests, result) flatMap { case (s, frameworks, opts, loader, discovered, (tests, frameworkOptions)) => val modifiedOpts = Tests.Filter(if(tests.isEmpty) _ => true else tests.toSet ) +: Tests.Argument(frameworkOptions : _*) +: opts Tests(frameworks, loader, discovered, modifiedOpts, s.log) map { results => Tests.showResults(s.log, results) } } ) lazy val packageBase = Seq( jarNameSetting, packageOptions in GlobalScope :== Nil, nameToString in GlobalScope :== (ArtifactName.show _) ) lazy val packageConfig = Seq( jarName <<= (jarName, configuration) { (n,c) => n.copy(config = c.name) }, packageOptions in packageBin <<= (packageOptions, mainClass in packageBin) map { _ ++ _.map(Package.MainClass.apply).toList } ) ++ packageTasks(packageBin, "", packageBinTask) ++ packageTasks(packageSrc, "src", packageSrcTask) ++ packageTasks(packageDoc, "doc", packageDocTask) private[this] val allSubpaths = (dir: File) => (dir.*** --- dir) x relativeTo(dir) def packageBinTask = classMappings def packageDocTask = doc map allSubpaths def packageSrcTask = concat(resourceMappings, sourceMappings) private type Mappings = Initialize[Task[Seq[(File, String)]]] def concat(as: Mappings, bs: Mappings) = (as zipWith bs)( (a,b) => (a :^: b :^: KNil) map { case a :+: b :+: HNil => a ++ b } ) def classMappings = (compileInputs, products) map { (in, _) => allSubpaths(in.config.classesDirectory) } // drop base directories, since there are no valid mappings for these def sourceMappings = (sources, sourceDirectories, baseDirectory) map { (srcs, sdirs, base) => ( (srcs --- sdirs --- base) x (relativeTo(sdirs)|relativeTo(base))) toSeq } def resourceMappings = (resources, resourceDirectories) map { (rs, rdirs) => (rs --- rdirs) x relativeTo(rdirs) toSeq } def jarNameSetting = jarName <<= (moduleID, version, scalaVersion, crossPaths) { (n,v, sv, withCross) => ArtifactName(base = n, version = v, config = "", tpe = "", ext = "jar", cross = if(withCross) sv else "") } def jarPathSetting = jarPath <<= (target, jarName, nameToString) { (t, n, toString) => t / toString(n) } def packageTasks(key: TaskKey[Package.Configuration], tpeString: String, mappingsTask: Initialize[Task[Seq[(File,String)]]]) = inTask(key)( Seq( key in ThisScope.copy(task = Global) <<= packageTask, mappings <<= mappingsTask, jarType :== tpeString, jarName <<= (jarType,jarName){ (tpe, name) => (name.copy(tpe = tpe)) }, cacheDirectory <<= cacheDirectory / key.key.label, jarPathSetting )) def packageTask: Initialize[Task[Package.Configuration]] = (jarPath, mappings, packageOptions, cacheDirectory, streams) map { (jar, srcs, options, cacheDir, s) => val config = new Package.Configuration(srcs, jar, options) Package(config, cacheDir, s.log) config } def selectRunMain(classes: Seq[String]): Option[String] = sbt.SelectMainClass(Some(SimpleReader readLine _), classes) def selectPackageMain(classes: Seq[String]): Option[String] = sbt.SelectMainClass(None, classes) def runTask(classpath: ScopedTask[Classpath], mainClassTask: ScopedTask[Option[String]], scalaRun: ScopedSetting[ScalaRun]): Initialize[InputTask[Unit]] = InputTask(_ => complete.Parsers.spaceDelimited("")) { result => (classpath, mainClassTask, scalaRun, streams, result) map { (cp, main, runner, s, args) => val mainClass = main getOrElse error("No main class detected.") runner.run(mainClass, data(cp), args, s.log) foreach error } } def runnerSetting = runner <<= (scalaInstance, baseDirectory, javaOptions, outputStrategy, fork, javaHome) { (si, base, options, strategy, forkRun, javaHomeDir) => if(forkRun) { new ForkRun( ForkOptions(scalaJars = si.jars, javaHome = javaHomeDir, outputStrategy = strategy, runJVMOptions = options, workingDirectory = Some(base)) ) } else new Run(si) } def docTask: Initialize[Task[File]] = (compileInputs, streams, docDirectory, configuration, scaladocOptions) map { (in, s, target, config, options) => val d = new Scaladoc(in.config.maxErrors, in.compilers.scalac) d(nameForSrc(config.name), in.config.sources, in.config.classpath, target, options)(s.log) target } def mainRunTask = run <<= runTask(fullClasspath in Configurations.Runtime, mainClass in run, runner in run) def discoverMainClasses(analysis: inc.Analysis): Seq[String] = Discovery.applications(Tests.allDefs(analysis)) collect { case (definition, discovered) if(discovered.hasMain) => definition.name } def consoleProjectTask = (EvaluateTask.state, streams, initialCommands in consoleProject) map { (state, s, extra) => Console.sbt(state, extra)(s.log); println() } def consoleTask: Initialize[Task[Unit]] = consoleTask(fullClasspath, console) def consoleQuickTask = consoleTask(externalDependencyClasspath, consoleQuick) def consoleTask(classpath: TaskKey[Classpath], task: TaskKey[_]): Initialize[Task[Unit]] = (compilers, classpath, scalacOptions in task, initialCommands in task, streams) map { (cs, cp, options, initCommands, s) => (new Console(cs.scalac))(data(cp), options, initCommands, s.log).foreach(msg => error(msg)) println() } def compileTask = (compileInputs, streams) map { (i,s) => Compiler(i,s.log) } def compileInputsTask = (dependencyClasspath, sources, compilers, javacOptions, scalacOptions, cacheDirectory, classDirectory, streams) map { (cp, srcs, cs, javacOpts, scalacOpts, cacheDir, classes, s) => val classpath = classes +: data(cp) val analysis = analysisMap(cp) val cache = cacheDir / "compile" Compiler.inputs(classpath, srcs, classes, scalacOpts, javacOpts, analysis, cache, 100)(cs, s.log) } def writePluginsDescriptor(plugins: Set[String], dir: File): List[File] = { val descriptor: File = dir / "sbt" / "sbt.plugins" if(plugins.isEmpty) { IO.delete(descriptor) Nil } else { IO.writeLines(descriptor, plugins.toSeq.sorted) descriptor :: Nil } } def discoverPlugins: Initialize[Task[Set[String]]] = (compile, sbtPlugin, streams) map { (analysis, isPlugin, s) => if(isPlugin) discoverSbtPlugins(analysis, s.log) else Set.empty } def discoverSbtPlugins(analysis: inc.Analysis, log: Logger): Set[String] = { val pluginClass = classOf[Plugin].getName val discovery = Discovery(Set(pluginClass), Set.empty)( Tests allDefs analysis ) discovery collect { case (df, disc) if (disc.baseClasses contains pluginClass) && disc.isModule => df.name } toSet; } def copyResourcesTask = (classDirectory, cacheDirectory, resources, resourceDirectories, streams) map { (target, cache, resrcs, dirs, s) => val cacheFile = cache / "copy-resources" val mappings = (resrcs --- dirs) x rebase(dirs, target) s.log.debug("Copy resource mappings: " + mappings.mkString("\n\t","\n\t","")) Sync(cacheFile)( mappings ) mappings } def testOnlyParser(resolved: ScopedKey[_]): State => Parser[(Seq[String],Seq[String])] = { state => import DefaultParsers._ def distinctParser(exs: Set[String]): Parser[Seq[String]] = token(Space ~> (NotSpace - "--").examples(exs) ).flatMap(ex => distinctParser(exs - ex).map(ex +: _)) ?? Nil val tests = savedLines(state, resolved, definedTests) val selectTests = distinctParser(tests.toSet) // todo: proper IDs val options = (token(Space ~> "--") ~> spaceDelimited("