From 7bb72ecb8b6dfffb165896599285f76d51a6a4cf Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Wed, 7 Oct 2009 21:27:53 -0400 Subject: [PATCH] Scaladoc, run, and console interfaces --- compile/AnalyzingCompiler.scala | 41 ++++++++++++++++------ compile/CompilerArguments.scala | 16 +++++++-- compile/interface/CompilerInterface.scala | 34 ------------------ compile/interface/ConsoleInterface.scala | 38 ++++++++++++++++++++ compile/interface/RunInterface.scala | 20 +++++++++++ compile/interface/ScaladocInterface.scala | 42 +++++++++++++++++++++++ util/io/FileUtilities.scala | 6 ++++ 7 files changed, 150 insertions(+), 47 deletions(-) create mode 100644 compile/interface/ConsoleInterface.scala create mode 100644 compile/interface/RunInterface.scala create mode 100644 compile/interface/ScaladocInterface.scala diff --git a/compile/AnalyzingCompiler.scala b/compile/AnalyzingCompiler.scala index 443722525..0056a7fd6 100644 --- a/compile/AnalyzingCompiler.scala +++ b/compile/AnalyzingCompiler.scala @@ -2,7 +2,7 @@ package xsbt import xsbti.{AnalysisCallback, Logger => xLogger} import java.io.File - import java.net.URLClassLoader + import java.net.{URL, URLClassLoader} /** Interface to the Scala compiler that uses the dependency analysis plugin. This class uses the Scala library and compiler * provided by scalaInstance. This class requires a ComponentManager in order to obtain the interface code to scalac and @@ -16,16 +16,16 @@ class AnalyzingCompiler(scalaInstance: ScalaInstance, manager: ComponentManager) callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger) { val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath) - val interfaceClass = getInterfaceClass("xsbt.CompilerInterface", log) - val interface = interfaceClass.newInstance.asInstanceOf[AnyRef] + call("xsbt.CompilerInterface", log)( + classOf[Array[String]], classOf[AnalysisCallback], classOf[Int], classOf[xLogger] ) ( + arguments.toArray[String] : Array[String], callback, maximumErrors: java.lang.Integer, log ) // this is commented out because of Scala ticket #2365 - /*val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: xLogger): Unit }] + /* + val interface = getInterfaceClass(interface, log).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)*/ - - val method = interfaceClass.getMethod("run", classOf[Array[String]], classOf[AnalysisCallback], classOf[Int], classOf[xLogger]) - method.invoke(interface, arguments.toArray[String] : Array[String], callback, maximumErrors: java.lang.Integer, log) } def doc(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], maximumErrors: Int, log: CompileLogger): Unit = doc(sources, classpath, outputDirectory, options, false, maximumErrors, log) @@ -33,10 +33,31 @@ class AnalyzingCompiler(scalaInstance: ScalaInstance, manager: ComponentManager) maximumErrors: Int, log: CompileLogger): Unit = { val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath) - val interfaceClass = getInterfaceClass("xsbt.ScaladocInterface", log) + call("xsbt.ScaladocInterface", log) (classOf[Array[String]], classOf[Int], classOf[xLogger]) (arguments.toArray[String] : Array[String], maximumErrors: java.lang.Integer, log) + } + def run(classpath: Set[File], mainClass: String, options: Seq[String], log: CompileLogger) + { + val arguments = new CompilerArguments(scalaInstance) + val classpathURLs = arguments.finishClasspath(classpath, true).toSeq + val bootClasspath = FileUtilities.pathSplit( arguments.createBootClasspath ) + val extraURLs = bootClasspath.filter(_.length > 0).map(new File(_)) + val classpathArray = (classpathURLs ++ extraURLs).map(_.toURI.toURL).toArray[URL] + call("xsbt.RunInterface", log)(classOf[Array[URL]], classOf[String], classOf[Array[String]], classOf[xLogger]) ( + classpathArray : Array[URL], mainClass, options.toArray[String] : Array[String], log + ) + } + def console(classpath: Set[File], initialCommands: String, log: CompileLogger): Unit = + { + val classpathString = CompilerArguments.absString(classpath) + val bootClasspath = (new CompilerArguments(scalaInstance)).createBootClasspath + call("xsbt.ConsoleInterface", log) (classOf[String], classOf[String], classOf[String], classOf[xLogger]) (bootClasspath, classpathString, initialCommands, log) + } + private def call(interfaceClassName: String, log: CompileLogger)(argTypes: Class[_]*)(args: AnyRef*) + { + val interfaceClass = getInterfaceClass(interfaceClassName, log) val interface = interfaceClass.newInstance.asInstanceOf[AnyRef] - val method = interfaceClass.getMethod("run", classOf[Array[String]], classOf[Int], classOf[xLogger]) - method.invoke(interface, arguments.toArray[String] : Array[String], maximumErrors: java.lang.Integer, log) + val method = interfaceClass.getMethod("run", argTypes : _*) + method.invoke(interface, args: _*) } private def getInterfaceClass(name: String, log: CompileLogger) = { diff --git a/compile/CompilerArguments.scala b/compile/CompilerArguments.scala index 12da5735e..f22a5491b 100644 --- a/compile/CompilerArguments.scala +++ b/compile/CompilerArguments.scala @@ -1,6 +1,7 @@ package xsbt import java.io.File + import CompilerArguments.{abs, absString} /** Forms the list of options that is passed to the compiler from the required inputs and other options. * The directory containing scala-library.jar and scala-compiler.jar (scalaLibDirectory) is required in @@ -13,11 +14,13 @@ class CompilerArguments(scalaInstance: ScalaInstance) { checkScalaHomeUnset() val bootClasspathOption = Seq("-bootclasspath", createBootClasspath) - val cpWithCompiler = classpath ++ (if(compilerOnClasspath) scalaInstance.compilerJar :: Nil else Nil) - val classpathOption = Seq("-cp", abs(cpWithCompiler).mkString(File.pathSeparator) ) + val cpWithCompiler = finishClasspath(classpath, compilerOnClasspath) + val classpathOption = Seq("-cp", absString(cpWithCompiler) ) val outputOption = Seq("-d", outputDirectory.getAbsolutePath) options ++ outputOption ++ bootClasspathOption ++ classpathOption ++ abs(sources) } + def finishClasspath(classpath: Set[File], compilerOnClasspath: Boolean): Set[File] = + classpath ++ (if(compilerOnClasspath) scalaInstance.compilerJar :: Nil else Nil) protected def abs(files: Set[File]) = files.map(_.getAbsolutePath) protected def checkScalaHomeUnset() { @@ -25,10 +28,17 @@ class CompilerArguments(scalaInstance: ScalaInstance) assert((scalaHome eq null) || scalaHome.isEmpty, "'scala.home' should not be set (was " + scalaHome + ")") } /** Add the correct Scala library jar to the boot classpath.*/ - protected def createBootClasspath = + def createBootClasspath = { val originalBoot = System.getProperty("sun.boot.class.path", "") val newBootPrefix = if(originalBoot.isEmpty) "" else originalBoot + File.pathSeparator newBootPrefix + scalaInstance.libraryJar.getAbsolutePath } +} +object CompilerArguments +{ + def abs(files: Seq[File]): Seq[String] = files.map(_.getAbsolutePath) + def abs(files: Set[File]): Seq[String] = abs(files.toSeq) + def absString(files: Seq[File]): String = abs(files).mkString(File.pathSeparator) + def absString(files: Set[File]): String = absString(files.toSeq) } \ No newline at end of file diff --git a/compile/interface/CompilerInterface.scala b/compile/interface/CompilerInterface.scala index 26a2bc784..479f3dcf2 100644 --- a/compile/interface/CompilerInterface.scala +++ b/compile/interface/CompilerInterface.scala @@ -58,38 +58,4 @@ class CompilerInterface } } } -class ScaladocInterface -{ - def run(args: Array[String], maximumErrors: Int, log: Logger) - { - import scala.tools.nsc.{doc, CompilerCommand, Global} - val reporter = new LoggerReporter(maximumErrors, log) - val docSettings: doc.Settings = new doc.Settings(reporter.error) - val command = new CompilerCommand(args.toList, docSettings, error, false) - trait Compat27 { def computeInternalPhases(): Unit = () } - val phasesSet = scala.collection.mutable.Set[scala.tools.nsc.SubComponent]() // for 2.7 source compatibility - object compiler extends Global(command.settings, reporter) with Compat27 - { - override def onlyPresentation = true - override def computeInternalPhases() { - phasesSet += syntaxAnalyzer - phasesSet += analyzer.namerFactory - phasesSet += analyzer.typerFactory - } - } - if(!reporter.hasErrors) - { - val run = new compiler.Run - run compile command.files - val generator = new doc.DefaultDocDriver - { - lazy val global: compiler.type = compiler - lazy val settings = docSettings - } - generator.process(run.units) - } - reporter.printSummary() - if(reporter.hasErrors) throw new InterfaceCompileFailed(args, "Scaladoc generation failed") - } -} class InterfaceCompileFailed(val arguments: Array[String], override val toString: String) extends xsbti.CompileFailed \ No newline at end of file diff --git a/compile/interface/ConsoleInterface.scala b/compile/interface/ConsoleInterface.scala new file mode 100644 index 000000000..51e54cedc --- /dev/null +++ b/compile/interface/ConsoleInterface.scala @@ -0,0 +1,38 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import xsbti.Logger +import scala.tools.nsc.{GenericRunnerCommand,InterpreterLoop} + +class ConsoleInterface +{ + def run(bootClasspathString: String, classpathString: String, initialCommands: String, log: Logger) + { + val settings = Settings(log) + settings.bootclasspath.value = bootClasspathString + settings.classpath.value = classpathString + log.info(Message("Starting scala interpreter...")) + log.debug(Message(" Classpath: " + settings.classpath.value)) + log.info(Message("")) + val loop = new InterpreterLoop { + override def createInterpreter() = { + super.createInterpreter() + if(!initialCommands.isEmpty) interpreter.interpret(initialCommands) + } + } + loop.main(settings) + } +} +object Settings +{ + def apply(log: Logger) = + { + val command = new GenericRunnerCommand(Nil, message => log.error(Message(message))) + if(command.ok) + command.settings + else + throw new InterfaceCompileFailed(Array(), command.usageMsg) + } +} diff --git a/compile/interface/RunInterface.scala b/compile/interface/RunInterface.scala new file mode 100644 index 000000000..674941aa3 --- /dev/null +++ b/compile/interface/RunInterface.scala @@ -0,0 +1,20 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import xsbti.Logger +import scala.tools.nsc.ObjectRunner + +import java.net.URL + +class RunInterface +{ + def run(classpathURLs: Array[URL], mainClass: String, options: Array[String], log: Logger) + { + log.info(Message("Running " + mainClass + " ...")) + log.debug(Message(" Classpath:" + classpathURLs.mkString("\n\t", "\n\t",""))) + try { ObjectRunner.run(classpathURLs.toList, mainClass, options.toList) } + catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause } + } +} \ No newline at end of file diff --git a/compile/interface/ScaladocInterface.scala b/compile/interface/ScaladocInterface.scala new file mode 100644 index 000000000..2b9d21d3d --- /dev/null +++ b/compile/interface/ScaladocInterface.scala @@ -0,0 +1,42 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import xsbti.Logger +import scala.tools.nsc.SubComponent + +class ScaladocInterface +{ + def run(args: Array[String], maximumErrors: Int, log: Logger) + { + import scala.tools.nsc.{doc, CompilerCommand, Global} + val reporter = new LoggerReporter(maximumErrors, log) + val docSettings: doc.Settings = new doc.Settings(reporter.error) + val command = new CompilerCommand(args.toList, docSettings, error, false) + trait Compat27 { def computeInternalPhases(): Unit = () } + val phasesSet = scala.collection.mutable.Set[scala.tools.nsc.SubComponent]() // for 2.7 source compatibility + object compiler extends Global(command.settings, reporter) with Compat27 + { + override def onlyPresentation = true + override def computeInternalPhases() { + phasesSet += syntaxAnalyzer + phasesSet += analyzer.namerFactory + phasesSet += analyzer.typerFactory + } + } + if(!reporter.hasErrors) + { + val run = new compiler.Run + run compile command.files + val generator = new doc.DefaultDocDriver + { + lazy val global: compiler.type = compiler + lazy val settings = docSettings + } + generator.process(run.units) + } + reporter.printSummary() + if(reporter.hasErrors) throw new InterfaceCompileFailed(args, "Scaladoc generation failed") + } +} \ No newline at end of file diff --git a/util/io/FileUtilities.scala b/util/io/FileUtilities.scala index 72837d153..83563740f 100644 --- a/util/io/FileUtilities.scala +++ b/util/io/FileUtilities.scala @@ -369,4 +369,10 @@ object FileUtilities transfer(in, out) out.toByteArray } + + /** A pattern used to split a String by path separator characters.*/ + private val PathSeparatorPattern = java.util.regex.Pattern.compile(File.pathSeparator) + + /** Splits a String around path separator characters. */ + def pathSplit(s: String) = PathSeparatorPattern.split(s) }