Filling in logging and making cross-compile work.

This commit is contained in:
Mark Harrah 2009-09-05 12:19:34 -04:00
parent e388ffd3a6
commit 8bfb2802fb
22 changed files with 404 additions and 151 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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" }
}
}
}

View File

@ -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 }
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -0,0 +1,6 @@
package xsbti;
public abstract class CompileFailed extends RuntimeException
{
public abstract String[] arguments();
}

View File

@ -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() }
}
}

View File

@ -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))
}
}

View File

@ -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)

View File

@ -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

View File

@ -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")
}
}
}
}

View File

@ -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))
} }
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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) }
}

25
util/log/Level.scala Normal file
View File

@ -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)
}

17
util/log/LogEvent.scala Normal file
View File

@ -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
}

View File

@ -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)
}