/* sbt -- Simple Build Tool * Copyright 2011 Mark Harrah */ package sbt import Build.data import Scope.{fillTaskAxis, GlobalScope, ThisScope} import compiler.Discovery import Project.{inConfig, Initialize, inScope, inTask, ScopedKey, Setting, SettingsDefinition} import Configurations.{Compile, CompilerPlugin, IntegrationTest, Runtime, Test} import complete._ import std.TaskExtra._ import org.scalatools.testing.{AnnotatedFingerprint, SubclassFingerprint} 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 sbinary.DefaultProtocol.StringFormat import Cache.seqFormat import Types._ import Path._ import Keys._ object Defaults extends BuildCommon { def configSrcSub(key: ScopedSetting[File]): Initialize[File] = (key in ThisScope.copy(config = Global), 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 lock(app: xsbti.AppConfiguration): xsbti.GlobalLock = app.provider.scalaProvider.launcher.globalLock 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( parallelExecution :== true, sbtVersion in GlobalScope <<= appConfiguration { _.provider.id.version }, pollInterval :== 500, logBuffered :== false, autoScalaLibrary :== true, trapExit :== false, trapExit in run :== true, logBuffered in testOnly :== true, logBuffered in test :== true, traceLevel in console :== Int.MaxValue, traceLevel in consoleProject :== Int.MaxValue, autoCompilerPlugins :== true, internalConfigurationMap :== Configurations.internalMap _, initialize :== (), credentials :== Nil, scalaHome :== None, javaHome :== None, version :== "0.1", outputStrategy :== None, fork :== false, javaOptions :== Nil, sbtPlugin :== false, crossPaths :== true, classpathTypes := Set("jar", "bundle"), aggregate :== Aggregation.Enabled, maxErrors :== 100, showTiming :== true, timingFormat :== Aggregation.defaultFormat, showSuccess :== true, commands :== Nil, retrieveManaged :== false, buildStructure <<= state map Project.structure, settings <<= buildStructure map ( _.data ), artifactClassifier :== None, artifactClassifier in packageSrc :== Some(SourceClassifier), artifactClassifier in packageDoc :== Some(DocClassifier), checksums :== IvySbt.DefaultChecksums, pomExtra := NodeSeq.Empty, pomPostProcess := idFun, pomIncludeRepository := Classpaths.defaultRepositoryFilter )) def projectCore: Seq[Setting[_]] = Seq( name <<= thisProject(_.id), runnerSetting ) def paths = Seq( baseDirectory <<= thisProject(_.base), target <<= baseDirectory / "target", defaultExcludes in GlobalScope :== (".*" - ".") || HiddenFileFilter, historyPath <<= target(t => Some(t / ".history")), sourceDirectory <<= baseDirectory / "src", sourceFilter in GlobalScope :== ("*.java" | "*.scala"), sourceManaged <<= crossTarget / "src_managed", resourceManaged <<= crossTarget / "resource_managed", cacheDirectory <<= target / "cache" ) lazy val configPaths = sourceConfigPaths ++ resourceConfigPaths ++ outputConfigPaths lazy val sourceConfigPaths = Seq( sourceDirectory <<= configSrcSub( sourceDirectory), sourceManaged <<= configSrcSub(sourceManaged), scalaSource <<= sourceDirectory / "scala", javaSource <<= sourceDirectory / "java", unmanagedSourceDirectories <<= Seq(scalaSource, javaSource).join, unmanagedSources <<= collectFiles(unmanagedSourceDirectories, sourceFilter, defaultExcludes in unmanagedSources), managedSourceDirectories <<= Seq(sourceManaged).join, managedSources <<= generate(sourceGenerators), sourceGenerators :== Nil, sourceDirectories <<= Classpaths.concatSettings(unmanagedSourceDirectories, managedSourceDirectories), sources <<= Classpaths.concat(unmanagedSources, managedSources) ) lazy val resourceConfigPaths = Seq( resourceDirectory <<= sourceDirectory / "resources", resourceManaged <<= configSrcSub(resourceManaged), unmanagedResourceDirectories <<= Seq(resourceDirectory).join, managedResourceDirectories <<= Seq(resourceManaged).join, resourceDirectories <<= Classpaths.concatSettings(unmanagedResourceDirectories, managedResourceDirectories), unmanagedResources <<= (unmanagedResourceDirectories, defaultExcludes in unmanagedResources) map unmanagedResourcesTask, resourceGenerators :== Nil, resourceGenerators <+= (definedSbtPlugins, resourceManaged) map writePluginsDescriptor, managedResources <<= generate(resourceGenerators), resources <<= Classpaths.concat(managedResources, unmanagedResources) ) lazy val outputConfigPaths = Seq( cacheDirectory <<= (cacheDirectory, configuration) { _ / _.name }, classDirectory <<= (crossTarget, configuration) { (outDir, conf) => outDir / (prefix(conf.name) + "classes") }, docDirectory <<= (crossTarget, configuration) { (outDir, conf) => outDir / (prefix(conf.name) + "api") } ) def addBaseSources = Seq( unmanagedSources <<= (unmanagedSources, baseDirectory, sourceFilter, defaultExcludes in unmanagedSources) map { (srcs,b,f,excl) => (srcs +++ b * (f -- excl)).get } ) def compileBase = inTask(console)(compilersSetting :: Nil) ++ Seq( classpathOptions in GlobalScope :== ClasspathOptions.boot, classpathOptions in GlobalScope in console :== ClasspathOptions.repl, compileOrder in GlobalScope :== CompileOrder.Mixed, compilersSetting, javacOptions in GlobalScope :== Nil, scalacOptions in GlobalScope :== Nil, scalaInstance <<= scalaInstanceSetting, scalaVersion in GlobalScope <<= appConfiguration( _.provider.scalaProvider.version), crossScalaVersions in GlobalScope <<= Seq(scalaVersion).join, crossTarget <<= (target, scalaInstance, crossPaths)( (t,si,cross) => if(cross) t / ("scala-" + si.actualVersion) else t ), cacheDirectory <<= crossTarget / "cache" ) def compilersSetting = compilers <<= (scalaInstance, appConfiguration, streams, classpathOptions, javaHome) map { (si, app, s, co, jh) => Compiler.compilers(si, co, jh)(app, s.log) } lazy val configTasks = Seq( initialCommands in GlobalScope :== "", compile <<= compileTask, compileInputs <<= compileInputsTask, console <<= consoleTask, consoleQuick <<= consoleQuickTask, discoveredMainClasses <<= TaskData.write(compile map discoverMainClasses) triggeredBy compile, definedSbtPlugins <<= discoverPlugins, inTask(run)(runnerSetting :: Nil).head, selectMainClass <<= discoveredMainClasses map selectRunMain, mainClass in run <<= (selectMainClass in run).identity, mainClass <<= discoveredMainClasses map selectPackageMain, run <<= runTask(fullClasspath, mainClass in run, runner in run), runMain <<= runMainTask(fullClasspath, runner in run), scaladocOptions <<= scalacOptions.identity, doc <<= docTask, copyResources <<= copyResourcesTask ) lazy val projectTasks: Seq[Setting[_]] = Seq( cleanFiles <<= Seq(managedDirectory, target).join, cleanKeepFiles <<= historyPath(_.toList), clean <<= (cleanFiles, cleanKeepFiles) map doClean, consoleProject <<= consoleProjectTask, watchSources <<= watchSourcesTask, watchTransitiveSources <<= watchTransitiveSourcesTask, watch <<= watchSetting ) def generate(generators: ScopedSetting[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] = generators {_.join.map(_.flatten) } def inAllConfigurations[T](key: ScopedTask[T]): Initialize[Task[Seq[T]]] = (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]]] = (state, thisProjectRef) flatMap { (s, base) => inAllDependencies(base, watchSources.task, Project structure s).join.map(_.flatten) } def watchSourcesTask: Initialize[Task[Seq[File]]] = Seq(unmanagedSources, unmanagedResources).map(inAllConfigurations).join { _.join.map(_.flatten.flatten.distinct) } 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 Some(Inc(i)) => throw i case None => error("key not found: " + Project.display(key)) } } } 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 unmanagedResourcesTask(dirs: Seq[File], excl: FileFilter) = dirs.descendentsExcept("*",excl).get lazy val testTasks = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ 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 <<= TaskData.writeRelated(detectTests)(_.map(_.name).distinct) triggeredBy compile, testListeners in GlobalScope :== Nil, testOptions in GlobalScope :== Nil, executeTests <<= (streams in test, loadedTestFrameworks, parallelExecution in test, testOptions in test, testLoader, definedTests) flatMap { (s, frameworkMap, par, options, loader, discovered) => Tests(frameworkMap, loader, discovered, options, par, s.log) }, test <<= (executeTests, streams) map { (results, s) => Tests.showResults(s.log, results) }, testOnly <<= testOnlyTask ) def testTaskOptions(key: Scoped): Seq[Setting[_]] = inTask(key)( Seq( testListeners <<= (streams, resolvedScoped, streamsManager, logBuffered in key, testListeners) map { (s, sco, sm, buff, ls) => TestLogger(s.log, testLogger(sm, test in sco.scope), buff) +: ls }, testOptions <<= (testOptions, testListeners) map { (options, ls) => Tests.Listeners(ls) +: options } ) ) def testLogger(manager: Streams, baseKey: Scoped)(tdef: TestDefinition): Logger = { val scope = baseKey.scope val extra = scope.extra match { case Select(x) => x; case _ => AttributeMap.empty } val key = ScopedKey(scope.copy(extra = Select(testExtra(extra, tdef))), baseKey.key) manager(key).log } def buffered(log: Logger): Logger = new BufferedLogger(FullLogger(log)) def testExtra(extra: AttributeMap, tdef: TestDefinition): AttributeMap = { val mod = tdef.fingerprint match { case f: SubclassFingerprint => f.isModule; case f: AnnotatedFingerprint => f.isModule; case _ => false } extra.put(name.key, tdef.name).put(isModule, mod) } def testOnlyTask = InputTask( TaskData(definedTests)(testOnlyParser)(Nil) ) { result => (streams, loadedTestFrameworks, parallelExecution in testOnly, testOptions in testOnly, testLoader, definedTests, result) flatMap { case (s, frameworks, par, 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, par, s.log) map { results => Tests.showResults(s.log, results) } } } def detectTests: Initialize[Task[Seq[TestDefinition]]] = (loadedTestFrameworks, compile, streams) map { (frameworkMap, analysis, s) => Tests.discover(frameworkMap.values.toSeq, analysis, s.log)._1 } lazy val packageBase = Seq( artifact <<= normalizedName(n => Artifact(n)), packageOptions in GlobalScope :== Nil, artifactName in GlobalScope :== ( Artifact.artifactName _ ) ) lazy val packageConfig = Seq( packageOptions in packageBin <<= (packageOptions, mainClass in packageBin) map { _ ++ _.map(Package.MainClass.apply).toList } ) ++ packageTasks(packageBin, packageBinTask) ++ packageTasks(packageSrc, packageSrcTask) ++ packageTasks(packageDoc, packageDocTask) final val SourceClassifier = "sources" final val DocClassifier = "javadoc" private[this] val allSubpaths = (dir: File) => (dir.*** --- dir) x (relativeTo(dir)|flat) def packageBinTask = classMappings def packageDocTask = doc map allSubpaths def packageSrcTask = concatMappings(resourceMappings, sourceMappings) private type Mappings = Initialize[Task[Seq[(File, String)]]] def concatMappings(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 = (unmanagedSources, unmanagedSourceDirectories, baseDirectory) map { (srcs, sdirs, base) => ( (srcs --- sdirs --- base) x (relativeTo(sdirs)|relativeTo(base)|flat)) toSeq } def resourceMappings = (unmanagedResources, unmanagedResourceDirectories) map { (rs, rdirs) => (rs --- rdirs) x (relativeTo(rdirs)|flat) toSeq } def collectFiles(dirs: ScopedTaskable[Seq[File]], filter: ScopedTaskable[FileFilter], excludes: ScopedTaskable[FileFilter]): Initialize[Task[Seq[File]]] = (dirs, filter, excludes) map { (d,f,excl) => d.descendentsExcept(f,excl).get } def artifactPathSetting(art: ScopedSetting[Artifact]) = (crossTarget, projectID, art, scalaVersion, artifactName) { (t, module, a, sv, toString) => t / toString(sv, module, a) asFile } def pairID[A,B] = (a: A, b: B) => (a,b) def packageTasks(key: TaskKey[File], mappingsTask: Initialize[Task[Seq[(File,String)]]]) = inTask(key)( Seq( key in ThisScope.copy(task = Global) <<= packageTask, packageConfiguration <<= packageConfigurationTask, mappings <<= mappingsTask, packagedArtifact <<= (artifact, key) map pairID, artifact <<= (artifact, artifactClassifier, configuration) { (a,classifier,c) => val cPart = if(c == Compile) Nil else c.name :: Nil val combined = cPart ++ classifier.toList a.copy(classifier = if(combined.isEmpty) None else Some(combined mkString "-")) }, cacheDirectory <<= cacheDirectory / key.key.label, artifactPath <<= artifactPathSetting(artifact) )) def packageTask: Initialize[Task[File]] = (packageConfiguration, cacheDirectory, streams) map { (config, cacheDir, s) => Package(config, cacheDir, s.log) config.jar } def packageConfigurationTask: Initialize[Task[Package.Configuration]] = (mappings, artifactPath, packageOptions) map { (srcs, path, options) => new Package.Configuration(srcs, path, options) } 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 doClean(clean: Seq[File], preserve: Seq[File]): Unit = IO.withTemporaryDirectory { temp => val mappings = preserve.filter(_.exists).zipWithIndex map { case (f, i) => (f, new File(temp, i.toHexString)) } IO.move(mappings) IO.delete(clean) IO.move(mappings.map(_.swap)) } def runMainTask(classpath: ScopedTask[Classpath], scalaRun: ScopedSetting[ScalaRun]): Initialize[InputTask[Unit]] = { import DefaultParsers._ InputTask( TaskData(discoveredMainClasses)(runMainParser)(Nil) ) { result => (classpath, scalaRun, streams, result) map { case (cp, runner, s, (mainClass, args)) => toError(runner.run(mainClass, data(cp), args, s.log)) } } } def runTask(classpath: ScopedTask[Classpath], mainClassTask: ScopedTask[Option[String]], scalaRun: ScopedSetting[ScalaRun]): Initialize[InputTask[Unit]] = inputTask { result => (classpath, mainClassTask, scalaRun, streams, result) map { (cp, main, runner, s, args) => val mainClass = main getOrElse error("No main class detected.") toError(runner.run(mainClass, data(cp), args, s.log)) } } def runnerSetting = runner <<= runnerInit def runnerInit: Initialize[ScalaRun] = (scalaInstance, baseDirectory, javaOptions, outputStrategy, fork, javaHome, trapExit) { (si, base, options, strategy, forkRun, javaHomeDir, trap) => if(forkRun) { new ForkRun( ForkOptions(scalaJars = si.jars, javaHome = javaHomeDir, outputStrategy = strategy, runJVMOptions = options, workingDirectory = Some(base)) ) } else new Run(si, trap) } def docTask: Initialize[Task[File]] = (cacheDirectory, compileInputs, streams, docDirectory, configuration, scaladocOptions) map { (cache, in, s, target, config, options) => val d = new Scaladoc(in.config.maxErrors, in.compilers.scalac) val cp = in.config.classpath.toList - in.config.classesDirectory d.cached(cache / "doc", nameForSrc(config.name), in.config.sources, cp, target, options, s.log) target } def mainRunTask = run <<= runTask(fullClasspath in Runtime, mainClass in run, runner in run) def mainRunMainTask = runMain <<= runMainTask(fullClasspath in Runtime, 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 = (state, streams, initialCommands in consoleProject) map { (state, s, extra) => ConsoleProject(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 in task, 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, compileOrder, streams) map { (cp, srcs, cs, javacOpts, scalacOpts, cacheDir, classes, order, s) => val classpath = classes +: data(cp) val analysis = analysisMap(cp) val cache = cacheDir / "compile" Compiler.inputs(classpath, srcs, classes, scalacOpts, javacOpts, analysis, cache, 100, order)(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) | flat(target)) s.log.debug("Copy resource mappings: " + mappings.mkString("\n\t","\n\t","")) Sync(cacheFile)( mappings ) mappings } def runMainParser: (State, Seq[String]) => Parser[(String, Seq[String])] = { import DefaultParsers._ (state, mainClasses) => Space ~> token(NotSpace examples mainClasses.toSet) ~ spaceDelimited("") } def testOnlyParser: (State, Seq[String]) => Parser[(Seq[String],Seq[String])] = { (state, tests) => import DefaultParsers._ val selectTests = distinctParser(tests.toSet) val options = (token(Space) ~> token("--") ~> spaceDelimited("