summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/plugins/Plugin.scala
blob: 3013199c366247769dfb549390b23eaca0b235c5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/* NSC -- new Scala compiler
 * Copyright 2007-2009 LAMP/EPFL
 * @author Lex Spoon
 */
// $Id$

package scala.tools.nsc
package plugins

import java.io.File
import java.net.URLClassLoader
import java.util.jar.JarFile
import java.util.zip.ZipException

import scala.collection.mutable
import mutable.ListBuffer
import scala.xml.XML

/** <p>
 *    Information about a plugin loaded from a jar file.
 *  </p>
 *  <p>
 *    The concrete subclass must have a one-argument constructor
 *    that accepts an instance of <code>Global</code>.
 *  </p><pre>
 *    (val global: Global)
 *  </pre>
 *
 *  @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

  /** 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 <code>-P:plugname:</code>
   *  part included.
   */
  val optionsHelp: Option[String] = None
}

/** ...
 *
 *  @author Lex Spoon
 *  @version 1.0, 2007-5-21
 */
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
    }
  }

  type AnyClass = Class[_]

  /** 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[AnyClass] = {
    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, both in the
   *  jar files explicitly listed, and in the jar files in the
   *  directories specified. Skips all plugins in <code>ignoring</code>.
   *  A single classloader is created and used to load all of them.
   */
  def loadAllFrom(jars: List[File],
		  dirs: List[File],
		  ignoring: List[String]): List[AnyClass] =
  {
    val alljars = new ListBuffer[File]

    alljars ++= jars

    for {
      dir <- dirs if dir.isDirectory
      entries = dir.listFiles
      if entries ne null
      entry <- entries.toList.sort(_.getName <= _.getName)
      if entry.toString.toLowerCase endsWith ".jar"
      pdesc <- loadDescription(entry)
      if !(ignoring contains pdesc.name)
    } alljars += entry

    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: AnyClass, global: Global): Plugin = {
    //println("instantiating "+clazz)
    //println(clazz.getDeclaredConstructors)
    val constructor = clazz.getConstructor(classOf[Global])
    constructor.newInstance(global).asInstanceOf[Plugin]
  }
}