Don't use runtime universe to discover autoImport

The previous implementation was using the Scala runtime universe to
check whether a plugin had or not an `autoImport` member. This is a bad
idea for the following reasons:

* The first time you use it, you class load the whole Scalac compiler
  universe. Not efficient. Measurements say this is about a second.
* There is a small overhead of going through the reflection API.

There exists a better approach that consists in checking if `autoImport`
exists with pure Java reflection. Since the class is already class
loaded, we check for:

* A class file named after the plugin FQN that includes `autoImport$` at
  the end, which means that an object named `autoImport` exists.
* A field in the plugin class that is named `autoImport`.

This complies with the plugin sbt specification:

http://www.scala-sbt.org/1.0/docs/Plugins.html#Controlling+the+import+with+autoImport
This commit is contained in:
jvican 2017-03-14 15:40:45 +01:00
parent 27b4faebed
commit a36d8401e1
No known key found for this signature in database
GPG Key ID: 42DAFA0F112E8050
1 changed files with 25 additions and 9 deletions

View File

@ -349,18 +349,34 @@ ${listConflicts(conflicting)}""")
case ap: AutoPlugin => model(ap)
}
private val autoImport = "autoImport"
/** Determines whether a plugin has a stable autoImport member by:
*
* 1. Checking whether there exists a public field.
* 2. Checking whether there exists a public object.
*
* The above checks work for inherited members too.
*
* @param ap The found plugin.
* @param loader The plugin loader.
* @return True if plugin has a stable member `autoImport`, otherwise false.
*/
private[sbt] def hasAutoImportGetter(ap: AutoPlugin, loader: ClassLoader): Boolean = {
import reflect.runtime.{ universe => ru }
import java.lang.reflect.Field
import scala.util.control.Exception.catching
val m = ru.runtimeMirror(loader)
val im = m.reflect(ap)
val hasGetterOpt = catching(classOf[ScalaReflectionException]) opt {
im.symbol.asType.toType.decl(ru.TermName("autoImport")) match {
case ru.NoSymbol => false
case sym => sym.asTerm.isGetter || sym.asTerm.isModule
}
// Make sure that we don't detect user-defined methods called autoImport
def existsAutoImportVal(clazz: Class[_]): Option[Field] = {
catching(classOf[NoSuchFieldException])
.opt(clazz.getDeclaredField(autoImport))
.orElse(Option(clazz.getSuperclass).flatMap(existsAutoImportVal))
}
hasGetterOpt getOrElse false
val pluginClazz = ap.getClass
existsAutoImportVal(pluginClazz)
.orElse(catching(classOf[ClassNotFoundException]).opt(
Class.forName(s"${pluginClazz.getName}$autoImport$$", false, loader)))
.isDefined
}
/** Debugging method to time how long it takes to run various compilation tasks. */