summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/plugins/Plugin.scala
blob: 093f8285e1cd9316470ef6ba70174db1a35b6bf3 (plain) (tree)
1
2
3
4
5
6
7
8
9
                            
                                

                    
 

                       
 
                       
                              

                                 
                    
 




                                                              
                          
       


                           














                                                                   
                                                                  
                             
                                                                    




                                                                    

                                                                    



                                        




                           

               

                                             


                                                        
                                                             
                                                       


                                                       

   
                                                         
                                                 
     




                                                                         
 
           


                                      





                                             
       


                         
                                  
     
 

                          
                                                   


                                                           


                                                                      


                                                                                   





                                                                                                    
     
 

                                                              
                                                             

                                                                   



                                             
   
                                  
                                    
                                                           

                                      
                                     
                                        
                            
 
                                   
                                               

   


                                                     
                                                              

                                                          

   
/* NSC -- new Scala compiler
 * Copyright 2007-2013 LAMP/EPFL
 * @author Lex Spoon
 */

package scala.tools.nsc
package plugins

import io.{ Path, Jar }
import java.net.URLClassLoader
import java.util.jar.JarFile
import java.util.zip.ZipException
import scala.xml.XML

/** 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

  /** 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
}

/** ...
 *
 *  @author Lex Spoon
 *  @version 1.0, 2007-5-21
 */
object Plugin {

  private val PluginXML = "scalac-plugin.xml"

  /** Create a class loader with the specified file plus
   *  the loader that loaded the Scala compiler.
   */
  private def loaderFor(jarfiles: Seq[Path]): ClassLoader = {
    val compilerLoader = classOf[Plugin].getClassLoader
    val jarurls = jarfiles map (_.toURL)

    new URLClassLoader(jarurls.toArray, compilerLoader)
  }

  /** Try to load a plugin description from the specified
   *  file, returning `None` if it does not work.
   */
  private def loadDescription(jarfile: Path): Option[PluginDescription] =
    // XXX Return to this once we have some ARM support
    if (!jarfile.exists) None
    else try {
      val jar = new JarFile(jarfile.jfile)

      try {
        jar getEntry PluginXML match {
          case null  => None
          case entry =>
            val in = jar getInputStream entry
            val packXML = XML load in
            in.close()

            PluginDescription fromXML packXML
        }
      }
      finally jar.close()
    }
    catch {
      case _: ZipException => None
    }

  type AnyClass = Class[_]

  /** Loads a plugin class from the named jar file.
   *
   *  @return `None` if the jar file has no plugin in it or
   *                 if the plugin is badly formed.
   */
  def loadFrom(jarfile: Path, loader: ClassLoader): Option[AnyClass] =
    loadDescription(jarfile) match {
      case None =>
        println("Warning: could not load descriptor for plugin %s".format(jarfile))
        None
      case Some(pdesc) =>
        try Some(loader loadClass pdesc.classname) catch {
        case _: Exception =>
          println("Warning: class not found for plugin in %s (%s)".format(jarfile, pdesc.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 `ignoring`.
   *  A single classloader is created and used to load all of them.
   */
  def loadAllFrom(
    jars: List[Path],
    dirs: List[Path],
    ignoring: List[String]): List[AnyClass] =
  {
    val alljars = (jars ::: (for {
      dir <- dirs if dir.isDirectory
      entry <- dir.toDirectory.files.toList sortBy (_.name)
// was:      if Path.isJarOrZip(entry)
      if Jar.isJarOrZip(entry)
      pdesc <- loadDescription(entry)
      if !(ignoring contains pdesc.name)
    } yield entry)).distinct

    val loader = loaderFor(alljars)
    (alljars map (loadFrom(_, loader))).flatten
  }

  /** Instantiate a plugin class, given the class and
   *  the compiler it is to be used in.
   */
  def instantiate(clazz: AnyClass, global: Global): Plugin = {
    val constructor = clazz getConstructor classOf[Global]
    (constructor newInstance global).asInstanceOf[Plugin]
  }
}