summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLex Spoon <lex@lexspoon.org>2007-06-07 09:11:46 +0000
committerLex Spoon <lex@lexspoon.org>2007-06-07 09:11:46 +0000
commitd1aed7012af7439181c4696fb33f5f4337b83684 (patch)
tree5f57aa3c6860ada20b5e4ef00debe18acf5884c1 /src
parent6739cacb9dbc1cbc3c459b87b8ba97923d687fbe (diff)
downloadscala-d1aed7012af7439181c4696fb33f5f4337b83684.tar.gz
scala-d1aed7012af7439181c4696fb33f5f4337b83684.tar.bz2
scala-d1aed7012af7439181c4696fb33f5f4337b83684.zip
Final merge from the plugins branch. The compiler
can now have plugins loaded at runtime via jars, and thus compiler components can be distributed indepedently of the central compiler.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala29
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterLoop.scala3
-rw-r--r--src/compiler/scala/tools/nsc/Main.scala44
-rw-r--r--src/compiler/scala/tools/nsc/MainGenericRunner.scala22
-rw-r--r--src/compiler/scala/tools/nsc/Settings.scala39
-rw-r--r--src/compiler/scala/tools/nsc/plugins/Plugin.scala138
-rw-r--r--src/compiler/scala/tools/nsc/plugins/PluginComponent.scala11
-rw-r--r--src/compiler/scala/tools/nsc/plugins/PluginDescription.scala68
-rw-r--r--src/compiler/scala/tools/nsc/plugins/PluginLoadException.scala5
-rw-r--r--src/compiler/scala/tools/nsc/plugins/Plugins.scala161
10 files changed, 497 insertions, 23 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index abb588970c..afedea0b1a 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -19,6 +19,7 @@ import scala.collection.mutable.{HashSet, HashMap, ListBuffer}
import symtab._
import symtab.classfile.{PickleBuffer, Pickler, ICodeReader}
import util.Statistics
+import plugins.Plugins
import ast._
import ast.parser._
import typechecker._
@@ -34,6 +35,7 @@ import backend.icode.analysis._
class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
with Trees
with CompilationUnits
+ with Plugins
{
// alternate constructors ------------------------------------------
def this(reporter: Reporter) =
@@ -367,7 +369,10 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
object typer extends analyzer.Typer(
analyzer.NoContext.make(EmptyTree, Global.this.definitions.RootClass, newScope))
- def phaseDescriptors: List[SubComponent] = List(
+ /** The built-in components. The full list of components, including
+ * plugins, is computed in the Plugins trait.
+ */
+ protected def builtInPhaseDescriptors: List[SubComponent] = List(
analyzer.namerFactory: SubComponent, // note: types are there because otherwise
analyzer.typerFactory: SubComponent, // consistency check after refchecks would fail.
superAccessors, // add super accessors
@@ -393,6 +398,24 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
if (forMSIL) genMSIL else genJVM, // generate .class files
sampleTransform)
+
+ private var phasesCache: Option[List[SubComponent]] = None
+
+ def phaseDescriptors = {
+ if (phasesCache.isEmpty)
+ phasesCache = Some(computePhaseDescriptors)
+ phasesCache.get
+ }
+
+ /** A description of the phases that will run */
+ def phaseDescriptions: String = {
+ val messages =
+ for (phase <- phaseDescriptors)
+ yield phase.phaseName //todo: + " - " + phase.description
+ messages.mkString("\n")
+ }
+
+
protected def insertBefore(c: SubComponent, cs: List[SubComponent], before: SubComponent): List[SubComponent] = cs match {
case List() => List(c)
case c1 :: cs1 => if (c1 == before) c :: cs else c1 :: insertBefore(c, cs1, before)
@@ -649,4 +672,8 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
def onlyPresentation = settings.doc.value
// used to disable caching in lampion IDE.
def inIDE = false
+
+
+ // force some initialization
+ new Run
}
diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
index f37044be8e..b58aa4cf64 100644
--- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala
+++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
@@ -263,6 +263,9 @@ class InterpreterLoop(in0: BufferedReader, out: PrintWriter) {
createInterpreter()
try {
+ if (interpreter.reporter.hasErrors) {
+ return // it is broken on startup; go ahead and exit
+ }
printWelcome
repl
} finally {
diff --git a/src/compiler/scala/tools/nsc/Main.scala b/src/compiler/scala/tools/nsc/Main.scala
index 66d4eb3c1a..fd2fe4cc82 100644
--- a/src/compiler/scala/tools/nsc/Main.scala
+++ b/src/compiler/scala/tools/nsc/Main.scala
@@ -44,29 +44,42 @@ object Main extends AnyRef with EvalLoop {
val command = new CompilerCommand(List.fromArray(args), settings, error, false)
if (command.settings.version.value)
reporter.info(null, versionMsg, true)
- else if (command.settings.help.value || command.settings.Xhelp.value) {
- if (command.settings.help.value) reporter.info(null, command.usageMsg, true)
- if (command.settings.Xhelp.value) reporter.info(null, command.xusageMsg, true)
- } else {
+ else {
try {
object compiler extends Global(command.settings, reporter)
if (reporter.hasErrors) {
reporter.flush()
return
}
- if (command.settings.resident.value)
- resident(compiler)
- else if (command.files.isEmpty)
- reporter.info(null, command.usageMsg, true)
+
+ if (command.settings.help.value || command.settings.Xhelp.value) {
+ if (command.settings.help.value) {
+ reporter.info(null, command.usageMsg, true)
+ reporter.info(null, compiler.pluginOptionsHelp, true)
+ }
+ if (command.settings.Xhelp.value)
+ reporter.info(null, command.xusageMsg, true)
+ } else if (command.settings.showPlugins.value)
+ reporter.info(null, compiler.pluginDescriptions, true)
+ else if (command.settings.showPhases.value)
+ reporter.info(null, compiler.phaseDescriptions, true)
else {
- val run = new compiler.Run
- run compile command.files
- if (command.settings.doc.value) {
- object generator extends DocGenerator {
- val global: compiler.type = compiler
- def settings = command.settings
+ if (command.settings.resident.value)
+ resident(compiler)
+ else if (command.files.isEmpty) {
+ reporter.info(null, command.usageMsg, true)
+ reporter.info(null, compiler.pluginOptionsHelp, true)
+ } else {
+ val run = new compiler.Run
+ run compile command.files
+ if (command.settings.doc.value) {
+ object generator extends DocGenerator {
+ val global: compiler.type = compiler
+ def settings = command.settings
+ }
+ generator.process(run.units)
}
- generator.process(run.units)
+ reporter.printSummary()
}
}
} catch {
@@ -75,7 +88,6 @@ object Main extends AnyRef with EvalLoop {
ex.printStackTrace();
reporter.error(null, "fatal error: " + msg)
}
- reporter.printSummary()
}
}
diff --git a/src/compiler/scala/tools/nsc/MainGenericRunner.scala b/src/compiler/scala/tools/nsc/MainGenericRunner.scala
index c5b903066f..ac895cea6f 100644
--- a/src/compiler/scala/tools/nsc/MainGenericRunner.scala
+++ b/src/compiler/scala/tools/nsc/MainGenericRunner.scala
@@ -64,11 +64,6 @@ object MainGenericRunner {
settings.defines.applyToCurrentJVM
- if (settings.help.value || !command.ok) {
- Console.println(command.usageMessage)
- return
- }
-
if (settings.version.value) {
Console.println(
"Scala code runner " +
@@ -77,6 +72,23 @@ object MainGenericRunner {
return
}
+ if (settings.help.value || !command.ok) {
+ println(command.usageMessage)
+ return
+ }
+
+ def sampleCompiler = new Global(settings)
+
+ if (settings.showPhases.value) {
+ println(sampleCompiler.phaseDescriptions)
+ return
+ }
+
+ if (settings.showPlugins.value) {
+ println(sampleCompiler.pluginDescriptions)
+ return
+ }
+
def paths0(str: String): List[String] =
str.split(File.pathSeparator).toList
diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala
index 88752e80ca..c60756916b 100644
--- a/src/compiler/scala/tools/nsc/Settings.scala
+++ b/src/compiler/scala/tools/nsc/Settings.scala
@@ -83,6 +83,12 @@ class Settings(error: String => Unit) {
val sourcepath = StringSetting ("-sourcepath", "path", "Specify where to find input source files", "")
val bootclasspath = StringSetting ("-bootclasspath", "path", "Override location of bootstrap class files", bootclasspathDefault)
val extdirs = StringSetting ("-extdirs", "dirs", "Override location of installed extensions", extdirsDefault)
+ val plugin = MultiStringSetting("-plugin", "file", "Load a plugin from a file")
+ val disable = MultiStringSetting("-disable", "plugin", "Disable a plugin")
+ val require = MultiStringSetting("-require", "plugin", "Abort unless a plugin is available")
+ val pluginOptions = new MultiStringSetting("-P", "plugin:opt", "Pass an option to a plugin") {
+ override def helpSyntax = "-P:<plugin>:<opt>"
+ }
val outdir = StringSetting ("-d", "directory", "Specify where to place generated class files", ".")
val encoding = new StringSetting ("-encoding", "encoding", "Specify character encoding used by source files", encodingDefault) { override def hiddenToIDE = false }
val target = ChoiceSetting ("-target", "Specify which backend to use", List("jvm-1.5", "jvm-1.4", "msil", "cldc"), "jvm-1.4")
@@ -114,7 +120,8 @@ class Settings(error: String => Unit) {
val version = new BooleanSetting("-version", "Print product version and exit") { override def hiddenToIDE = true }
val help = new BooleanSetting("-help", "Print a synopsis of standard options") { override def hiddenToIDE = true }
val nouescape = new BooleanSetting("-nouescape", "disables handling of \\u unicode escapes")
-// val showPhases = BooleanSetting("-showphases", "Print a synopsis of compiler phases")
+ val showPhases = BooleanSetting("-showphases", "Print a synopsis of compiler phases")
+ val showPlugins = BooleanSetting("-showplugins", "Print a synopsis of loaded plugins")
val inline = BooleanSetting("-Xinline", "Perform inlining when possible")
@@ -289,6 +296,36 @@ class Settings(error: String => Unit) {
if (value == default) Nil else List(name, value)
}
+ /** A setting that accumulates all strings supplied to it */
+ case class MultiStringSetting(name: String, arg: String, descr: String)
+ extends Setting(descr) {
+ override def hiddenToIDE = true
+ protected var v: List[String] = Nil
+ def value = v
+ def appendToValue(str: String) { v = v ::: List(str) }
+
+ protected val nameColon = name + ":"
+ def tryToSet(args: List[String]): List[String] = args match {
+ case arg :: rest if (arg.startsWith(nameColon)) =>
+ val toadd = arg.substring(nameColon.length())
+ if (toadd.length == 0) {
+ error("empty argument to " + nameColon)
+ args
+ } else {
+ appendToValue(toadd)
+ rest
+ }
+ case _ => args
+ }
+
+ override def helpSyntax = name + ":<" + arg + ">"
+
+ def unparse: List[String] =
+ for (opt <- value)
+ yield nameColon+opt
+ }
+
+
/** A setting represented by a string in a given set of <code>choices</code>,
* (<code>default</code> unless set).
*/
diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala
new file mode 100644
index 0000000000..73ebb66134
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala
@@ -0,0 +1,138 @@
+package scala.tools.nsc.plugins
+import java.io.File
+import java.util.jar.JarFile
+import java.util.zip.ZipException
+import scala.xml.XML
+import java.net.URLClassLoader
+import scala.collection.mutable
+import mutable.ListBuffer
+
+/** Information about a plugin loaded from a jar file.
+ *
+ * The concrete subclass must have a one-argument constructor
+ * that accepts an instance of Global.
+ *
+ * (val global: Global)
+ */
+abstract class Plugin {
+ /** The name of this plugin */
+ val name: String
+
+ /** The components that this phase defines */
+ val components: List[PluginComponent]
+
+ /** A one-line description of the plugin */
+ val description: String
+
+ /** The compiler that this plugin uses. This is normally equated
+ * to a constructor parameter in the concrete subclass. */
+ val global: Global
+
+ /** Handle any plugin-specific options. The -P:plugname: part
+ * will not be present. */
+ def processOptions(options: List[String], error: String=>Unit) {
+ if (!options.isEmpty)
+ error("Error: " + name + " has no options")
+ }
+
+ /** A description of this plugin's options, suitable as a response
+ * to the -help command-line option. Conventionally, the
+ * options should be listed with the -P:plugname: part included.
+ */
+ val optionsHelp: Option[String] = None
+}
+
+object Plugin {
+ /** Create a class loader with the specified file plus
+ * the loader that loaded the Scala compiler.
+ */
+ private def loaderFor(jarfiles: Seq[File]): ClassLoader = {
+ val compilerLoader = classOf[Plugin].getClassLoader
+ val jarurls = jarfiles.map(.toURL).toArray
+ new URLClassLoader(jarurls, compilerLoader)
+ }
+
+
+
+ /** Try to load a plugin description from the specified
+ * file, returning None if it does not work. */
+ private def loadDescription(jarfile: File): Option[PluginDescription] = {
+ if (!jarfile.exists) return None
+
+ try {
+ val jar = new JarFile(jarfile)
+ try {
+ val ent = jar.getEntry("scalac-plugin.xml")
+ if(ent == null) return None
+
+ val inBytes = jar.getInputStream(ent)
+ val packXML = XML.load(inBytes)
+ inBytes.close()
+
+ PluginDescription.fromXML(packXML)
+ } finally {
+ jar.close()
+ }
+ } catch {
+ case _:ZipException => None
+ }
+ }
+
+
+
+ /** Loads a plugin class from the named jar file. Returns None
+ * if the jar file has no plugin in it or if the plugin
+ * is badly formed. */
+ def loadFrom(jarfile: File,
+ loader: ClassLoader): Option[Class] =
+ {
+ val pluginInfo = loadDescription(jarfile).get
+
+ try {
+ Some(loader.loadClass(pluginInfo.classname))
+ } catch {
+ case _:ClassNotFoundException =>
+ println("Warning: class not found for plugin in " + jarfile +
+ " (" + pluginInfo.classname + ")")
+ None
+ }
+ }
+
+
+
+ /** Load all plugins found in the argument list, bot hin
+ * the jar files explicitly listed, and in the jar files in
+ * the directories specified. Skips all plugins in `ignoring'.
+ * A single classloader is created and used to load all of them. */
+ def loadAllFrom(jars: List[File],
+ dirs: List[File],
+ ignoring: List[String]): List[Class] =
+ {
+ val alljars = new ListBuffer[File]
+
+ alljars ++= jars
+
+ for {
+ dir <- dirs
+ entries = dir.listFiles.toList
+ sorted = entries.sort((f1,f2)=>f1.getName <= f2.getName)
+ ent <- sorted
+ if ent.toString.toLowerCase.endsWith(".jar")
+ pdesc <- loadDescription(ent)
+ if !(ignoring contains pdesc.name)
+ } alljars += ent
+
+ val loader = loaderFor(alljars.toList)
+ alljars.toList.map(f => loadFrom(f,loader)).flatMap(x => x)
+ }
+
+
+
+ /** Instantiate a plugin class, given the class and
+ * the compiler it is to be used in.
+ */
+ def instantiate(clazz: Class, global: Global): Plugin = {
+ val constructor = clazz.getConstructor(Array(classOf[Global]))
+ constructor.newInstance(Array(global)).asInstanceOf[Plugin]
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/plugins/PluginComponent.scala b/src/compiler/scala/tools/nsc/plugins/PluginComponent.scala
new file mode 100644
index 0000000000..8a4cee80f7
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/plugins/PluginComponent.scala
@@ -0,0 +1,11 @@
+package scala.tools.nsc.plugins
+
+/** A component that is part of a Plugin.
+ *
+ * @version 1.0
+ * @author Lex Spoon, 2007/5/29
+ */
+abstract class PluginComponent extends SubComponent {
+ /** the phase this plugin wants to run after */
+ val runsAfter: String
+}
diff --git a/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala b/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala
new file mode 100644
index 0000000000..f7abda65aa
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala
@@ -0,0 +1,68 @@
+package scala.tools.nsc.plugins
+import scala.xml.{Node,NodeSeq}
+import java.io.File
+
+/** A description of a compiler plugin, suitable for serialization
+ * to XML for inclusion in the plugin's .jar file.
+ *
+ * @author Lex Spoon
+ * @version 1.0, 2007-5-21
+ */
+abstract class PluginDescription {
+ /** A short name of the compiler, used to identify it in
+ * various contexts. The phase defined by the plugin
+ * should have the same name. */
+ val name: String
+
+
+ /** The name of the main class for the plugin */
+ val classname: String
+
+ /** An XML representation of this description. It can be
+ * read back using PluginDescription.fromXML . It should
+ * be stored inside the jor. */
+ def toXML: Node = {
+ <plugin>
+ <name>{name}</name>
+ <classname>{classname}</classname>
+ </plugin>
+ }
+}
+
+
+
+/** Utilities for the PluginDescription class.
+ *
+ * @author Lex Spoon
+ * @version 1.0, 2007-5-21
+ */
+object PluginDescription {
+ def fromXML(xml: Node): Option[PluginDescription] = {
+ // check the top-level tag
+ xml match {
+ case <plugin>{_*}</plugin> => ()
+ case _ => return None
+ }
+
+ /** Extract one field */
+ def getField(field: String): Option[String] = {
+ val text = (xml \\ field).text.trim
+ if (text == "") None else Some(text)
+ }
+
+ // extract the required fields
+ val name1 = getField("name") match {
+ case None => return None
+ case Some(str) => str
+ }
+ val classname1 = getField("classname") match {
+ case None => return None
+ case Some(str) => str
+ }
+
+ Some(new PluginDescription {
+ val name = name1
+ val classname = classname1
+ })
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/plugins/PluginLoadException.scala b/src/compiler/scala/tools/nsc/plugins/PluginLoadException.scala
new file mode 100644
index 0000000000..d69a5e098c
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/plugins/PluginLoadException.scala
@@ -0,0 +1,5 @@
+package scala.tools.nsc.plugins
+
+
+class PluginLoadException(filename: String, cause: Exception)
+extends Exception(cause)
diff --git a/src/compiler/scala/tools/nsc/plugins/Plugins.scala b/src/compiler/scala/tools/nsc/plugins/Plugins.scala
new file mode 100644
index 0000000000..b8d618a3fc
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/plugins/Plugins.scala
@@ -0,0 +1,161 @@
+package scala.tools.nsc.plugins
+import java.io.File
+
+/** Support for run-time loading of compiler plugins */
+trait Plugins { self: Global =>
+
+ /** Load all available plugin. Skips plugins that
+ * either have the same name as another one, or which
+ * define a phase name that another one does.
+ */
+ protected def loadPlugins: List[Plugin] = {
+ // load all the plugins
+ val jars = settings.plugin.value.map(new File(_))
+ val dirs =
+ for (name <- settings.extdirs.value.split(File.pathSeparator).toList)
+ yield new File(name)
+
+ val initPlugins =
+ for (plugClass <- Plugin.loadAllFrom(jars, dirs, settings.disable.value))
+ yield Plugin.instantiate(plugClass, this)
+
+ // remove any with conflicting names or subcomponent names
+ def pick(
+ plugins: List[Plugin],
+ plugNames: Set[String],
+ phaseNames: Set[String]): List[Plugin] =
+ {
+ plugins match {
+ case Nil => Nil
+ case plug :: rest =>
+ val plugPhaseNames = Set.empty ++ plug.components.map(.phaseName)
+ def withoutPlug = pick(rest, plugNames, plugPhaseNames)
+ def withPlug =
+ (plug ::
+ pick(rest,
+ plugNames+plug.name,
+ phaseNames++plugPhaseNames))
+
+ if (plugNames.contains(plug.name)) {
+ if (settings.verbose.value)
+ inform("[skipping a repeated plugin: " + plug.name + "]")
+ withoutPlug
+ } else if (settings.disable.value contains(plug.name)) {
+ if (settings.verbose.value)
+ inform("[disabling plugin: " + plug.name + "]")
+ withoutPlug
+ } else {
+ val commonPhases = phaseNames.intersect(plugPhaseNames)
+ if (!commonPhases.isEmpty) {
+ if (settings.verbose.value)
+ inform("[skipping plugin " + plug.name +
+ "because it repeats phase names: " +
+ commonPhases.mkString(", ") + "]")
+ withoutPlug
+ } else {
+ if (settings.verbose.value)
+ inform("[loaded plugin " + plug.name + "]")
+ withPlug
+ }
+ }
+ }
+ }
+
+ val plugs =
+ pick(initPlugins,
+ Set.empty,
+ Set.empty ++ builtInPhaseDescriptors.map(.phaseName))
+
+ for (req <- settings.require.value; if !plugs.exists(p => p.name==req))
+ error("Missing required plugin: " + req)
+
+
+ for (plug <- plugs) {
+ val nameColon = plug.name + ":"
+ val opts = for {
+ raw <- settings.pluginOptions.value
+ if raw.startsWith(nameColon)
+ } yield raw.substring(nameColon.length)
+
+ if (!opts.isEmpty)
+ plug.processOptions(opts, error)
+ }
+
+ for {
+ opt <- settings.pluginOptions.value
+ if !plugs.exists(p => opt.startsWith(p.name + ":"))
+ } error("bad option: -P:" + opt)
+
+ plugs
+ }
+
+
+ private var pluginsCache: Option[List[Plugin]] = None
+
+ def plugins: List[Plugin] = {
+ if (pluginsCache.isEmpty)
+ pluginsCache = Some(loadPlugins)
+ pluginsCache.get
+ }
+
+
+ /** A description of all the plugins that are loaded */
+ def pluginDescriptions: String = {
+ val messages =
+ for (plugin <- plugins)
+ yield plugin.name + " - " + plugin.description
+ messages.mkString("\n")
+ }
+
+
+ /** Compute a full list of phase descriptors, including
+ * both built-in phases and those coming from plugins. */
+ protected def computePhaseDescriptors: List[SubComponent] = {
+ def insert(descs: List[SubComponent], component: PluginComponent)
+ :List[SubComponent] =
+ {
+ descs match {
+ case Nil => assert(false); Nil
+ case hd::rest if hd.phaseName == component.runsAfter =>
+ hd :: component :: rest
+ case hd :: rest =>
+ hd :: (insert(rest, component))
+ }
+ }
+
+ var descriptors = builtInPhaseDescriptors
+ var plugsLeft = plugins.flatMap(.components)
+
+ // Insert all the plugins, one by one. Note that
+ // plugins are allowed to depend on each other, thus
+ // complicating the algorithm.
+
+ while (!plugsLeft.isEmpty) {
+ val nextPlug = plugsLeft.find(plug =>
+ descriptors.exists(d => d.phaseName == plug.runsAfter))
+ nextPlug match {
+ case None =>
+ error("Failed to load plugin phases")
+ for (plug <- plugsLeft)
+ error (plug.phaseName + " depends on " + plug.runsAfter)
+ return descriptors
+ case Some(nextPlug) =>
+ descriptors = insert(descriptors, nextPlug)
+ plugsLeft = plugsLeft.filter(p => !(p eq nextPlug))
+ }
+ }
+
+ descriptors
+ }
+
+ /** Summary of the options for all loaded plugins */
+ def pluginOptionsHelp: String = {
+ val buf = new StringBuffer
+ for (plug <- plugins; help <- plug.optionsHelp) {
+ buf append ("Options for plugin " + plug.name + ":\n")
+ buf append help
+ buf append "\n"
+ }
+ buf.toString
+ }
+}