scala.tools.nsc.plugins.Plugin.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the SubScript extension of the Scala Programming Language
The newest version!
/* NSC -- new Scala compiler
* Copyright 2007-2013 LAMP/EPFL
* @author Lex Spoon
*/
package scala.tools.nsc
package plugins
import scala.tools.nsc.io.{ Jar }
import scala.tools.nsc.util.ScalaClassLoader
import scala.reflect.io.{ Directory, File, Path }
import java.io.InputStream
import java.util.zip.ZipException
import scala.collection.mutable
import mutable.ListBuffer
import scala.util.{ Try, Success, Failure }
/** 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)
* }}}
*
* @author Lex Spoon
* @version 1.0, 2007-5-21
*/
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
def options: List[String] = {
// Process plugin options of form plugin:option
def namec = name + ":"
global.settings.pluginOptions.value filter (_ startsWith namec) map (_ stripPrefix namec)
}
/** Handle any plugin-specific options.
* The user writes `-P:plugname:opt1,opt2`,
* but the plugin sees `List(opt1, opt2)`.
* The plugin can opt out of further processing
* by returning false. For example, if the plugin
* has an "enable" flag, now would be a good time
* to sit on the bench.
* @param options plugin arguments
* @param error error function
* @return true to continue, or false to opt out
*/
def init(options: List[String], error: String => Unit): Boolean = {
processOptions(options, error)
true
}
@deprecated("use Plugin#init instead", since="2.11")
def processOptions(options: List[String], error: String => Unit): Unit = {
if (!options.isEmpty) error(s"Error: $name takes 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
}
/** ...
*
* @author Lex Spoon
* @version 1.0, 2007-5-21
*/
object Plugin {
private val PluginXML = "scalac-plugin.xml"
/** Create a class loader with the specified locations plus
* the loader that loaded the Scala compiler.
*/
private def loaderFor(locations: Seq[Path]): ScalaClassLoader = {
val compilerLoader = classOf[Plugin].getClassLoader
val urls = locations map (_.toURL)
ScalaClassLoader fromURLs (urls, compilerLoader)
}
/** Try to load a plugin description from the specified location.
*/
private def loadDescriptionFromJar(jarp: Path): Try[PluginDescription] = {
// XXX Return to this once we have more ARM support
def read(is: Option[InputStream]) = is match {
case None => throw new PluginLoadException(jarp.path, s"Missing $PluginXML in $jarp")
case Some(is) => PluginDescription.fromXML(is)
}
Try(new Jar(jarp.jfile).withEntryStream(PluginXML)(read))
}
private def loadDescriptionFromFile(f: Path): Try[PluginDescription] =
Try(PluginDescription.fromXML(new java.io.FileInputStream(f.jfile)))
type AnyClass = Class[_]
/** Use a class loader to load the plugin class.
*/
def load(classname: String, loader: ClassLoader): Try[AnyClass] = {
import scala.util.control.NonFatal
try {
Success[AnyClass](loader loadClass classname)
} catch {
case NonFatal(e) =>
Failure(new PluginLoadException(classname, s"Error: unable to load class: $classname"))
case e: NoClassDefFoundError =>
Failure(new PluginLoadException(classname, s"Error: class not found: ${e.getMessage} required by $classname"))
}
}
/** Load all plugins specified by the arguments.
* Each location of `paths` must be a valid plugin archive or exploded archive.
* Each of `paths` must define one plugin.
* Each of `dirs` may be a directory containing arbitrary plugin archives.
* Skips all plugins named in `ignoring`.
* A classloader is created to load each plugin.
*/
def loadAllFrom(
paths: List[List[Path]],
dirs: List[Path],
ignoring: List[String]): List[Try[AnyClass]] =
{
// List[(jar, Try(descriptor))] in dir
def scan(d: Directory) =
d.files.toList sortBy (_.name) filter (Jar isJarOrZip _) map (j => (j, loadDescriptionFromJar(j)))
type PDResults = List[Try[(PluginDescription, ScalaClassLoader)]]
// scan plugin dirs for jars containing plugins, ignoring dirs with none and other jars
val fromDirs: PDResults = dirs filter (_.isDirectory) flatMap { d =>
scan(d.toDirectory) collect {
case (j, Success(pd)) => Success((pd, loaderFor(Seq(j))))
}
}
// scan jar paths for plugins, taking the first plugin you find.
// a path element can be either a plugin.jar or an exploded dir.
def findDescriptor(ps: List[Path]) = {
def loop(qs: List[Path]): Try[PluginDescription] = qs match {
case Nil => Failure(new MissingPluginException(ps))
case p :: rest =>
if (p.isDirectory) loadDescriptionFromFile(p.toDirectory / PluginXML)
else if (p.isFile) loadDescriptionFromJar(p.toFile)
else loop(rest)
}
loop(ps)
}
val fromPaths: PDResults = paths map (p => (p, findDescriptor(p))) map {
case (p, Success(pd)) => Success((pd, loaderFor(p)))
case (_, Failure(e)) => Failure(e)
}
val seen = mutable.HashSet[String]()
val enabled = (fromPaths ::: fromDirs) map {
case Success((pd, loader)) if seen(pd.classname) =>
// a nod to SI-7494, take the plugin classes distinctly
Failure(new PluginLoadException(pd.name, s"Ignoring duplicate plugin ${pd.name} (${pd.classname})"))
case Success((pd, loader)) if ignoring contains pd.name =>
Failure(new PluginLoadException(pd.name, s"Disabling plugin ${pd.name}"))
case Success((pd, loader)) =>
seen += pd.classname
Plugin.load(pd.classname, loader)
case Failure(e) =>
Failure(e)
}
enabled // distinct and not disabled
}
/** Instantiate a plugin class, given the class and
* the compiler it is to be used in.
*/
def instantiate(clazz: AnyClass, global: Global): Plugin = {
(clazz getConstructor classOf[Global] newInstance global).asInstanceOf[Plugin]
}
}
class PluginLoadException(val path: String, message: String, cause: Exception) extends Exception(message, cause) {
def this(path: String, message: String) = this(path, message, null)
}
class MissingPluginException(path: String) extends PluginLoadException(path, s"No plugin in path $path") {
def this(paths: List[Path]) = this(paths mkString File.pathSeparator)
}