2009-08-17 16:51:43 +02:00
|
|
|
/* sbt -- Simple Build Tool
|
|
|
|
|
* Copyright 2008, 2009 Mark Harrah
|
|
|
|
|
*/
|
|
|
|
|
package xsbt
|
|
|
|
|
|
|
|
|
|
import scala.tools.nsc.{io, plugins, symtab, Global, Phase}
|
|
|
|
|
import io.{AbstractFile, PlainFile, ZipArchive}
|
|
|
|
|
import plugins.{Plugin, PluginComponent}
|
|
|
|
|
import symtab.Flags
|
|
|
|
|
import scala.collection.mutable.{HashMap, HashSet, Map, Set}
|
|
|
|
|
|
|
|
|
|
import java.io.File
|
|
|
|
|
import xsbti.{AnalysisCallback, AnalysisCallbackContainer}
|
|
|
|
|
|
|
|
|
|
class Analyzer(val global: Global) extends Plugin
|
|
|
|
|
{
|
|
|
|
|
val callback = global.asInstanceOf[AnalysisCallbackContainer].analysisCallback
|
|
|
|
|
|
|
|
|
|
import global._
|
|
|
|
|
|
2009-08-18 06:51:08 +02:00
|
|
|
val name = "xsbt-analyze"
|
2009-08-17 16:51:43 +02:00
|
|
|
val description = "A plugin to find all concrete instances of a given class and extract dependency information."
|
|
|
|
|
val components = List[PluginComponent](Component)
|
|
|
|
|
|
|
|
|
|
/* ================================================== */
|
|
|
|
|
// These two templates abuse scope for source compatibility between Scala 2.7.x and 2.8.x so that a single
|
|
|
|
|
// sbt codebase compiles with both series of versions.
|
|
|
|
|
// In 2.8.x, PluginComponent.runsAfter has type List[String] and the method runsBefore is defined on
|
|
|
|
|
// PluginComponent with default value Nil.
|
|
|
|
|
// In 2.7.x, runsBefore does not exist on PluginComponent and PluginComponent.runsAfter has type String.
|
|
|
|
|
//
|
|
|
|
|
// Therefore, in 2.8.x, object runsBefore is shadowed by PluginComponent.runsBefore (which is Nil) and so
|
|
|
|
|
// afterPhase :: runsBefore
|
|
|
|
|
// is equivalent to List[String](afterPhase)
|
|
|
|
|
// In 2.7.x, object runsBefore is not shadowed and so runsAfter has type String.
|
|
|
|
|
private object runsBefore { def :: (s: String) = s }
|
|
|
|
|
private abstract class CompatiblePluginComponent(afterPhase: String) extends PluginComponent
|
|
|
|
|
{
|
|
|
|
|
override val runsAfter = afterPhase :: runsBefore
|
|
|
|
|
}
|
|
|
|
|
/* ================================================== */
|
|
|
|
|
|
|
|
|
|
private object Component extends CompatiblePluginComponent("jvm")
|
|
|
|
|
{
|
|
|
|
|
val global = Analyzer.this.global
|
|
|
|
|
val phaseName = Analyzer.this.name
|
|
|
|
|
def newPhase(prev: Phase) = new AnalyzerPhase(prev)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class AnalyzerPhase(prev: Phase) extends Phase(prev)
|
|
|
|
|
{
|
|
|
|
|
def name = Analyzer.this.name
|
|
|
|
|
def run
|
|
|
|
|
{
|
|
|
|
|
val outputDirectory = new File(global.settings.outdir.value)
|
|
|
|
|
val superclassNames = callback.superclassNames.map(newTermName)
|
|
|
|
|
val superclassesAll =
|
|
|
|
|
for(name <- superclassNames) yield
|
|
|
|
|
{
|
|
|
|
|
try { Some(global.definitions.getClass(name)) }
|
|
|
|
|
catch { case fe: scala.tools.nsc.FatalError => callback.superclassNotFound(name.toString); None }
|
|
|
|
|
}
|
|
|
|
|
val superclasses = superclassesAll.filter(_.isDefined).map(_.get)
|
2009-08-18 16:25:43 +02:00
|
|
|
//println("Superclass names: " + superclassNames.mkString(", ") + "\n\tall: " + superclasses.mkString(", "))
|
2009-08-17 16:51:43 +02:00
|
|
|
|
|
|
|
|
for(unit <- currentRun.units)
|
|
|
|
|
{
|
|
|
|
|
// build dependencies structure
|
|
|
|
|
val sourceFile = unit.source.file.file
|
|
|
|
|
callback.beginSource(sourceFile)
|
|
|
|
|
for(on <- unit.depends)
|
|
|
|
|
{
|
|
|
|
|
val onSource = on.sourceFile
|
|
|
|
|
if(onSource == null)
|
|
|
|
|
{
|
|
|
|
|
classFile(on) match
|
|
|
|
|
{
|
|
|
|
|
case Some(f) =>
|
|
|
|
|
{
|
|
|
|
|
f match
|
|
|
|
|
{
|
|
|
|
|
case ze: ZipArchive#Entry => callback.jarDependency(new File(ze.getArchive.getName), sourceFile)
|
|
|
|
|
case pf: PlainFile => callback.classDependency(pf.file, sourceFile)
|
|
|
|
|
case _ => ()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case None => ()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
callback.sourceDependency(onSource.file, sourceFile)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// find subclasses and modules with main methods
|
|
|
|
|
for(clazz @ ClassDef(mods, n, _, _) <- unit.body)
|
|
|
|
|
{
|
|
|
|
|
val sym = clazz.symbol
|
|
|
|
|
if(sym != NoSymbol && mods.isPublic && !mods.isAbstract && !mods.isTrait &&
|
|
|
|
|
!sym.isImplClass && sym.isStatic && !sym.isNestedClass)
|
|
|
|
|
{
|
|
|
|
|
val isModule = sym.isModuleClass
|
|
|
|
|
for(superclass <- superclasses.filter(sym.isSubClass))
|
|
|
|
|
callback.foundSubclass(sourceFile, sym.fullNameString, superclass.fullNameString, isModule)
|
|
|
|
|
if(isModule && hasMainMethod(sym))
|
|
|
|
|
callback.foundApplication(sourceFile, sym.fullNameString)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// build list of generated classes
|
|
|
|
|
for(iclass <- unit.icode)
|
|
|
|
|
{
|
|
|
|
|
val sym = iclass.symbol
|
|
|
|
|
def addGenerated(separatorRequired: Boolean)
|
|
|
|
|
{
|
|
|
|
|
val classFile = fileForClass(outputDirectory, sym, separatorRequired)
|
|
|
|
|
if(classFile.exists)
|
|
|
|
|
callback.generatedClass(sourceFile, classFile)
|
|
|
|
|
}
|
|
|
|
|
if(sym.isModuleClass && !sym.isImplClass)
|
|
|
|
|
{
|
|
|
|
|
if(isTopLevelModule(sym) && sym.linkedClassOfModule == NoSymbol)
|
|
|
|
|
addGenerated(false)
|
|
|
|
|
addGenerated(true)
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
addGenerated(false)
|
|
|
|
|
}
|
|
|
|
|
callback.endSource(sourceFile)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def classFile(sym: Symbol): Option[AbstractFile] =
|
|
|
|
|
{
|
|
|
|
|
import scala.tools.nsc.symtab.Flags
|
|
|
|
|
val name = sym.fullNameString(java.io.File.separatorChar) + (if (sym.hasFlag(Flags.MODULE)) "$" else "")
|
|
|
|
|
val entry = classPath.root.find(name, false)
|
|
|
|
|
if (entry ne null)
|
|
|
|
|
Some(entry.classFile)
|
|
|
|
|
else if(isTopLevelModule(sym))
|
|
|
|
|
{
|
|
|
|
|
val linked = sym.linkedClassOfModule
|
|
|
|
|
if(linked == NoSymbol)
|
|
|
|
|
None
|
|
|
|
|
else
|
|
|
|
|
classFile(linked)
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def isTopLevelModule(sym: Symbol): Boolean =
|
|
|
|
|
atPhase (currentRun.picklerPhase.next) {
|
|
|
|
|
sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass
|
|
|
|
|
}
|
|
|
|
|
private def fileForClass(outputDirectory: File, s: Symbol, separatorRequired: Boolean): File =
|
|
|
|
|
fileForClass(outputDirectory, s, separatorRequired, ".class")
|
|
|
|
|
private def fileForClass(outputDirectory: File, s: Symbol, separatorRequired: Boolean, postfix: String): File =
|
|
|
|
|
{
|
|
|
|
|
if(s.owner.isPackageClass && s.isPackageClass)
|
|
|
|
|
new File(packageFile(outputDirectory, s), postfix)
|
|
|
|
|
else
|
|
|
|
|
fileForClass(outputDirectory, s.owner.enclClass, true, s.simpleName + (if(separatorRequired) "$" else "") + postfix)
|
|
|
|
|
}
|
|
|
|
|
private def packageFile(outputDirectory: File, s: Symbol): File =
|
|
|
|
|
{
|
|
|
|
|
if(s.isEmptyPackageClass || s.isRoot)
|
|
|
|
|
outputDirectory
|
|
|
|
|
else
|
|
|
|
|
new File(packageFile(outputDirectory, s.owner.enclClass), s.simpleName.toString)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def hasMainMethod(sym: Symbol): Boolean =
|
|
|
|
|
{
|
|
|
|
|
val main = sym.info.nonPrivateMember(newTermName("main"))//nme.main)
|
|
|
|
|
main.tpe match
|
|
|
|
|
{
|
|
|
|
|
case OverloadedType(pre, alternatives) => alternatives.exists(alt => isVisible(alt) && isMainType(pre.memberType(alt)))
|
|
|
|
|
case tpe => isVisible(main) && isMainType(main.owner.thisType.memberType(main))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private def isVisible(sym: Symbol) = sym != NoSymbol && sym.isPublic && !sym.isDeferred
|
|
|
|
|
private def isMainType(tpe: Type) =
|
|
|
|
|
{
|
|
|
|
|
tpe match
|
|
|
|
|
{
|
|
|
|
|
// singleArgument is of type Symbol in 2.8.0 and type Type in 2.7.x
|
|
|
|
|
case MethodType(List(singleArgument), result) => isUnitType(result) && isStringArray(singleArgument)
|
|
|
|
|
case _ => false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private lazy val StringArrayType = appliedType(definitions.ArrayClass.typeConstructor, definitions.StringClass.tpe :: Nil)
|
|
|
|
|
// isStringArray is overloaded to handle the incompatibility between 2.7.x and 2.8.0
|
|
|
|
|
private def isStringArray(tpe: Type): Boolean = tpe.typeSymbol == StringArrayType.typeSymbol
|
|
|
|
|
private def isStringArray(sym: Symbol): Boolean = isStringArray(sym.tpe)
|
|
|
|
|
private def isUnitType(tpe: Type) = tpe.typeSymbol == definitions.UnitClass
|
|
|
|
|
}
|