diff --git a/LICENSE b/LICENSE index be586c877..6d1f89b26 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2008, 2009 Steven Blundy, Mark Harrah, David MacIver, Mikko Peltonen +Copyright (c) 2008, 2009, 2010 Steven Blundy, Josh Cough, Nathan Hamblen, Mark Harrah, David MacIver, Mikko Peltonen, Tony Sloane, Vesa Vilhonen All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/NOTICE b/NOTICE index 07766ee52..1c90f88f5 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,6 @@ Simple Build Tool (sbt) -Copyright 2008, 2009 Steven Blundy, Mark Harrah, David MacIver, Mikko Peltonen +Copyright 2008, 2009, 2010 Steven Blundy, Josh Cough, Nathan Hamblen, Mark Harrah, David MacIver, Mikko Peltonen, Tony Sloane, Vesa Vilhonen +Licensed under BSD-style license (see LICENSE) Portions based on code by Mike Clark in JDepend @@ -14,30 +15,12 @@ Portions based on code from the Scala compiler Copyright 2002-2008 EPFL, Lausanne Licensed under BSD-style license (see licenses/LICENSE_Scala) -Portions based on code from specs -Copyright 2007-2008 Eric Torreborre -Licensed under MIT license (see licenses/LICENSE_specs) - -Portions based on code from ScalaTest -Copyright 2001-2008 Artima, Inc. -Licensed under the Apache License, Version 2.0(see licenses/LICENSE_Apache) - -Portions based on code from ScalaCheck -Copyright 2007, Rickard Nilsson -Licensed under BSD-style license (see licenses/LICENSE_ScalaCheck) - -Jetty is licensed under the Apache License, Version 2.0 (see licenses/LICENSE_Apache). - -ScalaTest is distributed with sbt (in the subversion repository) -and requires the following notice: - - This product includes software developed by - Artima, Inc. (http://www.artima.com/). - +JLine is distributed with the sbt launcher. +It is licensed under a BSD-style license (see licenses/LICENSE_JLine) Apache Ivy, licensed under the Apache License, Version 2.0 -(see licenses/LICENSE_Apache) is distributed with sbt and -requires the following notice: +(see licenses/LICENSE_Apache) is distributed with the sbt launcher. +It requires the following notice: This product includes software developed by The Apache Software Foundation (http://www.apache.org/). diff --git a/licenses/LICENSE_sbt b/licenses/LICENSE_sbt index 1b82fe7ca..b28d901b0 100644 --- a/licenses/LICENSE_sbt +++ b/licenses/LICENSE_sbt @@ -1,4 +1,4 @@ -Copyright (c) 2008 Mark Harrah, David MacIver +Copyright (c) 2008, 2009, 2010 Steven Blundy, Josh Cough, Nathan Hamblen, Mark Harrah, David MacIver, Mikko Peltonen, Tony Sloane, Vesa Vilhonen All rights reserved. Redistribution and use in source and binary forms, with or without @@ -22,4 +22,4 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + diff --git a/src/main/scala/sbt/Logger.scala b/src/main/scala/sbt/Logger.scala index b4d9de0d3..86e014125 100644 --- a/src/main/scala/sbt/Logger.scala +++ b/src/main/scala/sbt/Logger.scala @@ -10,7 +10,7 @@ final class Success(val msg: String) extends LogEvent final class Log(val level: Level.Value, val msg: String) extends LogEvent final class Trace(val exception: Throwable) extends LogEvent final class SetLevel(val newLevel: Level.Value) extends LogEvent -final class SetTrace(val enabled: Boolean) extends LogEvent +final class SetTrace(val level: Int) extends LogEvent final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent object ControlEvent extends Enumeration @@ -22,8 +22,9 @@ abstract class Logger extends xsbt.CompileLogger with xsbt.IvyLogger { def getLevel: Level.Value def setLevel(newLevel: Level.Value) - def enableTrace(flag: Boolean) - def traceEnabled: Boolean + def setTrace(flag: Int) + def getTrace: Int + final def traceEnabled = getTrace >= 0 def ansiCodesSupported = false def atLevel(level: Level.Value) = level.id >= getLevel.id @@ -47,7 +48,7 @@ abstract class Logger extends xsbt.CompileLogger with xsbt.IvyLogger case l: Log => log(l.level, l.msg) case t: Trace => trace(t.exception) case setL: SetLevel => setLevel(setL.newLevel) - case setT: SetTrace => enableTrace(setT.enabled) + case setT: SetTrace => setTrace(setT.level) case c: ControlEvent => control(c.event, c.msg) } } @@ -64,12 +65,12 @@ abstract class Logger extends xsbt.CompileLogger with xsbt.IvyLogger /** Implements the level-setting methods of Logger.*/ abstract class BasicLogger extends Logger { - private var traceEnabledVar = true + private var traceEnabledVar = java.lang.Integer.MAX_VALUE private var level: Level.Value = Level.Info def getLevel = level def setLevel(newLevel: Level.Value) { level = newLevel } - def enableTrace(flag: Boolean) { traceEnabledVar = flag } - def traceEnabled = traceEnabledVar + def setTrace(level: Int) { traceEnabledVar = level } + def getTrace = traceEnabledVar } final class SynchronizedLogger(delegate: Logger) extends Logger @@ -77,8 +78,8 @@ final class SynchronizedLogger(delegate: Logger) extends Logger override lazy val ansiCodesSupported = delegate.ansiCodesSupported def getLevel = { synchronized { delegate.getLevel } } def setLevel(newLevel: Level.Value) { synchronized { delegate.setLevel(newLevel) } } - def enableTrace(enabled: Boolean) { synchronized { delegate.enableTrace(enabled) } } - def traceEnabled: Boolean = { synchronized { delegate.traceEnabled } } + def setTrace(level: Int) { synchronized { delegate.setTrace(level) } } + def getTrace: Int = { synchronized { delegate.getTrace } } def trace(t: => Throwable) { synchronized { delegate.trace(t) } } def log(level: Level.Value, message: => String) { synchronized { delegate.log(level, message) } } @@ -95,10 +96,10 @@ final class MultiLogger(delegates: List[Logger]) extends BasicLogger super.setLevel(newLevel) dispatch(new SetLevel(newLevel)) } - override def enableTrace(enabled: Boolean) + override def setTrace(level: Int) { - super.enableTrace(enabled) - dispatch(new SetTrace(enabled)) + super.setTrace(level) + dispatch(new SetTrace(level)) } def trace(t: => Throwable) { dispatch(new Trace(t)) } def log(level: Level.Value, message: => String) { dispatch(new Log(level, message)) } @@ -119,6 +120,8 @@ final class FilterLogger(delegate: Logger) extends BasicLogger if(traceEnabled) delegate.trace(t) } + override def setTrace(level: Int) { delegate.setTrace(level) } + override def getTrace = delegate.getTrace def log(level: Level.Value, message: => String) { if(atLevel(level)) @@ -216,12 +219,12 @@ final class BufferedLogger(delegate: Logger) extends Logger delegate.setLevel(newLevel) } def getLevel = synchronized { delegate.getLevel } - def traceEnabled = synchronized { delegate.traceEnabled } - def enableTrace(flag: Boolean): Unit = + def getTrace = synchronized { delegate.getTrace } + def setTrace(level: Int): Unit = synchronized { - buffer.foreach{_ += new SetTrace(flag) } - delegate.enableTrace(flag) + buffer.foreach{_ += new SetTrace(level) } + delegate.setTrace(level) } def trace(t: => Throwable): Unit = @@ -295,8 +298,9 @@ class ConsoleLogger extends BasicLogger def trace(t: => Throwable): Unit = System.out.synchronized { - if(traceEnabled) - t.printStackTrace + val traceLevel = getTrace + if(traceLevel >= 0) + System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) } } def log(level: Level.Value, message: => String) { diff --git a/src/main/scala/sbt/Main.scala b/src/main/scala/sbt/Main.scala index 8578429d8..b16851136 100755 --- a/src/main/scala/sbt/Main.scala +++ b/src/main/scala/sbt/Main.scala @@ -29,6 +29,7 @@ object Main val UsageErrorExitCode = 4 val BuildErrorExitCode = 5 val ProgramErrorExitCode = 6 + val MaxInt = java.lang.Integer.MAX_VALUE } import Main._ @@ -385,6 +386,8 @@ class xMain extends xsbti.AppMain setProperty(currentProject, action.substring(SetAction.length + 1)) else if(action.startsWith(GetAction + " ")) getProperty(currentProject, action.substring(GetAction.length + 1)) + else if(action.startsWith(TraceCommand + " ")) + setTrace(currentProject, action.substring(TraceCommand.length + 1)) else handleCommand(currentProject, action) } @@ -432,6 +435,7 @@ class xMain extends xsbti.AppMain case GetAction => getArgumentError(project.log) case SetAction => setArgumentError(project.log) case ProjectAction => setProjectError(project.log) + case TraceCommand => setTraceError(project.log); true case ShowCurrent => printProject("Current project is ", project) Console.println("Current Scala version is " + project.buildScalaVersion) @@ -439,7 +443,6 @@ class xMain extends xsbti.AppMain printTraceEnabled(project) true case ShowActions => showActions(project); true - case TraceCommand => toggleTrace(project); true case Level(level) => setLevel(project, level); true case ContinuousCompileCommand => compileContinuously(project) case action if action.startsWith(ContinuousExecutePrefix) => executeContinuously(project, action.substring(ContinuousExecutePrefix.length).trim) @@ -493,15 +496,21 @@ class xMain extends xsbti.AppMain } /** Toggles whether stack traces are enabled.*/ - private def toggleTrace(project: Project) + private def setTrace(project: Project, value: String): Boolean = { - val newValue = !project.log.traceEnabled - project.projectClosure.foreach(_.log.enableTrace(newValue)) - printTraceEnabled(project) + try + { + val newValue = if(value == "on") MaxInt else if(value == "off") -1 else if(value == "nosbt") 0 else value.toInt + project.projectClosure.foreach(_.log.setTrace(newValue)) + printTraceEnabled(project) + true + } + catch { case _: NumberFormatException => setTraceError(project.log) } } private def printTraceEnabled(project: Project) { - Console.println("Stack traces are " + (if(project.log.traceEnabled) "enabled" else "disabled")) + def traceLevel(level: Int) = if(level == 0) " (no stack elements)" else if(level == MaxInt) "" else " (maximum " + level + " stack elements per exception)" + Console.println("Stack traces are " + (if(project.log.traceEnabled) "enabled" + traceLevel(project.log.getTrace) else "disabled")) } /** Sets the logging level on the given project.*/ private def setLevel(project: Project, level: Level.Value) @@ -688,6 +697,7 @@ class xMain extends xsbti.AppMain } private def isTerminateAction(s: String) = TerminateActions.elements.contains(s.toLowerCase) + private def setTraceError(log: Logger) = logError(log)("Invalid arguments for 'trace': expected 'on', 'off', or integer number of stack elements to show per exception.") private def setArgumentError(log: Logger) = logError(log)("Invalid arguments for 'set': expected property name and new value.") private def getArgumentError(log: Logger) = logError(log)("Invalid arguments for 'get': expected property name.") private def setProjectError(log: Logger) = logError(log)("Invalid arguments for 'project': expected project name.") diff --git a/src/main/scala/sbt/Project.scala b/src/main/scala/sbt/Project.scala index 53f67a754..ca3dda3de 100644 --- a/src/main/scala/sbt/Project.scala +++ b/src/main/scala/sbt/Project.scala @@ -311,7 +311,6 @@ object Project { val log = new ConsoleLogger log.setLevel(Level.Debug) - log.enableTrace(true) log } diff --git a/src/main/scala/sbt/Resources.scala b/src/main/scala/sbt/Resources.scala index 1aa906358..28668ddae 100644 --- a/src/main/scala/sbt/Resources.scala +++ b/src/main/scala/sbt/Resources.scala @@ -82,7 +82,6 @@ class Resources(val baseDirectory: File, additional: ClassLoader, app: AppProvid { val buffered = new BufferedLogger(log) buffered.setLevel(Level.Debug) - buffered.enableTrace(true) def error(msg: String) = { buffered.stopAll() diff --git a/src/main/scala/sbt/StackTrace.scala b/src/main/scala/sbt/StackTrace.scala new file mode 100644 index 000000000..1ecd6e8bf --- /dev/null +++ b/src/main/scala/sbt/StackTrace.scala @@ -0,0 +1,63 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Tony Sloane + */ +package sbt + +object StackTrace +{ + def isSbtClass(name: String) = name.startsWith("sbt") || name.startsWith("xsbt") + /** + * Return a printable representation of the stack trace associated + * with t. Information about t and its Throwable causes is included. + * The number of lines to be included for each Throwable is configured + * via d which should be greater than or equal to zero. If d is zero, + * then all elements are included up to (but not including) the first + * element that comes from sbt. If d is greater than zero, then up to + * that many lines are included, where the line for the Throwable is + * counted plus one line for each stack element. Less lines will be + * included if there are not enough stack elements. + */ + def trimmed(t : Throwable, d : Int) : String = { + require(d >= 0) + val b = new StringBuilder () + + def appendStackTrace (t : Throwable, first : Boolean) { + + val include : StackTraceElement => Boolean = + if (d == 0) + element => !isSbtClass(element.getClassName) + else { + var count = d - 1 + (_ => { count -= 1; count >= 0 }) + } + + def appendElement (e : StackTraceElement) { + b.append ("\tat ") + b.append (e) + b.append ('\n') + } + + if (!first) + b.append ("Caused by: ") + b.append (t) + b.append ('\n') + + val els = t.getStackTrace () + var i = 0 + while ((i < els.size) && include (els (i))) { + appendElement (els (i)) + i += 1 + } + + } + + appendStackTrace (t, true) + var c = t + while (c.getCause () != null) { + c = c.getCause () + appendStackTrace (c, false) + } + b.toString () + + } +} \ No newline at end of file diff --git a/src/main/scala/sbt/WebApp.scala b/src/main/scala/sbt/WebApp.scala index b1d8404dc..54292a652 100644 --- a/src/main/scala/sbt/WebApp.scala +++ b/src/main/scala/sbt/WebApp.scala @@ -1,5 +1,5 @@ /* sbt -- Simple Build Tool - * Copyright 2008 Mark Harrah + * Copyright 2008, 2009, 2010 Mark Harrah */ package sbt @@ -82,9 +82,6 @@ private trait JettyRun } sealed trait JettyConfiguration extends NotNull { - def war: Path - def scanDirectories: Seq[File] - def scanInterval: Int /** The classpath to get Jetty from. */ def jettyClasspath: PathFinder def classpathName: String @@ -92,6 +89,10 @@ sealed trait JettyConfiguration extends NotNull } trait DefaultJettyConfiguration extends JettyConfiguration { + def war: Path + def scanDirectories: Seq[File] + def scanInterval: Int + def contextPath: String def port: Int /** The classpath containing the classes, jars, and resources for the web application. */ @@ -129,7 +130,27 @@ private object LazyJettyRun extends JettyRun Log.setLog(new JettyLogger(configuration.log)) val server = new Server - val listener = + def configureScanner(listener: Scanner.BulkListener, scanDirectories: Seq[File], scanInterval: Int) = + { + if(scanDirectories.isEmpty) + None + else + { + 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.setReportExistingFilesOnStartup(false) + scanner.addListener(listener) + scanner.start() + Some(new WeakReference(scanner)) + } + } + + val (listener, scanner) = configuration match { case c: DefaultJettyConfiguration => @@ -144,43 +165,23 @@ private object LazyJettyRun extends JettyRun setLoader() server.setHandler(webapp) - Some(new Scanner.BulkListener with Reload { + val listener = new Scanner.BulkListener with Reload { def reloadApp() = reload(server, setLoader(), log) def filesChanged(files: java.util.List[_]) { reloadApp() } - }) + } + (Some(listener), configureScanner(listener, c.scanDirectories, c.scanInterval)) 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 + (None, None) } - def configureScanner() = - { - val scanDirectories = configuration.scanDirectories - if(listener.isEmpty || scanDirectories.isEmpty) - None - else - { - 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(configuration.scanInterval) - scanner.setReportExistingFilesOnStartup(false) - scanner.addListener(listener.get) - scanner.start() - Some(new WeakReference(scanner)) - } - } - try { server.start() - new StopServer(new WeakReference(server), listener.map(new WeakReference(_)), configureScanner(), oldLog) + new StopServer(new WeakReference(server), listener.map(new WeakReference(_)), scanner, oldLog) } catch { case e => server.stop(); throw e } } diff --git a/src/sbt-test/run/console-project/changes/BlankProject.scala b/src/sbt-test/run/console-project/changes/BlankProject.scala new file mode 100644 index 000000000..b18e9f09b --- /dev/null +++ b/src/sbt-test/run/console-project/changes/BlankProject.scala @@ -0,0 +1,3 @@ +import sbt._ + +class BlankProject(info: ProjectInfo) extends DefaultProject(info) \ No newline at end of file