From 8bfb2802fb9ae20bff078785deca636c718f116c Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 5 Sep 2009 12:19:34 -0400 Subject: [PATCH] Filling in logging and making cross-compile work. --- compile/CompileLogger.scala | 10 +++ compile/Compiler.scala | 48 ++++++++--- compile/CompilerArguments.scala | 22 +++++ compile/ComponentCompiler.scala | 3 +- compile/interface/CompilerInterface.scala | 31 +++++-- compile/interface/Message.scala | 4 +- .../src/test/scala/TestCompile.scala | 5 +- compile/src/test/scala/CompileTest.scala | 14 +-- .../src/main/java/xsbti/CompileFailed.java | 6 ++ interface/src/test/scala/TestLogger.scala | 38 -------- ivy/src/test/scala/TestIvyLogger.scala | 11 ++- launch/Launch.scala | 3 +- project/build/XSbt.scala | 27 ++++-- tasks/standard/Compile.scala | 43 ++++++---- tasks/standard/src/test/scala/SyncTest.scala | 14 +-- util/classpath/DualLoader.scala | 9 +- util/log/BasicLogger.scala | 15 ++++ util/log/BufferedLogger.scala | 86 +++++++++++++++++++ util/log/ConsoleLogger.scala | 76 ++++++++++++++++ util/log/Level.scala | 25 ++++++ util/log/LogEvent.scala | 17 ++++ util/log/Logger.scala | 48 +++-------- 22 files changed, 404 insertions(+), 151 deletions(-) create mode 100644 compile/CompileLogger.scala create mode 100644 compile/CompilerArguments.scala create mode 100644 interface/src/main/java/xsbti/CompileFailed.java delete mode 100644 interface/src/test/scala/TestLogger.scala create mode 100644 util/log/BasicLogger.scala create mode 100644 util/log/BufferedLogger.scala create mode 100644 util/log/ConsoleLogger.scala create mode 100644 util/log/Level.scala create mode 100644 util/log/LogEvent.scala diff --git a/compile/CompileLogger.scala b/compile/CompileLogger.scala new file mode 100644 index 000000000..d07381232 --- /dev/null +++ b/compile/CompileLogger.scala @@ -0,0 +1,10 @@ +package xsbt + +trait CompileLogger extends xsbti.Logger with NotNull +{ + def info(msg: => String) + def debug(msg: => String) + def warn(msg: => String) + def error(msg: => String) + def trace(t: => Throwable) +} diff --git a/compile/Compiler.scala b/compile/Compiler.scala index 8ebf63af0..588b6b8a5 100644 --- a/compile/Compiler.scala +++ b/compile/Compiler.scala @@ -1,6 +1,6 @@ package xsbt -import xsbti.{AnalysisCallback, Logger} +import xsbti.{AnalysisCallback, Logger => xLogger} import java.io.File import java.net.URLClassLoader @@ -12,38 +12,52 @@ import java.net.URLClassLoader /** A basic interface to the compiler. It is called in the same virtual machine, but no dependency analysis is done. This * is used, for example, to compile the interface/plugin code.*/ -class RawCompiler(scalaLoader: ClassLoader) +class RawCompiler(scalaLoader: ClassLoader, scalaLibDirectory: File, log: CompileLogger) { - def apply(arguments: Seq[String]) + lazy val scalaVersion = Class.forName("scala.tools.nsc.Properties", true, scalaLoader).getMethod("versionString").invoke(null) + def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String]): Unit = + apply(sources, classpath, outputDirectory, options, false) + def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean) { // reflection is required for binary compatibility - // The following import ensures there is a compile error if the class name changes, - // but it should not be otherwise directly referenced + // The following imports ensure there is a compile error if the identifiers change, + // but should not be otherwise directly referenced import scala.tools.nsc.Main + import scala.tools.nsc.Properties + val arguments = CompilerArguments(scalaLibDirectory)(sources, classpath, outputDirectory, options, compilerOnClasspath) + log.debug("Vanilla interface to Scala compiler " + scalaVersion + " with arguments: " + arguments.mkString("\n\t", "\n\t", "")) val mainClass = Class.forName("scala.tools.nsc.Main", true, scalaLoader) val process = mainClass.getMethod("process", classOf[Array[String]]) val realArray: Array[String] = arguments.toArray assert(realArray.getClass eq classOf[Array[String]]) process.invoke(null, realArray) + checkForFailure(mainClass, arguments.toArray) + } + protected def checkForFailure(mainClass: Class[_], args: Array[String]) + { + val reporter = mainClass.getMethod("reporter").invoke(null) + val failed = reporter.asInstanceOf[{ def hasErrors: Boolean }].hasErrors + if(failed) throw new xsbti.CompileFailed { val arguments = args; override def toString = "Vanilla compile failed" } } } /** Interface to the compiler that uses the dependency analysis plugin.*/ -class AnalyzeCompiler(scalaVersion: String, scalaLoader: ClassLoader, manager: ComponentManager) extends NotNull +class AnalyzeCompiler(scalaVersion: String, scalaLoader: ClassLoader, scalaLibDirectory: File, manager: ComponentManager) extends NotNull { - def this(scalaVersion: String, provider: xsbti.ScalaProvider, manager: ComponentManager) = - this(scalaVersion, provider.getScalaLoader(scalaVersion), manager) - /** The jar containing the compiled plugin and the compiler interface code. This will be passed to scalac as a compiler plugin - * and used to load the class that actually interfaces with Global.*/ - def apply(arguments: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger) + def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger): Unit = + apply(sources, classpath, outputDirectory, options, false, callback, maximumErrors, log) + def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean, + callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger) { + val arguments = CompilerArguments(scalaLibDirectory)(sources, classpath, outputDirectory, options, compilerOnClasspath) // this is the instance used to compile the analysis - val componentCompiler = new ComponentCompiler(scalaVersion, new RawCompiler(scalaLoader), manager) + val componentCompiler = new ComponentCompiler(scalaVersion, new RawCompiler(scalaLoader, scalaLibDirectory, log), manager) + log.debug("Getting " + ComponentCompiler.compilerInterfaceID + " from component compiler for Scala " + scalaVersion + " (loader=" + scalaLoader + ")") val interfaceJar = componentCompiler(ComponentCompiler.compilerInterfaceID) val dual = createDualLoader(scalaLoader, getClass.getClassLoader) // this goes to scalaLoader for scala classes and sbtLoader for xsbti classes val interfaceLoader = new URLClassLoader(Array(interfaceJar.toURI.toURL), dual) - val interface = Class.forName("xsbt.CompilerInterface", true, interfaceLoader).newInstance - val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger): Unit }] + val interface = Class.forName("xsbt.CompilerInterface", true, interfaceLoader).newInstance.asInstanceOf[AnyRef] + val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: xLogger): Unit }] // these arguments are safe to pass across the ClassLoader boundary because the types are defined in Java // so they will be binary compatible across all versions of Scala runnable.run(arguments.toArray, callback, maximumErrors, log) @@ -54,4 +68,10 @@ class AnalyzeCompiler(scalaVersion: String, scalaLoader: ClassLoader, manager: C val notXsbtiFilter = (name: String) => !xsbtiFilter(name) new DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false) } + override def toString = "Analyzing compiler (Scala " + scalaVersion + ")" +} +object AnalyzeCompiler +{ + def apply(scalaVersion: String, provider: xsbti.ScalaProvider, manager: ComponentManager): AnalyzeCompiler = + new AnalyzeCompiler(scalaVersion, provider.getScalaLoader(scalaVersion), provider.getScalaHome(scalaVersion), manager) } \ No newline at end of file diff --git a/compile/CompilerArguments.scala b/compile/CompilerArguments.scala new file mode 100644 index 000000000..38d524a00 --- /dev/null +++ b/compile/CompilerArguments.scala @@ -0,0 +1,22 @@ +package xsbt + + import java.io.File + +object CompilerArguments +{ + def apply(scalaLibDirectory: File)(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean): Seq[String] = + { + val scalaHome = System.getProperty("scala.home") + assert((scalaHome eq null) || scalaHome.isEmpty, "'scala.home' should not be set (was " + scalaHome + ")") + def abs(files: Set[File]) = files.map(_.getAbsolutePath) + val originalBoot = System.getProperty("sun.boot.class.path", "") + val newBootPrefix = if(originalBoot.isEmpty) "" else originalBoot + File.pathSeparator + val bootClasspathOption = Seq("-bootclasspath", newBootPrefix + scalaLibraryJar(scalaLibDirectory).getAbsolutePath) + val cp2 = classpath ++ (if(compilerOnClasspath) scalaCompilerJar(scalaLibDirectory):: Nil else Nil) + val classpathOption = Seq("-cp", abs(cp2).mkString(File.pathSeparator) ) + val outputOption = Seq("-d", outputDirectory.getAbsolutePath) + options ++ outputOption ++ bootClasspathOption ++ classpathOption ++ abs(sources) + } + private def scalaLibraryJar(scalaLibDirectory: File): File = new File(scalaLibDirectory, "scala-library.jar") + private def scalaCompilerJar(scalaLibDirectory: File): File = new File(scalaLibDirectory, "scala-compiler.jar") +} \ No newline at end of file diff --git a/compile/ComponentCompiler.scala b/compile/ComponentCompiler.scala index c9d8cbadb..f2b15b889 100644 --- a/compile/ComponentCompiler.scala +++ b/compile/ComponentCompiler.scala @@ -41,8 +41,7 @@ class ComponentCompiler(scalaVersion: String, compiler: RawCompiler, manager: Co val (sourceFiles, resources) = extractedSources.partition(_.getName.endsWith(".scala")) withTemporaryDirectory { outputDirectory => val xsbtiJars = manager.files(xsbtiID) - val arguments = Seq("-d", outputDirectory.getAbsolutePath, "-cp", xsbtiJars.mkString(File.pathSeparator)) ++ sourceFiles.toSeq.map(_.getAbsolutePath) - compiler(arguments) + compiler(Set() ++ sourceFiles, Set() ++ xsbtiJars, outputDirectory, Nil, true) copy(resources x (FileMapper.rebase(dir, outputDirectory))) zip((outputDirectory ***) x (PathMapper.relativeTo(outputDirectory)), targetJar) } diff --git a/compile/interface/CompilerInterface.scala b/compile/interface/CompilerInterface.scala index 736f00865..e5bd4c143 100644 --- a/compile/interface/CompilerInterface.scala +++ b/compile/interface/CompilerInterface.scala @@ -10,12 +10,17 @@ class CompilerInterface { def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger) { - import scala.tools.nsc.{CompilerCommand, FatalError, Global, Settings, reporters, util} - import util.FakePos + def debug(msg: => String) = log.debug(Message(msg)) + import scala.tools.nsc.{CompilerCommand, FatalError, Global, Settings, reporters, util} + import util.FakePos + + debug("Interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString) + val reporter = new LoggerReporter(maximumErrors, log) val settings = new Settings(reporter.error) val command = new CompilerCommand(args.toList, settings, error, false) + val phasesSet = new scala.collection.mutable.HashSet[Any] // 2.7 compatibility object compiler extends Global(command.settings, reporter) { object sbtAnalyzer extends @@ -23,27 +28,35 @@ class CompilerInterface val global: compiler.type = compiler val phaseName = Analyzer.name val runsAfter = List("jvm") + override val runsBefore = List("terminal") val runsRightAfter = None } - with SubComponent + with SubComponent with Compat27 { val analyzer = new Analyzer(global, callback) def newPhase(prev: Phase) = analyzer.newPhase(prev) def name = phaseName } - override protected def builtInPhaseDescriptors() = (super.builtInPhaseDescriptors ++ Seq(sbtAnalyzer)) - /*override protected def computeInternalPhases() + lazy val pdescriptors = // done this way for compatibility between 2.7 and 2.8 { - super.computeInternalPhases() phasesSet += sbtAnalyzer - }*/ + val superd = super.phaseDescriptors + if(superd.contains(sbtAnalyzer)) superd else ( super.phaseDescriptors ++ Seq(sbtAnalyzer) ).toList + } + override def phaseDescriptors = pdescriptors + trait Compat27 { val runsBefore: List[String] = Nil } } if(!reporter.hasErrors) { val run = new compiler.Run + debug(args.mkString("Calling compiler with arguments (CompilerInterface):\n\t", "\n\t", "")) run compile command.files - reporter.printSummary() } - !reporter.hasErrors + reporter.printSummary() + if(reporter.hasErrors) + { + debug("Compilation failed (CompilerInterface)") + throw new xsbti.CompileFailed { val arguments = args; override def toString = "Analyzed compilation failed" } + } } } \ No newline at end of file diff --git a/compile/interface/Message.scala b/compile/interface/Message.scala index f83cb2094..b3bc4330e 100644 --- a/compile/interface/Message.scala +++ b/compile/interface/Message.scala @@ -3,9 +3,7 @@ */ package xsbt -import xsbti.F0 - object Message { - def apply(s: => String) = new F0[String] { def apply() = s } + def apply(s: => String) = new xsbti.F0[String] { def apply() = s } } \ No newline at end of file diff --git a/compile/interface/src/test/scala/TestCompile.scala b/compile/interface/src/test/scala/TestCompile.scala index 1f08b36ae..66ff1c747 100644 --- a/compile/interface/src/test/scala/TestCompile.scala +++ b/compile/interface/src/test/scala/TestCompile.scala @@ -2,7 +2,7 @@ package xsbt import java.io.File import java.net.URLClassLoader -import xsbti.{Logger, TestCallback, TestLogger} +import xsbti.TestCallback import FileUtilities.withTemporaryDirectory object TestCompile @@ -13,7 +13,8 @@ object TestCompile { val testCallback = new TestCallback(superclassNames.toArray) val i = new CompilerInterface - TestLogger { log => + val log = new BufferedLogger(new ConsoleLogger) + log.bufferQuietly { i.run(arguments.toArray, testCallback, 5, log) f(testCallback, log) } diff --git a/compile/src/test/scala/CompileTest.scala b/compile/src/test/scala/CompileTest.scala index c1629488b..eb4d59de0 100644 --- a/compile/src/test/scala/CompileTest.scala +++ b/compile/src/test/scala/CompileTest.scala @@ -13,13 +13,12 @@ object CompileTest extends Specification WithCompiler( "2.7.2" )(testCompileAnalysis) } } - private def testCompileAnalysis(compiler: AnalyzeCompiler, log: xsbti.Logger) + private def testCompileAnalysis(compiler: AnalyzeCompiler, log: CompileLogger) { WithFiles( new File("Test.scala") -> "object Test" ) { sources => withTemporaryDirectory { temp => - val arguments = "-d" :: temp.getAbsolutePath :: sources.map(_.getAbsolutePath).toList val callback = new xsbti.TestCallback(Array()) - compiler(arguments, callback, 10, log) + compiler(Set() ++ sources, Set.empty, temp, Nil, callback, 10, log) (callback.beganSources) must haveTheSameElementsAs(sources) } } @@ -27,16 +26,19 @@ object CompileTest extends Specification } object WithCompiler { - def apply[T](scalaVersion: String)(f: (AnalyzeCompiler, xsbti.Logger) => T): T = + def apply[T](scalaVersion: String)(f: (AnalyzeCompiler, CompileLogger) => T): T = { - TestIvyLogger { log => + System.setProperty("scala.home", "") // need to make sure scala.home is unset + val log = new TestIvyLogger with CompileLogger + log.setLevel(Level.Debug) + log.bufferQuietly { FileUtilities.withTemporaryDirectory { temp => val launch = new xsbt.boot.Launch(temp) val sbtVersion = xsbti.Versions.Sbt val manager = new ComponentManager(launch.getSbtHome(sbtVersion, scalaVersion), log) prepare(manager, ComponentCompiler.compilerInterfaceSrcID, "CompilerInterface.scala") prepare(manager, ComponentCompiler.xsbtiID, classOf[xsbti.AnalysisCallback]) - f(new AnalyzeCompiler(scalaVersion, launch, manager), log) + f(AnalyzeCompiler(scalaVersion, launch, manager), log) } } } diff --git a/interface/src/main/java/xsbti/CompileFailed.java b/interface/src/main/java/xsbti/CompileFailed.java new file mode 100644 index 000000000..bb5b2a93a --- /dev/null +++ b/interface/src/main/java/xsbti/CompileFailed.java @@ -0,0 +1,6 @@ +package xsbti; + +public abstract class CompileFailed extends RuntimeException +{ + public abstract String[] arguments(); +} \ No newline at end of file diff --git a/interface/src/test/scala/TestLogger.scala b/interface/src/test/scala/TestLogger.scala deleted file mode 100644 index c8df588f6..000000000 --- a/interface/src/test/scala/TestLogger.scala +++ /dev/null @@ -1,38 +0,0 @@ -package xsbti - -class TestLogger extends Logger -{ - private val buffer = new scala.collection.mutable.ArrayBuffer[F0[Unit]] - def info(msg: F0[String]) = buffer("[info] ", msg) - def warn(msg: F0[String]) = buffer("[warn] ", msg) - def debug(msg: F0[String]) = buffer("[debug] ", msg) - def error(msg: F0[String]) = buffer("[error] ", msg) - def verbose(msg: F0[String]) = buffer("[verbose] ", msg) - def info(msg: => String) = buffer("[info] ", msg) - def warn(msg: => String) = buffer("[warn] ", msg) - def debug(msg: => String) = buffer("[debug] ", msg) - def error(msg: => String) = buffer("[error] ", msg) - def verbose(msg: => String) = buffer("[verbose] ", msg) - def show() { buffer.foreach(_()) } - def clear() { buffer.clear() } - def trace(t: F0[Throwable]) { buffer += f0(t().printStackTrace) } - private def buffer(s: String, msg: F0[String]) { buffer(s, msg()) } - private def buffer(s: String, msg: => String) { buffer += f0(println(s + msg)) } -} -object TestLogger -{ - def apply[T](f: Logger => T): T = - { - val log = new TestLogger - try { f(log) } - catch { case e: Exception => log.show(); throw e } - finally { log.clear() } - } - def apply[L <: TestLogger, T](newLogger: => L)(f: L => T): T = - { - val log = newLogger - try { f(log) } - catch { case e: Exception => log.show(); throw e } - finally { log.clear() } - } -} \ No newline at end of file diff --git a/ivy/src/test/scala/TestIvyLogger.scala b/ivy/src/test/scala/TestIvyLogger.scala index 5ed7716d2..9d987b806 100644 --- a/ivy/src/test/scala/TestIvyLogger.scala +++ b/ivy/src/test/scala/TestIvyLogger.scala @@ -1,9 +1,12 @@ package xsbt -import xsbti.TestLogger - -class TestIvyLogger extends TestLogger with IvyLogger +class TestIvyLogger extends BufferedLogger(new ConsoleLogger) with IvyLogger { def verbose(msg: => String) = info(msg) } object TestIvyLogger { - def apply[T](f: TestIvyLogger => T): T = TestLogger(new TestIvyLogger)(f) + def apply[T](f: IvyLogger => T): T = + { + val log = new TestIvyLogger + log.setLevel(Level.Debug) + log.bufferQuietly(f(log)) + } } \ No newline at end of file diff --git a/launch/Launch.scala b/launch/Launch.scala index f7335f0cc..cdaea303b 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -26,6 +26,7 @@ class Launch(projectRootDirectory: File, mainClassName: String) extends Launcher import Launch._ final def boot(args: Array[String]) { + System.setProperty("scala.home", "") // avoid errors from mixing Scala versions in the same JVM checkAndLoad(args) match { case e: Exit => System.exit(e.code) @@ -74,7 +75,7 @@ class Launch(projectRootDirectory: File, mainClassName: String) extends Launcher val scalaLoader = getScalaLoader(scalaVersion) createSbtLoader(sbtVersion, scalaVersion, scalaLoader) } - + def run(sbtLoader: ClassLoader, mainClassName: String, configuration: SbtConfiguration): MainResult = { val sbtMain = Class.forName(mainClassName, true, sbtLoader) diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 5a4f54608..fc00e466a 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -14,10 +14,10 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) val ioSub = project(utilPath / "io", "IO", new IOProject(_), controlSub) val classpathSub = project(utilPath / "classpath", "Classpath", new Base(_)) - val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface Src", new CompilerInterfaceProject(_), interfaceSub) - val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub) - val logSub = project(utilPath / "log", "Logging", new Base(_)) + val logSub = project(utilPath / "log", "Logging", new Base(_), interfaceSub) + + val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface Src", new CompilerInterfaceProject(_), interfaceSub) val taskSub = project(tasksPath, "Tasks", new TaskProject(_), controlSub, collectionSub) val cacheSub = project(cachePath, "Cache", new CacheProject(_), taskSub, ioSub) @@ -58,7 +58,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) } class StandardTaskProject(info: ProjectInfo) extends Base(info) { - override def testClasspath = super.testClasspath +++ compilerSub.testClasspath + override def testClasspath = super.testClasspath +++ compilerSub.testClasspath --- compilerInterfaceClasspath } class IOProject(info: ProjectInfo) extends Base(info) with TestDependencies @@ -73,31 +73,40 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) override def scratch = true override def consoleClasspath = testClasspath } - class CompileProject(info: ProjectInfo) extends Base(info) + class CompileProject(info: ProjectInfo) extends Base(info) with TestWithLog { override def testCompileAction = super.testCompileAction dependsOn(launchSub.testCompile, compileInterfaceSub.`package`, interfaceSub.`package`) // don't include launch interface in published dependencies because it will be provided by launcher override def deliverProjectDependencies = Set(super.deliverProjectDependencies.toSeq : _*) - launchInterfaceSub.projectID - override def testClasspath = super.testClasspath +++ launchSub.testClasspath +++ compileInterfaceSub.jarPath +++ interfaceSub.jarPath + override def testClasspath = super.testClasspath +++ launchSub.testClasspath +++ compileInterfaceSub.jarPath +++ interfaceSub.jarPath --- compilerInterfaceClasspath override def compileOptions = super.compileOptions ++ Seq(CompileOption("-Xno-varargs-conversion")) //needed for invoking nsc.scala.tools.Main.process(Array[String]) } - class IvyProject(info: ProjectInfo) extends Base(info) with TestWithIO + class IvyProject(info: ProjectInfo) extends Base(info) with TestWithIO with TestWithLog { val ivy = "org.apache.ivy" % "ivy" % "2.0.0" } - class InterfaceProject(info: ProjectInfo) extends DefaultProject(info) with ManagedBase + class InterfaceProject(info: ProjectInfo) extends DefaultProject(info) with ManagedBase with TestWithLog { // ensure that interfaces are only Java sources and that they cannot reference Scala classes override def mainSources = descendents(mainSourceRoots, "*.java") override def compileOrder = CompileOrder.JavaThenScala } - class CompilerInterfaceProject(info: ProjectInfo) extends Base(info) with SourceProject with TestWithIO + class CompilerInterfaceProject(info: ProjectInfo) extends Base(info) with SourceProject with TestWithIO with TestWithLog + { + def xTestClasspath = projectClasspath(Configurations.Test) + } trait TestWithIO extends BasicScalaProject { // use IO from tests override def testCompileAction = super.testCompileAction dependsOn(ioSub.testCompile) override def testClasspath = super.testClasspath +++ ioSub.testClasspath } + trait TestWithLog extends BasicScalaProject + { + override def testCompileAction = super.testCompileAction dependsOn(logSub.compile) + override def testClasspath = super.testClasspath +++ logSub.compileClasspath + } + def compilerInterfaceClasspath = compileInterfaceSub.projectClasspath(Configurations.Test) } trait SourceProject extends BasicScalaProject diff --git a/tasks/standard/Compile.scala b/tasks/standard/Compile.scala index 8f573496e..09f6e64b2 100644 --- a/tasks/standard/Compile.scala +++ b/tasks/standard/Compile.scala @@ -6,17 +6,19 @@ trait Compile extends TrackedTaskDefinition[CompileReport] { val sources: Task[Set[File]] val classpath: Task[Set[File]] + val outputDirectory: Task[File] val options: Task[Seq[String]] - + val trackedClasspath = Difference.inputs(classpath, FilesInfo.lastModified, cacheFile("classpath")) val trackedSource = Difference.inputs(sources, FilesInfo.hash, cacheFile("sources")) val trackedOptions = { import Cache._ - new Changed(options.map(_.toList), cacheFile("options")) + import Task._ + new Changed((outputDirectory, options) map ( "-d" :: _.getAbsolutePath :: _.toList), cacheFile("options")) } val invalidation = InvalidateFiles(cacheFile("dependencies/")) - + lazy val task = create def create = trackedClasspath { rawClasspathChanges => // detect changes to the classpath (last modified only) @@ -26,40 +28,38 @@ trait Compile extends TrackedTaskDefinition[CompileReport] trackedOptions(newOpts, sameOpts) bind { // detect changes to options case (options, sourceChanges, classpathChanges) => invalidation( classpathChanges +++ sourceChanges ) { (report, tracking) => // invalidation based on changes - compile(sourceChanges, classpathChanges, options, report, tracking) + outputDirectory bind { outDir => compile(sourceChanges, classpathChanges, outDir, options, report, tracking) } } } } - } dependsOn(sources, options)// raise these dependencies to the top for parallelism - - def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport] + } dependsOn(sources, options, outputDirectory)// raise these dependencies to the top for parallelism + + def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], outputDirectory: File, options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport] lazy val tracked = getTracked protected def getTracked = Seq(trackedClasspath, trackedSource, trackedOptions, invalidation) } -class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File]], val options: Task[Seq[String]], - val superclassNames: Task[Set[String]], val compilerTask: Task[AnalyzeCompiler], val cacheDirectory: File, val log: xsbti.Logger) extends Compile +class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File]], val outputDirectory: Task[File], val options: Task[Seq[String]], + val superclassNames: Task[Set[String]], val compilerTask: Task[AnalyzeCompiler], val cacheDirectory: File, val log: CompileLogger) extends Compile { import Task._ import scala.collection.mutable.{ArrayBuffer, Buffer, HashMap, HashSet, Map, Set => mSet} - + override def create = super.create dependsOn(superclassNames, compilerTask) // raise these dependencies to the top for parallelism - def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport] = + def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], outputDirectory: File, + options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport] = { val sources = report.invalid ** sourceChanges.checked // determine the sources that need recompiling (report.invalid also contains classes and libraries) val classpath = classpathChanges.checked - compile(sources, classpath, options, tracking) + compile(sources, classpath, outputDirectory, options, tracking) } - def compile(sources: Set[File], classpath: Set[File], options: Seq[String], tracking: UpdateTracking[File]): Task[CompileReport] = + def compile(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], tracking: UpdateTracking[File]): Task[CompileReport] = { (compilerTask, superclassNames) map { (compiler, superClasses) => if(!sources.isEmpty) { val callback = new CompileAnalysisCallback(superClasses.toArray, tracking) - val classpathString = abs(classpath).mkString(File.pathSeparator) - val classpathOption = if(classpathString.isEmpty) Seq.empty else Seq("-cp", classpathString) - val arguments = classpathOption ++ options ++ abs(sources).toSeq - - compiler(arguments, callback, 100, log) + log.debug("Compile task calling compiler " + compiler) + compiler(sources, classpath, outputDirectory, options, callback, 100, log) } val readTracking = tracking.read val applicationSet = new HashSet[String] @@ -71,6 +71,13 @@ class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File def subclasses(superclass: String) = Set() ++ subclassMap.getOrElse(superclass, Nil) val applications = Set() ++ applicationSet val classes = Set() ++ readTracking.allProducts + override def toString = + { + val superStrings = superclasses.map(superC => superC + " >: \n\t\t" + subclasses(superC).mkString("\n\t\t")) + val applicationsPart = if(applications.isEmpty) Nil else Seq("Applications") ++ applications + val lines = Seq("Compilation Report:", sources.size + " sources", classes.size + " classes") ++ superStrings + lines.mkString("\n\t") + } } } } diff --git a/tasks/standard/src/test/scala/SyncTest.scala b/tasks/standard/src/test/scala/SyncTest.scala index 811057d79..e51c53fe4 100644 --- a/tasks/standard/src/test/scala/SyncTest.scala +++ b/tasks/standard/src/test/scala/SyncTest.scala @@ -30,7 +30,7 @@ object SyncTest } object CompileTest { - def apply(dir: String, scalaVersion: String, opts: Seq[String], supers: Set[String]) + def apply(dir: String, scalaVersion: String, options: Seq[String], supers: Set[String]) { def test() { @@ -41,12 +41,12 @@ object CompileTest val classpath = Task( dir / "lib" * "*.jar" ) WithCompiler(scalaVersion) { (compiler, log) => temp { cacheDir => temp { outDir => - val options = Task(opts ++ Seq("-d", outDir.getAbsolutePath) ) - val compile = new StandardCompile(sources, classpath, options, Task(supers), Task(compiler), cacheDir, log) - TaskRunner(compile.task) - readLine("Press enter to continue...") - TaskRunner(compile.task) - readLine("Press enter to continue...") + val compile = new StandardCompile(sources, classpath, Task(outDir), Task(options), Task(supers), Task(compiler), cacheDir, log) + println("Result: " + TaskRunner(compile.task)) + println("Result: " + TaskRunner(compile.task)) + TaskRunner(compile.clean) + println("Result: " + TaskRunner(compile.task)) + println("Result: " + TaskRunner(compile.task)) } } } } diff --git a/util/classpath/DualLoader.scala b/util/classpath/DualLoader.scala index ca679045a..7a05cbf60 100644 --- a/util/classpath/DualLoader.scala +++ b/util/classpath/DualLoader.scala @@ -3,9 +3,16 @@ package xsbt import java.net.URL import java.util.Enumeration +final class NullLoader extends ClassLoader +{ + override final def loadClass(className: String, resolve: Boolean): Class[_] = throw new ClassNotFoundException("No classes can be loaded from the null loader") + override def getResource(name: String): URL = null + override def getResources(name: String): Enumeration[URL] = null +} + class DifferentLoaders(message: String, val loaderA: ClassLoader, val loaderB: ClassLoader) extends ClassNotFoundException(message) class DualLoader(parentA: ClassLoader, aOnlyClasses: String => Boolean, aOnlyResources: String => Boolean, - parentB: ClassLoader, bOnlyClasses: String => Boolean, bOnlyResources: String => Boolean) extends ClassLoader + parentB: ClassLoader, bOnlyClasses: String => Boolean, bOnlyResources: String => Boolean) extends ClassLoader(new NullLoader) { def this(parentA: ClassLoader, aOnly: String => Boolean, parentB: ClassLoader, bOnly: String => Boolean) = this(parentA, aOnly, aOnly, parentB, bOnly, bOnly) diff --git a/util/log/BasicLogger.scala b/util/log/BasicLogger.scala new file mode 100644 index 000000000..23607c8ed --- /dev/null +++ b/util/log/BasicLogger.scala @@ -0,0 +1,15 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ + package xsbt + +/** Implements the level-setting methods of Logger.*/ +abstract class BasicLogger extends Logger +{ + private var traceEnabledVar = true + 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 +} diff --git a/util/log/BufferedLogger.scala b/util/log/BufferedLogger.scala new file mode 100644 index 000000000..7e8348f2d --- /dev/null +++ b/util/log/BufferedLogger.scala @@ -0,0 +1,86 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ + package xsbt + + import scala.collection.mutable.ListBuffer + +/** A logger that can buffer the logging done on it 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 to the backing logger. +* The logging level set at the time a message is originally logged is used, not +* the level at the time 'play' is called. +* +* This class assumes that it is the only client of the delegate logger. +* */ +class BufferedLogger(delegate: Logger) extends Logger +{ + private[this] val buffer = new ListBuffer[LogEvent] + private[this] var recording = false + + /** Enables buffering. */ + def record() = { recording = true } + def buffer[T](f: => T): T = + { + record() + try { f } + finally { stopQuietly() } + } + def bufferQuietly[T](f: => T): T = + { + record() + try + { + val result = f + clear() + result + } + catch { case e => stopQuietly(); throw e } + } + private def stopQuietly() = try { stop() } catch { case e: Exception => () } + + /** Flushes the buffer to the delegate logger. This method calls logAll on the delegate + * so that the messages are written consecutively. The buffer is cleared in the process. */ + def play() { delegate.logAll(buffer.readOnly); buffer.clear() } + /** Clears buffered events and disables buffering. */ + def clear(): Unit = { buffer.clear(); recording = false } + /** Plays buffered events and disables buffering. */ + def stop() { play(); clear() } + + def setLevel(newLevel: Level.Value) + { + buffer += new SetLevel(newLevel) + delegate.setLevel(newLevel) + } + def getLevel = delegate.getLevel + def traceEnabled = delegate.traceEnabled + def enableTrace(flag: Boolean) + { + buffer += new SetTrace(flag) + delegate.enableTrace(flag) + } + + def trace(t: => Throwable): Unit = + doBufferableIf(traceEnabled, new Trace(t), _.trace(t)) + def success(message: => String): Unit = + doBufferable(Level.Info, new Success(message), _.success(message)) + def log(level: Level.Value, message: => String): Unit = + doBufferable(level, new Log(level, message), _.log(level, message)) + def logAll(events: Seq[LogEvent]): Unit = + if(recording) + buffer ++= events + else + 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 = + if(condition) + { + if(recording) + buffer += appendIfBuffered + else + doUnbuffered(delegate) + } +} \ No newline at end of file diff --git a/util/log/ConsoleLogger.scala b/util/log/ConsoleLogger.scala new file mode 100644 index 000000000..a7217f026 --- /dev/null +++ b/util/log/ConsoleLogger.scala @@ -0,0 +1,76 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ + package xsbt + +object ConsoleLogger +{ + private val formatEnabled = ansiSupported && !formatExplicitlyDisabled + + private[this] def formatExplicitlyDisabled = java.lang.Boolean.getBoolean("sbt.log.noformat") + private[this] def ansiSupported = + try { jline.Terminal.getTerminal.isANSISupported } + catch { case e: Exception => !isWindows } + + private[this] def os = System.getProperty("os.name") + private[this] def isWindows = os.toLowerCase.indexOf("windows") >= 0 +} + +/** A logger that logs to the console. On supported systems, the level labels are +* colored. */ +class ConsoleLogger extends BasicLogger +{ + import ConsoleLogger.formatEnabled + def messageColor(level: Level.Value) = Console.RESET + def labelColor(level: Level.Value) = + level match + { + case Level.Error => Console.RED + case Level.Warn => Console.YELLOW + case _ => Console.RESET + } + def successLabelColor = Console.GREEN + def successMessageColor = Console.RESET + override def success(message: => String) + { + if(atLevel(Level.Info)) + log(successLabelColor, Level.SuccessLabel, successMessageColor, message) + } + def trace(t: => Throwable): Unit = + System.out.synchronized + { + if(traceEnabled) + t.printStackTrace + } + def log(level: Level.Value, message: => String) + { + if(atLevel(level)) + log(labelColor(level), level.toString, messageColor(level), message) + } + private def setColor(color: String) + { + if(formatEnabled) + System.out.synchronized { System.out.print(color) } + } + private def log(labelColor: String, label: String, messageColor: String, message: String): Unit = + System.out.synchronized + { + for(line <- message.split("""\n""")) + { + setColor(Console.RESET) + System.out.print('[') + setColor(labelColor) + System.out.print(label) + setColor(Console.RESET) + System.out.print("] ") + setColor(messageColor) + System.out.print(line) + setColor(Console.RESET) + System.out.println() + } + } + + def logAll(events: Seq[LogEvent]) = System.out.synchronized { events.foreach(log) } + def control(event: ControlEvent.Value, message: => String) + { log(labelColor(Level.Info), Level.Info.toString, Console.BLUE, message) } +} \ No newline at end of file diff --git a/util/log/Level.scala b/util/log/Level.scala new file mode 100644 index 000000000..86abc257d --- /dev/null +++ b/util/log/Level.scala @@ -0,0 +1,25 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ + package xsbt + +/** An enumeration defining the levels available for logging. A level includes all of the levels +* with id larger than its own id. For example, Warn (id=3) includes Error (id=4).*/ +object Level extends Enumeration with NotNull +{ + val Debug = Value(1, "debug") + val Info = Value(2, "info") + val Warn = Value(3, "warn") + val Error = Value(4, "error") + /** Defines the label to use for success messages. A success message is logged at the info level but + * uses this label. Because the label for levels is defined in this module, the success + * label is also defined here. */ + val SuccessLabel = "success" + + // added because elements was renamed to iterator in 2.8.0 nightly + def levels = Debug :: Info :: Warn :: Error :: Nil + /** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */ + def apply(s: String) = levels.find(s == _.toString) + /** Same as apply, defined for use in pattern matching. */ + private[xsbt] def unapply(s: String) = apply(s) +} \ No newline at end of file diff --git a/util/log/LogEvent.scala b/util/log/LogEvent.scala new file mode 100644 index 000000000..19a5b24db --- /dev/null +++ b/util/log/LogEvent.scala @@ -0,0 +1,17 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ + package xsbt + +sealed trait LogEvent extends NotNull +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 ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent + +object ControlEvent extends Enumeration +{ + val Start, Header, Finish = Value +} \ No newline at end of file diff --git a/util/log/Logger.scala b/util/log/Logger.scala index b5203acb8..153596f6f 100644 --- a/util/log/Logger.scala +++ b/util/log/Logger.scala @@ -3,13 +3,14 @@ */ package xsbt -abstract class Logger extends NotNull + import xsbti.{Logger => xLogger, F0} +abstract class Logger extends xLogger with NotNull { def getLevel: Level.Value def setLevel(newLevel: Level.Value) def enableTrace(flag: Boolean) def traceEnabled: Boolean - + def atLevel(level: Level.Value) = level.id >= getLevel.id def trace(t: => Throwable): Unit final def debug(message: => String): Unit = log(Level.Debug, message) @@ -19,7 +20,7 @@ abstract class Logger extends NotNull def success(message: => String): Unit def log(level: Level.Value, message: => String): Unit def control(event: ControlEvent.Value, message: => String): Unit - + def logAll(events: Seq[LogEvent]): Unit /** Defined in terms of other methods in Logger and should not be called from them. */ final def log(event: LogEvent) @@ -34,38 +35,11 @@ abstract class Logger extends NotNull case c: ControlEvent => control(c.event, c.msg) } } + + def debug(msg: F0[String]): Unit = log(Level.Debug, msg) + def warn(msg: F0[String]): Unit = log(Level.Warn, msg) + def info(msg: F0[String]): Unit = log(Level.Info, msg) + def error(msg: F0[String]): Unit = log(Level.Error, msg) + def trace(msg: F0[Throwable]) = trace(msg.apply) + def log(level: Level.Value, msg: F0[String]): Unit = log(level, msg.apply) } - -sealed trait LogEvent extends NotNull -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 ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent - -object ControlEvent extends Enumeration -{ - val Start, Header, Finish = Value -} - -/** An enumeration defining the levels available for logging. A level includes all of the levels -* with id larger than its own id. For example, Warn (id=3) includes Error (id=4).*/ -object Level extends Enumeration with NotNull -{ - val Debug = Value(1, "debug") - val Info = Value(2, "info") - val Warn = Value(3, "warn") - val Error = Value(4, "error") - /** Defines the label to use for success messages. A success message is logged at the info level but - * uses this label. Because the label for levels is defined in this module, the success - * label is also defined here. */ - val SuccessLabel = "success" - - // added because elements was renamed to iterator in 2.8.0 nightly - def levels = Debug :: Info :: Warn :: Error :: Nil - /** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */ - def apply(s: String) = levels.find(s == _.toString) - /** Same as apply, defined for use in pattern matching. */ - private[xsbt] def unapply(s: String) = apply(s) -} \ No newline at end of file