From cd8c8ea0079b087fa8f864c98382b558ecd74f5e Mon Sep 17 00:00:00 2001 From: dmharrah Date: Sun, 2 Aug 2009 01:26:18 +0000 Subject: [PATCH] * Many logging related changes and fixes. Added FilterLogger and cleaned up interaction between Logger, scripted testing, and the builder projects. This included removing the recordingDepth hack from Logger. Logger buffering is now enabled/disabled per thread. * Fix compileOptions being fixed after the first compile * Minor fixes to output directory checking * Added defaultLoggingLevel method for setting the initial level of a project's Logger * Cleaned up internal approach to adding extra default configurations like 'plugin' * Added syncPathsTask for synchronizing paths to a target directory * Allow multiple instances of Jetty (new jettyRunTasks can be defined with different ports) * jettyRunTask accepts configuration in a single configuration wrapper object instead of many parameters * Fix web application class loading (#35) by using jettyClasspath=testClasspath---jettyRunClasspath for loading Jetty. A better way would be to have a 'jetty' configuration and have jettyClasspath=managedClasspath('jetty'), but this maintains compatibility. * Copy resources to target/resources and target/test-resources using copyResources and copyTestResources tasks. Properly include all resources in web applications and classpaths (#36). mainResources and testResources are now the definitive methods for getting resources. git-svn-id: https://simple-build-tool.googlecode.com/svn/trunk@936 d89573ee-9141-11dd-94d4-bdf5e562f29c --- src/main/scala/sbt/AnalysisCallback.scala | 8 +- src/main/scala/sbt/AutoCompilerPlugins.scala | 18 +- src/main/scala/sbt/BasicProjectTypes.scala | 18 +- src/main/scala/sbt/BuilderProject.scala | 14 +- src/main/scala/sbt/Conditional.scala | 7 +- src/main/scala/sbt/DefaultProject.scala | 58 ++++-- src/main/scala/sbt/IntegrationTesting.scala | 8 + src/main/scala/sbt/Logger.scala | 172 +++++++++--------- src/main/scala/sbt/Project.scala | 21 ++- src/main/scala/sbt/ProjectInfo.scala | 3 +- src/main/scala/sbt/ProjectPaths.scala | 16 +- src/main/scala/sbt/Resources.scala | 18 +- src/main/scala/sbt/ScalaProject.scala | 12 +- src/main/scala/sbt/WebApp.scala | 176 ++++++++++--------- src/main/scala/sbt/impl/ProcessImpl.scala | 4 +- src/main/scala/sbt/impl/RunTask.scala | 54 +++--- 16 files changed, 308 insertions(+), 299 deletions(-) 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))