diff --git a/src/main/scala/sbt/AnalysisCallback.scala b/src/main/scala/sbt/AnalysisCallback.scala index 3578c8942..51b5618c5 100644 --- a/src/main/scala/sbt/AnalysisCallback.scala +++ b/src/main/scala/sbt/AnalysisCallback.scala @@ -59,9 +59,9 @@ trait AnalysisCallback extends NotNull /** Called when a module with a public 'main' method with the right signature is found.*/ def foundApplication(sourcePath: Path, className: String): Unit } -abstract class BasicAnalysisCallback[A <: BasicCompileAnalysis](val basePath: Path, val superclassNames: Iterable[String], - protected val analysis: A) extends AnalysisCallback +abstract class BasicAnalysisCallback[A <: BasicCompileAnalysis](val basePath: Path, protected val analysis: A) extends AnalysisCallback { + def superclassNames: Iterable[String] def superclassNotFound(superclassName: String) {} def beginSource(sourcePath: Path): Unit = @@ -85,8 +85,8 @@ abstract class BasicAnalysisCallback[A <: BasicCompileAnalysis](val basePath: Pa def endSource(sourcePath: Path): Unit = analysis.removeSelfDependency(sourcePath) } -abstract class BasicCompileAnalysisCallback(basePath: Path, superclassNames: Iterable[String], analysis: CompileAnalysis) - extends BasicAnalysisCallback(basePath, superclassNames, analysis) +abstract class BasicCompileAnalysisCallback(basePath: Path, analysis: CompileAnalysis) + extends BasicAnalysisCallback(basePath, analysis) { def foundApplication(sourcePath: Path, className: String): Unit = analysis.addApplication(sourcePath, className) diff --git a/src/main/scala/sbt/AutoCompilerPlugins.scala b/src/main/scala/sbt/AutoCompilerPlugins.scala index 1b5a94864..885cfdd68 100644 --- a/src/main/scala/sbt/AutoCompilerPlugins.scala +++ b/src/main/scala/sbt/AutoCompilerPlugins.scala @@ -6,22 +6,8 @@ package sbt trait AutoCompilerPlugins extends BasicScalaProject { import Configurations.CompilerPlugin - abstract override def ivyConfigurations = - { - val superConfigurations = super.ivyConfigurations.toList - val newConfigurations = - if(superConfigurations.isEmpty) - { - if(useDefaultConfigurations) - CompilerPlugin :: Configurations.defaultMavenConfigurations - else - Configurations.Default :: CompilerPlugin :: Nil - } - else - CompilerPlugin :: superConfigurations - log.debug("Auto configurations: " + newConfigurations.toList.mkString(", ")) - Configurations.removeDuplicates(newConfigurations) - } + abstract override def extraDefaultConfigurations = + CompilerPlugin :: super.extraDefaultConfigurations abstract override def compileOptions = compilerPlugins ++ super.compileOptions /** A PathFinder that provides the classpath to search for compiler plugins. */ diff --git a/src/main/scala/sbt/BasicProjectTypes.scala b/src/main/scala/sbt/BasicProjectTypes.scala index 038a9215e..03a1d3073 100644 --- a/src/main/scala/sbt/BasicProjectTypes.scala +++ b/src/main/scala/sbt/BasicProjectTypes.scala @@ -232,7 +232,7 @@ trait BasicManagedProject extends ManagedProject with ReflectiveManagedProject w /** The dependency manager that represents inline declarations. The default manager packages the information * from 'ivyXML', 'projectID', 'repositories', and 'libraryDependencies' and does not typically need to be * be overridden. */ - def manager = new SimpleManager(ivyXML, true, projectID, repositories, ivyConfigurations, defaultConfiguration, artifacts, libraryDependencies.toList: _*) + def manager = new SimpleManager(ivyXML, true, projectID, repositories.toSeq, ivyConfigurations, defaultConfiguration, artifacts, libraryDependencies.toList: _*) /** The pattern for Ivy to use when retrieving dependencies into the local project. Classpath management * depends on the first directory being [conf] and the extension being [ext].*/ @@ -245,24 +245,18 @@ trait BasicManagedProject extends ManagedProject with ReflectiveManagedProject w override def ivyConfigurations: Iterable[Configuration] = { val reflective = super.ivyConfigurations + val extra = extraDefaultConfigurations if(useDefaultConfigurations) { - if(reflective.isEmpty && !useIntegrationTestConfiguration) + if(reflective.isEmpty && extra.isEmpty) Nil else - { - val base = Configurations.defaultMavenConfigurations ++ reflective - val allConfigurations = - if(useIntegrationTestConfiguration) - base ++ List(Configurations.IntegrationTest) - else - base - Configurations.removeDuplicates(allConfigurations) - } + Configurations.removeDuplicates(Configurations.defaultMavenConfigurations ++ reflective ++ extra) } else - reflective + reflective ++ extraDefaultConfigurations } + def extraDefaultConfigurations: List[Configuration] = Nil def useIntegrationTestConfiguration = false def defaultConfiguration: Option[Configuration] = Some(Configurations.DefaultConfiguration(useDefaultConfigurations)) def useMavenConfigurations = true // TODO: deprecate after going through a minor version series to verify that this works ok diff --git a/src/main/scala/sbt/BuilderProject.scala b/src/main/scala/sbt/BuilderProject.scala index 738833939..ceef28d46 100644 --- a/src/main/scala/sbt/BuilderProject.scala +++ b/src/main/scala/sbt/BuilderProject.scala @@ -7,6 +7,7 @@ import BasicProjectPaths._ sealed abstract class InternalProject extends Project { + override def defaultLoggingLevel = Level.Warn override final def historyPath = None override def tasks: Map[String, Task] = Map.empty override final protected def disableCrossPaths = false @@ -76,8 +77,9 @@ private sealed abstract class BasicBuilderProject extends InternalProject with S } } protected def analysisCallback: AnalysisCallback = - new BasicAnalysisCallback(info.projectPath, List(Project.ProjectClassName), analysis) + new BasicAnalysisCallback(info.projectPath, analysis) { + def superclassNames = List(Project.ProjectClassName) def foundApplication(sourcePath: Path, className: String) {} def foundSubclass(sourcePath: Path, subclassName: String, superclassName: String, isModule: Boolean) { @@ -107,12 +109,12 @@ private sealed abstract class BasicBuilderProject extends InternalProject with S override final def methods = Map.empty } /** The project definition used to build project definitions. */ -private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path, additional: Iterable[Path], override protected val logImpl: Logger) extends BasicBuilderProject +private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path, additional: Iterable[Path], rawLogger: Logger) extends BasicBuilderProject { private lazy val pluginProject = { if(pluginPath.exists) - Some(new PluginBuilderProject(ProjectInfo(pluginPath.asFile, Nil, None))) + Some(new PluginBuilderProject(ProjectInfo(pluginPath.asFile, Nil, None)(rawLogger))) else None } @@ -125,7 +127,6 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path, final class PluginBuilderProject(val info: ProjectInfo) extends BasicBuilderProject { - override protected def logImpl = BuilderProject.this.log val pluginUptodate = propertyOptional[Boolean](false) def tpe = "plugin definition" def managedSourcePath = path(BasicDependencyPaths.DefaultManagedSourceDirectoryName) @@ -159,8 +160,8 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path, { Control.thread(projectDefinition) { case Some(definition) => - logInfo("\nUpdating plugins") - val pluginInfo = ProjectInfo(info.projectPath.asFile, Nil, None) + logInfo("\nUpdating plugins...") + val pluginInfo = ProjectInfo(info.projectPath.asFile, Nil, None)(rawLogger) val pluginBuilder = Project.constructProject(pluginInfo, Project.getProjectClass[PluginDefinition](definition, projectClasspath, getClass.getClassLoader)) pluginBuilder.projectName() = "Plugin builder" pluginBuilder.projectVersion() = OpaqueVersion("1.0") @@ -199,6 +200,7 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path, } class PluginDefinition(val info: ProjectInfo) extends InternalProject with BasicManagedProject { + override def defaultLoggingLevel = Level.Info override final def outputPattern = "[artifact](-[revision]).[ext]" override final val tasks = Map("update" -> update) override def projectClasspath(config: Configuration) = Path.emptyPathFinder diff --git a/src/main/scala/sbt/Conditional.scala b/src/main/scala/sbt/Conditional.scala index fb31fb60f..446f4d9a1 100644 --- a/src/main/scala/sbt/Conditional.scala +++ b/src/main/scala/sbt/Conditional.scala @@ -213,12 +213,11 @@ class CompileConditional(override val config: CompileConfiguration) extends Abst protected def constructAnalysis(analysisPath: Path, projectPath: Path, log: Logger) = new CompileAnalysis(analysisPath, projectPath, log) protected def analysisCallback = new CompileAnalysisCallback - protected class CompileAnalysisCallback extends BasicCompileAnalysisCallback(projectPath, testDefinitionClassNames, analysis) + protected class CompileAnalysisCallback extends BasicCompileAnalysisCallback(projectPath, analysis) { - def foundSubclass(sourcePath: Path, subclassName: String, superclassName: String, isModule: Boolean) - { + def superclassNames = testDefinitionClassNames + def foundSubclass(sourcePath: Path, subclassName: String, superclassName: String, isModule: Boolean): Unit = analysis.addTest(sourcePath, TestDefinition(isModule, subclassName, superclassName)) - } } } abstract class AbstractCompileConditional(val config: AbstractCompileConfiguration) extends Conditional[Path, Path, File] diff --git a/src/main/scala/sbt/DefaultProject.scala b/src/main/scala/sbt/DefaultProject.scala index 4f72edbad..bba9655df 100644 --- a/src/main/scala/sbt/DefaultProject.scala +++ b/src/main/scala/sbt/DefaultProject.scala @@ -147,13 +147,13 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec } } /** The unmanaged base classpath. By default, the unmanaged classpaths for test and run include this classpath. */ - protected def mainUnmanagedClasspath = mainCompilePath +++ mainResourceClasspath +++ unmanagedClasspath + protected def mainUnmanagedClasspath = mainCompilePath +++ mainResourcesOutputPath +++ unmanagedClasspath /** The unmanaged classpath for the run configuration. By default, it includes the base classpath returned by * `mainUnmanagedClasspath`.*/ protected def runUnmanagedClasspath = mainUnmanagedClasspath +++ mainDependencies.scalaCompiler /** The unmanaged classpath for the test configuration. By default, it includes the run classpath, which includes the base * classpath returned by `mainUnmanagedClasspath`.*/ - protected def testUnmanagedClasspath = testCompilePath +++ testResourceClasspath +++ testDependencies.scalaCompiler +++ runUnmanagedClasspath + protected def testUnmanagedClasspath = testCompilePath +++ testResourcesOutputPath +++ testDependencies.scalaCompiler +++ runUnmanagedClasspath /** @deprecated Use `mainDependencies.scalaJars`*/ @deprecated protected final def scalaJars: Iterable[File] = mainDependencies.scalaJars.get.map(_.asFile) @@ -181,9 +181,8 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec def log = BasicScalaProject.this.log def projectPath = info.projectPath def baseCompileOptions: Seq[CompileOption] - lazy val localBaseOptions = baseCompileOptions - def options = optionsAsString(localBaseOptions.filter(!_.isInstanceOf[MaxCompileErrors])) - def maxErrors = maximumErrors(localBaseOptions) + def options = optionsAsString(baseCompileOptions.filter(!_.isInstanceOf[MaxCompileErrors])) + def maxErrors = maximumErrors(baseCompileOptions) def compileOrder = BasicScalaProject.this.compileOrder } class MainCompileConfig extends BaseCompileConfig @@ -233,9 +232,9 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec protected def compileAction = task { doCompile(mainCompileConditional) } describedAs MainCompileDescription protected def testCompileAction = task { doCompile(testCompileConditional) } dependsOn compile describedAs TestCompileDescription protected def cleanAction = cleanTask(outputPath, cleanOptions) describedAs CleanDescription - protected def runAction = task { args => runTask(getMainClass(true), runClasspath, args, getRunner) dependsOn(compile) } describedAs RunDescription + protected def runAction = task { args => runTask(getMainClass(true), runClasspath, args, getRunner) dependsOn(compile, copyResources) } describedAs RunDescription protected def consoleQuickAction = consoleTask(consoleClasspath, getRunner) describedAs ConsoleQuickDescription - protected def consoleAction = consoleTask(consoleClasspath, getRunner).dependsOn(testCompile) describedAs ConsoleDescription + protected def consoleAction = consoleTask(consoleClasspath, getRunner).dependsOn(testCompile, copyResources, copyTestResources) describedAs ConsoleDescription protected def docAction = scaladocTask(mainLabel, mainSources, mainDocPath, docClasspath, documentOptions).dependsOn(compile) describedAs DocDescription protected def docTestAction = scaladocTask(testLabel, testSources, testDocPath, docClasspath, documentOptions).dependsOn(testCompile) describedAs TestDocDescription protected def testAction = defaultTestTask(testOptions) @@ -246,7 +245,7 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec protected def defaultTestQuickMethod(failedOnly: Boolean) = testQuickMethod(testCompileConditional.analysis, testOptions)(options => defaultTestTask(quickOptions(failedOnly) ::: options.toList)) protected def defaultTestTask(testOptions: => Seq[TestOption]) = - testTask(testFrameworks, testClasspath, testCompileConditional.analysis, testOptions).dependsOn(testCompile) describedAs TestDescription + testTask(testFrameworks, testClasspath, testCompileConditional.analysis, testOptions).dependsOn(testCompile, copyResources, copyTestResources) describedAs TestDescription override def packageToPublishActions: Seq[ManagedTask] = `package` :: Nil @@ -263,6 +262,9 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec protected def incrementVersionAction = task { incrementVersionNumber(); None } describedAs IncrementVersionDescription protected def releaseAction = (test && packageAll && incrementVersion) describedAs ReleaseDescription + protected def copyResourcesAction = syncPathsTask(mainResources, mainResourcesOutputPath) describedAs CopyResourcesDescription + protected def copyTestResourcesAction = syncPathsTask(testResources, testResourcesOutputPath) describedAs CopyTestResourcesDescription + lazy val compile = compileAction lazy val testCompile = testCompileAction lazy val clean = cleanAction @@ -283,6 +285,8 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec lazy val graph = graphAction lazy val incrementVersion = incrementVersionAction lazy val release = releaseAction + lazy val copyResources = copyResourcesAction + lazy val copyTestResources = copyTestResourcesAction lazy val testQuick = testQuickAction lazy val testFailed = testFailedAction @@ -306,21 +310,39 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec override def watchPaths = mainSources +++ testSources +++ mainResources +++ testResources } abstract class BasicWebScalaProject extends BasicScalaProject with WebScalaProject with WebScalaPaths -{ +{ p => import BasicWebScalaProject._ override def watchPaths = super.watchPaths +++ webappResources lazy val prepareWebapp = prepareWebappAction protected def prepareWebappAction = - prepareWebappTask(webappResources, temporaryWarPath, webappClasspath, mainDependencies.scalaJars) dependsOn(compile) + prepareWebappTask(webappResources, temporaryWarPath, webappClasspath, mainDependencies.scalaJars) dependsOn(compile, copyResources) + private lazy val jettyInstance = new JettyRunner(jettyConfiguration) + + def jettyConfiguration = + new DefaultJettyConfiguration + { + def classpath = jettyRunClasspath + def jettyClasspath = p.jettyClasspath + def war = jettyWebappPath + def contextPath = jettyContextPath + def classpathName = "test" + def scanDirectories = p.scanDirectories.map(_.asFile) + def scanInterval = p.scanInterval + def port = jettyPort + def log = p.log + } + /** This is the classpath used to determine what classes, resources, and jars to put in the war file.*/ def webappClasspath = publicClasspath - def jettyRunClasspath = testClasspath + /** This is the classpath containing Jetty.*/ + def jettyClasspath = testClasspath --- jettyRunClasspath + /** This is the classpath containing the web application.*/ + def jettyRunClasspath = publicClasspath def jettyWebappPath = temporaryWarPath lazy val jettyRun = jettyRunAction lazy val jetty = task { idle() } dependsOn(jettyRun) describedAs(JettyDescription) - protected def jettyRunAction = - jettyRunTask(jettyWebappPath, jettyContextPath, jettyPort, jettyRunClasspath, "test", scanDirectories.map(_.asFile), scanInterval) dependsOn(prepareWebapp) describedAs(JettyRunDescription) + protected def jettyRunAction = jettyRunTask(jettyInstance) dependsOn(prepareWebapp) describedAs(JettyRunDescription) private def idle() = { log.info("Waiting... (press any key to interrupt)") @@ -338,13 +360,13 @@ abstract class BasicWebScalaProject extends BasicScalaProject with WebScalaProje /** The directories that should be watched to determine if the web application needs to be reloaded..*/ def scanDirectories: Seq[Path] = jettyWebappPath :: Nil /** The time in seconds between scans that check whether the web application should be reloaded.*/ - def scanInterval: Int = 3 + def scanInterval: Int = JettyRunner.DefaultScanInterval /** The port that Jetty runs on. */ - def jettyPort: Int = JettyRun.DefaultPort + def jettyPort: Int = JettyRunner.DefaultPort lazy val jettyRestart = jettyStop && jettyRun lazy val jettyStop = jettyStopAction - protected def jettyStopAction = jettyStopTask describedAs(JettyStopDescription) + protected def jettyStopAction = jettyStopTask(jettyInstance) describedAs(JettyStopDescription) /** The clean action for a web project is modified so that it first stops jetty if it is running, * since the webapp directory will be removed by the clean.*/ @@ -403,6 +425,10 @@ object BasicScalaProject "Increments the micro part of the version (the third number) by one. (This is only valid for versions of the form #.#.#-*)" val ReleaseDescription = "Compiles, tests, generates documentation, packages, and increments the version." + val CopyResourcesDescription = + "Copies resources to the target directory where they can be included on classpaths." + val CopyTestResourcesDescription = + "Copies test resources to the target directory where they can be included on the test classpath." private def warnMultipleMainClasses(log: Logger) = { diff --git a/src/main/scala/sbt/IntegrationTesting.scala b/src/main/scala/sbt/IntegrationTesting.scala index 2329e06ac..eaff08c9e 100644 --- a/src/main/scala/sbt/IntegrationTesting.scala +++ b/src/main/scala/sbt/IntegrationTesting.scala @@ -56,6 +56,14 @@ trait BasicIntegrationTesting extends ScalaIntegrationTesting with IntegrationTe def integrationTestFrameworks = testFrameworks override def useIntegrationTestConfiguration = false + abstract override def extraDefaultConfigurations = + { + val superConfigurations = super.extraDefaultConfigurations + if(useIntegrationTestConfiguration) + integrationTestConfiguration :: superConfigurations + else + superConfigurations + } abstract override def fullUnmanagedClasspath(config: Configuration) = { val superClasspath = super.fullUnmanagedClasspath(config) diff --git a/src/main/scala/sbt/Logger.scala b/src/main/scala/sbt/Logger.scala index 12b820808..9941f6b65 100644 --- a/src/main/scala/sbt/Logger.scala +++ b/src/main/scala/sbt/Logger.scala @@ -96,6 +96,34 @@ final class MultiLogger(delegates: List[Logger]) extends BasicLogger private def dispatch(event: LogEvent) { delegates.foreach(_.log(event)) } } +/** A filter logger is used to delegate messages but not the logging level to another logger. This means +* that messages are logged at the higher of the two levels set by this logger and its delegate. +* */ +final class FilterLogger(delegate: Logger) extends BasicLogger +{ + def trace(t: => Throwable) + { + if(traceEnabled) + delegate.trace(t) + } + def log(level: Level.Value, message: => String) + { + if(atLevel(level)) + delegate.log(level, message) + } + def success(message: => String) + { + if(atLevel(Level.Info)) + delegate.success(message) + } + def control(event: ControlEvent.Value, message: => String) + { + if(atLevel(Level.Info)) + delegate.control(event, message) + } + def logAll(events: Seq[LogEvent]): Unit = events.foreach(delegate.log) +} + /** A logger that can buffer the logging done on it by currently executing Thread and * then can flush the buffer to the delegate logger provided in the constructor. Use * 'startRecording' to start buffering and then 'play' from to flush the buffer for the @@ -110,78 +138,67 @@ final class MultiLogger(delegates: List[Logger]) extends BasicLogger final class BufferedLogger(delegate: Logger) extends Logger { private[this] val buffers = wrap.Wrappers.weakMap[Thread, Buffer[LogEvent]] - /* The recording depth part is to enable a weak nesting of recording calls. When recording is - * nested (recordingDepth >= 2), calls to play/playAll add the buffers for worker Threads to the - * serial buffer (main Thread) and calls to clear/clearAll clear worker Thread buffers only. */ - private[this] def recording = recordingDepth > 0 - private[this] var recordingDepth = 0 + private[this] var recordingAll = false - private[this] val mainThread = Thread.currentThread - private[this] def getBuffer(key: Thread) = buffers.getOrElseUpdate(key, new ListBuffer[LogEvent]) - private[this] def buffer = getBuffer(key) + private[this] def getOrCreateBuffer = buffers.getOrElseUpdate(key, createBuffer) + private[this] def buffer = if(recordingAll) Some(getOrCreateBuffer) else buffers.get(key) + private[this] def createBuffer = new ListBuffer[LogEvent] private[this] def key = Thread.currentThread - private[this] def serialBuffer = getBuffer(mainThread) - - private[this] def inWorker = Thread.currentThread ne mainThread - /** Enables buffering. */ - def startRecording() { synchronized { recordingDepth += 1 } } + @deprecated def startRecording() = recordAll() + /** Enables buffering for logging coming from the current Thread. */ + def record(): Unit = synchronized { buffers(key) = createBuffer } + /** Enables buffering for logging coming from all Threads. */ + def recordAll(): Unit = synchronized{ recordingAll = true } + def buffer[T](f: => T): T = + { + record() + try { f } + finally { Control.trap(stop()) } + } + def bufferAll[T](f: => T): T = + { + recordAll() + try { f } + finally { Control.trap(stopAll()) } + } + /** Flushes the buffer to the delegate logger for the current thread. This method calls logAll on the delegate * so that the messages are written consecutively. The buffer is cleared in the process. */ def play(): Unit = synchronized { - if(recordingDepth == 1) + for(buffer <- buffers.get(key)) delegate.logAll(wrap.Wrappers.readOnly(buffer)) - else if(recordingDepth > 1 && inWorker) - serialBuffer ++= buffer } def playAll(): Unit = synchronized { - if(recordingDepth == 1) - { - for(buffer <- buffers.values) - delegate.logAll(wrap.Wrappers.readOnly(buffer)) - } - else if(recordingDepth > 1) - { - for((key, buffer) <- buffers.toList if key ne mainThread) - serialBuffer ++= buffer - } + for(buffer <- buffers.values) + delegate.logAll(wrap.Wrappers.readOnly(buffer)) } - /** Clears buffered events for the current thread. It does not disable buffering. */ - def clear(): Unit = synchronized { if(recordingDepth == 1 || inWorker) buffers -= key } - /** Clears buffered events for all threads and disables buffering. */ + /** Clears buffered events for the current thread and disables buffering. */ + def clear(): Unit = synchronized { buffers -= key } + /** Clears buffered events for all threads and disables all buffering. */ + def clearAll(): Unit = synchronized { buffers.clear(); recordingAll = false } + /** Plays buffered events for the current thread and disables buffering. */ def stop(): Unit = synchronized { - clearAll() - if(recordingDepth > 0) - recordingDepth -= 1 + play() + clear() } - /** Clears buffered events for all threads. */ - def clearAll(): Unit = + def stopAll(): Unit = synchronized { - if(recordingDepth <= 1) - buffers.clear() - else - { - val serial = serialBuffer - buffers.clear() - buffers(mainThread) = serial - } + playAll() + clearAll() } - def runAndFlush[T](f: => T): T = - { - try { f } - finally { play(); clear() } - } def setLevel(newLevel: Level.Value): Unit = - synchronized { - if(recording) buffer += new SetLevel(newLevel) + synchronized + { + buffer.foreach{_ += new SetLevel(newLevel) } delegate.setLevel(newLevel) } def getLevel = synchronized { delegate.getLevel } @@ -189,58 +206,39 @@ final class BufferedLogger(delegate: Logger) extends Logger def enableTrace(flag: Boolean): Unit = synchronized { - if(recording) buffer += new SetTrace(flag) + buffer.foreach{_ += new SetTrace(flag) } delegate.enableTrace(flag) } def trace(t: => Throwable): Unit = - synchronized - { - if(traceEnabled) - { - if(recording) buffer += new Trace(t) - else delegate.trace(t) - } - } + doBufferableIf(traceEnabled, new Trace(t), _.trace(t)) def success(message: => String): Unit = - synchronized - { - if(atLevel(Level.Info)) - { - if(recording) - buffer += new Success(message) - else - delegate.success(message) - } - } + doBufferable(Level.Info, new Success(message), _.success(message)) def log(level: Level.Value, message: => String): Unit = - synchronized - { - if(atLevel(level)) - { - if(recording) - buffer += new Log(level, message) - else - delegate.log(level, message) - } - } + doBufferable(level, new Log(level, message), _.log(level, message)) def logAll(events: Seq[LogEvent]): Unit = synchronized { - if(recording) - buffer ++= events - else - delegate.logAll(events) + buffer match + { + case Some(b) => b ++= events + case None => delegate.logAll(events) + } } def control(event: ControlEvent.Value, message: => String): Unit = + doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message)) + private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit = + doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered) + private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit = synchronized { - if(atLevel(Level.Info)) + if(condition) { - if(recording) - buffer += new ControlEvent(event, message) - else - delegate.control(event, message) + buffer match + { + case Some(b) => b += appendIfBuffered + case None => doUnbuffered(delegate) + } } } } diff --git a/src/main/scala/sbt/Project.scala b/src/main/scala/sbt/Project.scala index fef74bf9a..62587ccd3 100644 --- a/src/main/scala/sbt/Project.scala +++ b/src/main/scala/sbt/Project.scala @@ -13,7 +13,13 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment { /** The logger for this project definition. */ final val log: Logger = logImpl - protected def logImpl: Logger = new BufferedLogger(new ConsoleLogger) + protected def logImpl: Logger = + { + val lg = new BufferedLogger(info.logger) + lg.setLevel(defaultLoggingLevel) + lg + } + protected def defaultLoggingLevel = Level.Info trait ActionOption extends NotNull @@ -166,7 +172,7 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment * The construct function is used to obtain the Project instance. Any project/build/ directory for the project * is ignored. The project is declared to have the dependencies given by deps.*/ def project[P <: Project](path: Path, name: String, construct: ProjectInfo => P, deps: Project*): P = - initialize(construct(ProjectInfo(path.asFile, deps, Some(this))), Some(new SetupInfo(name, None, None, false)), log) + initialize(construct(ProjectInfo(path.asFile, deps, Some(this))(log)), Some(new SetupInfo(name, None, None, false)), log) /** Initializes the project directories when a user has requested that sbt create a new project.*/ def initializeDirectories() {} @@ -186,14 +192,14 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment /** The list of directories to which this project writes. This is used to verify that multiple * projects have not been defined with the same output directories. */ - def outputDirectories: Iterable[Path] = outputRootPath :: Nil + def outputDirectories: Iterable[Path] = outputPath :: Nil def rootProject = Project.rootProject(this) /** The path to the file that provides persistence for properties.*/ final def envBackingPath = info.builderPath / Project.DefaultEnvBackingName /** The path to the file that provides persistence for history. */ def historyPath: Option[Path] = Some(outputRootPath / ".history") def outputPath = crossPath(outputRootPath) - def outputRootPath = outputDirectoryName + def outputRootPath: Path = outputDirectoryName def outputDirectoryName = DefaultOutputDirectoryName private def getProject(result: LoadResult, path: Path): Project = @@ -297,7 +303,7 @@ object Project loadProject(projectDirectory, deps, parent, getClass.getClassLoader, log) private[sbt] def loadProject(projectDirectory: File, deps: Iterable[Project], parent: Option[Project], additional: ClassLoader, log: Logger): LoadResult = { - val info = ProjectInfo(projectDirectory, deps, parent) + val info = ProjectInfo(projectDirectory, deps, parent)(log) ProjectInfo.setup(info, log) match { case err: SetupError => new LoadSetupError(err.message) @@ -310,12 +316,9 @@ object Project { try { - val oldLevel = log.getLevel - log.setLevel(Level.Warn) val result = for(builderClass <- getProjectDefinition(info, additional, log).right) yield initialize(constructProject(info, builderClass), setupInfo, log) - log.setLevel(oldLevel) result.fold(new LoadError(_), new LoadSuccess(_)) } catch @@ -381,7 +384,7 @@ object Project { val pluginProjectPath = info.builderPath / PluginProjectDirectoryName val additionalPaths = additional match { case u: URLClassLoader => u.getURLs.map(url => Path.fromFile(FileUtilities.toFile(url))); case _ => Nil } - val builderProject = new BuilderProject(ProjectInfo(builderProjectPath.asFile, Nil, None), pluginProjectPath, additionalPaths, buildLog) + val builderProject = new BuilderProject(ProjectInfo(builderProjectPath.asFile, Nil, None)(buildLog), pluginProjectPath, additionalPaths, buildLog) builderProject.compile.run.toLeft(()).right.flatMap { ignore => builderProject.projectDefinition.right.map { case Some(definition) => getProjectClass[Project](definition, builderProject.projectClasspath, additional) diff --git a/src/main/scala/sbt/ProjectInfo.scala b/src/main/scala/sbt/ProjectInfo.scala index b2e4fe349..316d3ff0f 100644 --- a/src/main/scala/sbt/ProjectInfo.scala +++ b/src/main/scala/sbt/ProjectInfo.scala @@ -6,8 +6,9 @@ package sbt import java.io.File import FileUtilities._ -final case class ProjectInfo(projectDirectory: File, dependencies: Iterable[Project], parent: Option[Project]) extends NotNull +final case class ProjectInfo(projectDirectory: File, dependencies: Iterable[Project], parent: Option[Project])(log: Logger) extends NotNull { + val logger = new FilterLogger(log) val projectPath: Path = { val toRoot = parent.flatMap(p => Path.relativize(p.info.projectPath, projectDirectory)) diff --git a/src/main/scala/sbt/ProjectPaths.scala b/src/main/scala/sbt/ProjectPaths.scala index 7a75faa40..2891571eb 100644 --- a/src/main/scala/sbt/ProjectPaths.scala +++ b/src/main/scala/sbt/ProjectPaths.scala @@ -26,9 +26,6 @@ trait ScalaPaths extends PackagePaths /** A PathFinder that selects all test resources. */ def testResources: PathFinder - def mainResourceClasspath: PathFinder - def testResourceClasspath: PathFinder - def mainCompilePath: Path def testCompilePath: Path def mainAnalysisPath: Path @@ -36,6 +33,8 @@ trait ScalaPaths extends PackagePaths def mainDocPath: Path def testDocPath: Path def graphPath: Path + def mainResourcesOutputPath: Path + def testResourcesOutputPath: Path /** A PathFinder that selects all the classes compiled from the main sources.*/ def mainClasses: PathFinder @@ -66,6 +65,7 @@ trait BasicScalaPaths extends Project with ScalaPaths { def mainResourcesPath: PathFinder def testResourcesPath: PathFinder + def managedDependencyPath: Path def managedDependencyRootPath: Path def dependencyPath: Path @@ -82,8 +82,6 @@ trait BasicScalaPaths extends Project with ScalaPaths } def testSources = sources(testSourceRoots) - def mainResourceClasspath = mainResourcesPath - def testResourceClasspath = testResourcesPath def mainResources = descendents(mainResourcesPath ##, "*") def testResources = descendents(testResourcesPath ##, "*") @@ -100,7 +98,7 @@ trait BasicScalaPaths extends Project with ScalaPaths info.bootPath +++ info.builderProjectOutputPath +++ info.pluginsOutputPath +++ info.pluginsManagedSourcePath +++ info.pluginsManagedDependencyPath - override def outputDirectories = outputRootPath :: managedDependencyRootPath :: Nil + override def outputDirectories = outputPath :: managedDependencyPath :: Nil } @deprecated trait BasicProjectPaths extends MavenStyleScalaPaths @@ -123,6 +121,8 @@ trait MavenStyleScalaPaths extends BasicScalaPaths with BasicPackagePaths def graphDirectoryName = DefaultGraphDirectoryName def mainAnalysisDirectoryName = DefaultMainAnalysisDirectoryName def testAnalysisDirectoryName = DefaultTestAnalysisDirectoryName + def mainResourcesOutputDirectoryName = DefautMainResourcesOutputDirectoryName + def testResourcesOutputDirectoryName = DefautTestResourcesOutputDirectoryName def sourcePath = path(sourceDirectoryName) @@ -132,6 +132,7 @@ trait MavenStyleScalaPaths extends BasicScalaPaths with BasicPackagePaths def mainResourcesPath = mainSourcePath / resourcesDirectoryName def mainDocPath = docPath / mainDirectoryName / apiDirectoryName def mainCompilePath = outputPath / mainCompileDirectoryName + def mainResourcesOutputPath = outputPath / mainResourcesOutputDirectoryName def mainAnalysisPath = outputPath / mainAnalysisDirectoryName def testSourcePath = sourcePath / testDirectoryName @@ -140,6 +141,7 @@ trait MavenStyleScalaPaths extends BasicScalaPaths with BasicPackagePaths def testResourcesPath = testSourcePath / resourcesDirectoryName def testDocPath = docPath / testDirectoryName / apiDirectoryName def testCompilePath = outputPath / testCompileDirectoryName + def testResourcesOutputPath = outputPath / testResourcesOutputDirectoryName def testAnalysisPath = outputPath / testAnalysisDirectoryName def docPath = outputPath / docDirectoryName @@ -183,6 +185,8 @@ object BasicProjectPaths val DefaultGraphDirectoryName = "graph" val DefaultMainAnalysisDirectoryName = "analysis" val DefaultTestAnalysisDirectoryName = "test-analysis" + val DefautMainResourcesOutputDirectoryName = "resources" + val DefautTestResourcesOutputDirectoryName = "test-resources" val DefaultMainDirectoryName = "main" val DefaultScalaDirectoryName = "scala" diff --git a/src/main/scala/sbt/Resources.scala b/src/main/scala/sbt/Resources.scala index 01b319a89..13bb8e620 100644 --- a/src/main/scala/sbt/Resources.scala +++ b/src/main/scala/sbt/Resources.scala @@ -79,21 +79,21 @@ class Resources(val baseDirectory: File, additional: ClassLoader) else { val buffered = new BufferedLogger(log) + buffered.setLevel(Level.Debug) + buffered.enableTrace(true) def error(msg: String) = { - buffered.playAll() - buffered.stop() + buffered.stopAll() Left(msg) } - - buffered.startRecording() + buffered.recordAll() resultToEither(Project.loadProject(dir, Nil, None, additional, buffered)) match { case Left(msg) => reload match { case ReloadErrorExpected => - buffered.stop() + buffered.clearAll() previousProject.toRight("Initial project load failed.") case s: ReloadSuccessExpected => error(s.prefixIfError + msg) case NoReload /* shouldn't happen */=> error(msg) @@ -103,7 +103,7 @@ class Resources(val baseDirectory: File, additional: ClassLoader) { case ReloadErrorExpected => error("Expected project load failure, but it succeeded.") case _ => - buffered.stop() + buffered.clearAll() Right(p) } } @@ -111,15 +111,13 @@ class Resources(val baseDirectory: File, additional: ClassLoader) loadResult match { case Right(project) => - project.log.enableTrace(log.traceEnabled) - project.log.setLevel(log.getLevel) f(project) match { case ContinueResult(newF, newReload) => withProject(log, Some(project), newReload, dir)(newF) case ValueResult(value) => Right(value) - case err: ErrorResult => Left(err.message) + case err: ErrorResult => error(err.message) } - case Left(message) => Left(message) + case Left(message) => error(message) } } diff --git a/src/main/scala/sbt/ScalaProject.scala b/src/main/scala/sbt/ScalaProject.scala index c104b3c37..5fa96e7b3 100644 --- a/src/main/scala/sbt/ScalaProject.scala +++ b/src/main/scala/sbt/ScalaProject.scala @@ -143,6 +143,8 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje } } + def syncPathsTask(sources: PathFinder, destinationDirectory: Path): Task = + task { FileUtilities.syncPaths(sources, destinationDirectory, log) } def syncTask(sourceDirectory: Path, destinationDirectory: Path): Task = task { FileUtilities.sync(sourceDirectory, destinationDirectory, log) } def copyTask(sources: PathFinder, destinationDirectory: Path): Task = @@ -323,14 +325,8 @@ trait WebScalaProject extends ScalaProject } }}}}).left.toOption } - def jettyRunTask(warPath: => Path, defaultContextPath: => String, port: Int, classpath: PathFinder, classpathName: String, scanDirectories: Seq[File], scanInterval: Int): Task = - task { JettyRun(classpath.get, classpathName, warPath, defaultContextPath, port, scanDirectories, scanInterval, log) } - def jettyRunTask(warPath: => Path, defaultContextPath: => String, classpath: PathFinder, classpathName: String, scanDirectories: Seq[File], scanInterval: Int): Task = - jettyRunTask(warPath, defaultContextPath, JettyRun.DefaultPort, classpath, classpathName, scanDirectories, scanInterval) - def jettyRunTask(warPath: => Path, defaultContextPath: => String, classpath: PathFinder, classpathName: String, - jettyConfigurationXML: scala.xml.NodeSeq, jettyConfigurationFiles: Seq[File]): Task = - task { JettyRun(classpath.get, classpathName, warPath, defaultContextPath, jettyConfigurationXML, jettyConfigurationFiles, log) } - def jettyStopTask = task { JettyRun.stop(); None } + def jettyRunTask(jettyRun: JettyRunner) = task { jettyRun() } + def jettyStopTask(jettyRun: JettyRunner) = task { jettyRun.stop(); None } } object ScalaProject { diff --git a/src/main/scala/sbt/WebApp.scala b/src/main/scala/sbt/WebApp.scala index 37ffff6b9..16d47f9de 100644 --- a/src/main/scala/sbt/WebApp.scala +++ b/src/main/scala/sbt/WebApp.scala @@ -7,10 +7,13 @@ import java.io.File import java.net.{URL, URLClassLoader} import scala.xml.NodeSeq -object JettyRun extends ExitHook +object JettyRunner { val DefaultPort = 8080 - + val DefaultScanInterval = 3 +} +class JettyRunner(configuration: JettyConfiguration) extends ExitHook +{ ExitHooks.register(this) def name = "jetty-shutdown" @@ -19,49 +22,38 @@ object JettyRun extends ExitHook private def started(s: Stoppable) { running = Some(s) } def stop() { - synchronized + running.foreach(_.stop()) + running = None + } + def apply(): Option[String] = + { + import configuration._ + def runJetty() = { - running.foreach(_.stop()) - running = None + val baseLoader = this.getClass.getClassLoader + val classpathURLs = jettyClasspath.get.map(_.asURL).toSeq + val loader: ClassLoader = new java.net.URLClassLoader(classpathURLs.toArray, baseLoader) + val lazyLoader = new LazyFrameworkLoader(implClassName, Array(FileUtilities.sbtJar.toURI.toURL), loader, baseLoader) + val runner = ModuleUtilities.getObject(implClassName, lazyLoader).asInstanceOf[JettyRun] + runner(configuration) + } + + if(running.isDefined) + Some("This instance of Jetty is already running.") + else + { + try + { + started(runJetty()) + None + } + catch + { + case e: NoClassDefFoundError => runError(e, "Jetty and its dependencies must be on the " + classpathName + " classpath: ", log) + case e => runError(e, "Error running Jetty: ", log) + } } } - def apply(classpath: Iterable[Path], classpathName: String, war: Path, defaultContextPath: String, jettyConfigurationXML: NodeSeq, - jettyConfigurationFiles: Seq[File], log: Logger): Option[String] = - run(classpathName, new JettyRunConfiguration(war, defaultContextPath, DefaultPort, jettyConfigurationXML, - jettyConfigurationFiles, Nil, 0, toURLs(classpath)), log) - def apply(classpath: Iterable[Path], classpathName: String, war: Path, defaultContextPath: String, port: Int, scanDirectories: Seq[File], - scanPeriod: Int, log: Logger): Option[String] = - run(classpathName, new JettyRunConfiguration(war, defaultContextPath, port, NodeSeq.Empty, Nil, scanDirectories, scanPeriod, toURLs(classpath)), log) - private def toURLs(paths: Iterable[Path]) = paths.map(_.asURL).toSeq - private def run(classpathName: String, configuration: JettyRunConfiguration, log: Logger): Option[String] = - synchronized - { - import configuration._ - def runJetty() = - { - val baseLoader = this.getClass.getClassLoader - val loader: ClassLoader = new java.net.URLClassLoader(classpathURLs.toArray, baseLoader) - val lazyLoader = new LazyFrameworkLoader(implClassName, Array(FileUtilities.sbtJar.toURI.toURL), loader, baseLoader) - val runner = ModuleUtilities.getObject(implClassName, lazyLoader).asInstanceOf[JettyRun] - runner(configuration, log) - } - - if(running.isDefined) - Some("Jetty is already running.") - else - { - try - { - started(runJetty()) - None - } - catch - { - case e: NoClassDefFoundError => runError(e, "Jetty and its dependencies must be on the " + classpathName + " classpath: ", log) - case e => runError(e, "Error running Jetty: ", log) - } - } - } private val implClassName = "sbt.LazyJettyRun" private def runError(e: Throwable, messageBase: String, log: Logger) = @@ -77,11 +69,30 @@ private trait Stoppable } private trait JettyRun { - def apply(configuration: JettyRunConfiguration, log: Logger): Stoppable + def apply(configuration: JettyConfiguration): Stoppable +} +sealed trait JettyConfiguration extends NotNull +{ + def war: Path + def scanDirectories: Seq[File] + def scanInterval: Int + /** The classpath to get Jetty from. */ + def jettyClasspath: PathFinder + /** The classpath containing the classes, jars, and resources for the web application. */ + def classpath: PathFinder + def classpathName: String + def log: Logger +} +trait DefaultJettyConfiguration extends JettyConfiguration +{ + def contextPath: String + def port: Int +} +abstract class CustomJettyConfiguration extends JettyConfiguration +{ + def jettyConfigurationFiles: Seq[File] = Nil + def jettyConfigurationXML: NodeSeq = NodeSeq.Empty } -private class JettyRunConfiguration(val war: Path, val defaultContextPath: String, val port: Int, - val jettyConfigurationXML: NodeSeq, val jettyConfigurationFiles: Seq[File], - val scanDirectories: Seq[File], val scanInterval: Int, val classpathURLs: Seq[URL]) extends NotNull /* This class starts Jetty. * NOTE: DO NOT actively use this class. You will see NoClassDefFoundErrors if you fail @@ -102,51 +113,51 @@ private object LazyJettyRun extends JettyRun val DefaultMaxIdleTime = 30000 - def apply(configuration: JettyRunConfiguration, log: Logger): Stoppable = + def apply(configuration: JettyConfiguration): Stoppable = { - import configuration._ val oldLog = Log.getLog - Log.setLog(new JettyLogger(log)) + Log.setLog(new JettyLogger(configuration.log)) val server = new Server - val useDefaults = jettyConfigurationXML.isEmpty && jettyConfigurationFiles.isEmpty val listener = - if(useDefaults) + configuration match { - configureDefaultConnector(server, port) - def createLoader = new URLClassLoader(classpathURLs.toArray, this.getClass.getClassLoader) - val webapp = new WebAppContext(war.absolutePath, defaultContextPath) - webapp.setClassLoader(createLoader) - server.setHandler(webapp) - - Some(new Scanner.BulkListener { - def filesChanged(files: java.util.List[_]) { - reload(server, webapp.setClassLoader(createLoader), log) - } - }) - } - else - { - for(x <- jettyConfigurationXML) - (new XmlConfiguration(x.toString)).configure(server) - for(file <- jettyConfigurationFiles) - (new XmlConfiguration(file.toURI.toURL)).configure(server) - None + case c: DefaultJettyConfiguration => + import c._ + configureDefaultConnector(server, port) + def classpathURLs = classpath.get.map(_.asURL).toSeq + def createLoader = new URLClassLoader(classpathURLs.toArray, this.getClass.getClassLoader) + val webapp = new WebAppContext(war.absolutePath, contextPath) + webapp.setClassLoader(createLoader) + server.setHandler(webapp) + + Some(new Scanner.BulkListener { + def filesChanged(files: java.util.List[_]) { + reload(server, webapp.setClassLoader(createLoader), log) + } + }) + case c: CustomJettyConfiguration => + for(x <- c.jettyConfigurationXML) + (new XmlConfiguration(x.toString)).configure(server) + for(file <- c.jettyConfigurationFiles) + (new XmlConfiguration(file.toURI.toURL)).configure(server) + None } def configureScanner() = { + val scanDirectories = configuration.scanDirectories if(listener.isEmpty || scanDirectories.isEmpty) None else { - log.debug("Scanning for changes to: " + scanDirectories.mkString(", ")) + configuration.log.debug("Scanning for changes to: " + scanDirectories.mkString(", ")) val scanner = new Scanner val list = new java.util.ArrayList[File] scanDirectories.foreach(x => list.add(x)) scanner.setScanDirs(list) scanner.setRecursive(true) - scanner.setScanInterval(scanInterval) + scanner.setScanInterval(configuration.scanInterval) scanner.setReportExistingFilesOnStartup(false) scanner.addListener(listener.get) scanner.start() @@ -186,18 +197,15 @@ private object LazyJettyRun extends JettyRun } private def reload(server: Server, reconfigure: => Unit, log: Logger) { - JettyRun.synchronized - { - log.info("Reloading web application...") - val handlers = wrapNull(server.getHandlers, server.getHandler) - log.debug("Stopping handlers: " + handlers.mkString(", ")) - handlers.foreach(_.stop) - log.debug("Reconfiguring...") - reconfigure - log.debug("Restarting handlers: " + handlers.mkString(", ")) - handlers.foreach(_.start) - log.info("Reload complete.") - } + log.info("Reloading web application...") + val handlers = wrapNull(server.getHandlers, server.getHandler) + log.debug("Stopping handlers: " + handlers.mkString(", ")) + handlers.foreach(_.stop) + log.debug("Reconfiguring...") + reconfigure + log.debug("Restarting handlers: " + handlers.mkString(", ")) + handlers.foreach(_.start) + log.info("Reload complete.") } private def wrapNull(a: Array[Handler], b: Handler) = (a, b) match diff --git a/src/main/scala/sbt/impl/ProcessImpl.scala b/src/main/scala/sbt/impl/ProcessImpl.scala index ab8157a4a..325cabf15 100644 --- a/src/main/scala/sbt/impl/ProcessImpl.scala +++ b/src/main/scala/sbt/impl/ProcessImpl.scala @@ -105,9 +105,7 @@ private abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPa private[this] def runBuffered(log: Logger, connectInput: Boolean) = { val log2 = new BufferedLogger(log) - log2.startRecording() - try { run(log2, connectInput).exitValue() } - finally { log2.playAll(); log2.clearAll() } + log2.bufferAll { run(log2, connectInput).exitValue() } } def !(io: ProcessIO) = run(io).exitValue() diff --git a/src/main/scala/sbt/impl/RunTask.scala b/src/main/scala/sbt/impl/RunTask.scala index 8b87e4143..5ce5a6da4 100644 --- a/src/main/scala/sbt/impl/RunTask.scala +++ b/src/main/scala/sbt/impl/RunTask.scala @@ -2,6 +2,7 @@ * Copyright 2009 Mark Harrah */ package sbt.impl + import sbt._ import scala.collection.{immutable, mutable} import scala.collection.Map @@ -44,11 +45,7 @@ private final class RunTask(root: Task, rootName: String, maximumTasks: Int) ext // it ignores the root task so that the root task may be run with buffering disabled so that the output // occurs without delay. private def runTasksExceptRoot() = - { - withBuffered(_.startRecording()) - try { ParallelRunner.run(expandedRoot, expandedTaskName, runIfNotRoot, maximumTasks, (t: Task) => t.manager.log) } - finally { withBuffered(_.stop()) } - } + ParallelRunner.run(expandedRoot, expandedTaskName, runIfNotRoot, maximumTasks, (t: Task) => t.manager.log) private def withBuffered(f: BufferedLogger => Unit) { for(buffered <- bufferedLoggers) @@ -69,38 +66,29 @@ private final class RunTask(root: Task, rootName: String, maximumTasks: Int) ext val label = if(multiProject) (action.manager.name + " / " + actionName) else actionName def banner(event: ControlEvent.Value, firstSeparator: String, secondSeparator: String) = Control.trap(action.manager.log.control(event, firstSeparator + " " + label + " " + secondSeparator)) - if(parallel) + val buffered = parallel && !isRoot(action) + if(buffered) + banner(ControlEvent.Start, "\n ", "...") + def doRun() = { - try { banner(ControlEvent.Start, "\n ", "...") } - finally { flush(action) } + banner(ControlEvent.Header, "\n==", "==") + try { action.invoke } + catch { case e: Exception => action.manager.log.trace(e); Some(e.toString) } + finally { banner(ControlEvent.Finish, "==", "==") } } - banner(ControlEvent.Header, "\n==", "==") - try { action.invoke } - catch { case e: Exception => action.manager.log.trace(e); Some(e.toString) } - finally + + if(buffered) + bufferLogging(action, doRun()) + else + doRun() + } + private def bufferLogging[T](action: Task, f: => T) = + bufferedLogger(action.manager) match { - banner(ControlEvent.Finish, "==", "==") - if(parallel) - flush(action) + case Some(buffered) => buffered.buffer { f } + case None => f } - } - private def trapFinally(toTrap: => Unit)(runFinally: => Unit) - { - try { toTrap } - catch { case e: Exception => () } - finally { try { runFinally } catch { case e: Exception => () } } - } - private def flush(action: Task) - { - for(buffered <- bufferedLogger(action.manager)) - Control.trap(flush(buffered)) - } - private def flush(buffered: BufferedLogger) - { - buffered.play() - buffered.clear() - } - + /* Most of the following is for implicitly adding dependencies (see the expand method)*/ private val projectDependencyCache = identityMap[Project, Iterable[Project]] private def dependencies(project: Project) = projectDependencyCache.getOrElseUpdate(project, project.topologicalSort.dropRight(1))