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
|
2010-03-23 13:30:53 +01:00
|
|
|
import java.util.zip.ZipFile
|
2009-09-04 05:40:47 +02:00
|
|
|
import xsbti.AnalysisCallback
|
2009-08-17 16:51:43 +02:00
|
|
|
|
2009-09-04 05:40:47 +02:00
|
|
|
object Analyzer
|
|
|
|
|
{
|
|
|
|
|
def name = "xsbt-analyzer"
|
|
|
|
|
}
|
2010-06-16 02:38:18 +02:00
|
|
|
final class Analyzer(val global: Global, val callback: AnalysisCallback) extends Compat
|
2009-08-17 16:51:43 +02:00
|
|
|
{
|
|
|
|
|
import global._
|
2009-08-19 05:25:34 +02:00
|
|
|
|
2009-09-04 05:40:47 +02:00
|
|
|
def newPhase(prev: Phase): Phase = new AnalyzerPhase(prev)
|
2009-08-17 16:51:43 +02:00
|
|
|
private class AnalyzerPhase(prev: Phase) extends Phase(prev)
|
|
|
|
|
{
|
2009-11-16 14:46:47 +01:00
|
|
|
override def description = "Extracts dependency information, finds concrete instances of provided superclasses, and application entry points."
|
2009-09-04 05:40:47 +02:00
|
|
|
def name = Analyzer.name
|
2009-08-17 16:51:43 +02:00
|
|
|
def run
|
|
|
|
|
{
|
|
|
|
|
val outputDirectory = new File(global.settings.outdir.value)
|
2009-08-19 05:25:34 +02:00
|
|
|
|
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
|
|
|
|
|
{
|
2010-03-23 13:30:53 +01:00
|
|
|
case ze: ZipArchive#Entry => callback.jarDependency(new File(archive(ze).getName), sourceFile)
|
2009-08-17 16:51:43 +02:00
|
|
|
case pf: PlainFile => callback.classDependency(pf.file, sourceFile)
|
|
|
|
|
case _ => ()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case None => ()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
callback.sourceDependency(onSource.file, sourceFile)
|
|
|
|
|
}
|
2009-08-19 05:25:34 +02:00
|
|
|
|
2009-08-17 16:51:43 +02:00
|
|
|
// 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)
|
|
|
|
|
{
|
2010-03-23 01:42:59 +01:00
|
|
|
if(isTopLevelModule(sym) && linkedClass(sym) == NoSymbol)
|
2009-08-17 16:51:43 +02:00
|
|
|
addGenerated(false)
|
|
|
|
|
addGenerated(true)
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
addGenerated(false)
|
|
|
|
|
}
|
|
|
|
|
callback.endSource(sourceFile)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-08-19 05:25:34 +02:00
|
|
|
|
|
|
|
|
private def classForName(name: String) =
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if(name.indexOf('.') < 0)
|
|
|
|
|
{
|
|
|
|
|
val sym = definitions.EmptyPackageClass.info.member(newTypeName(name))
|
|
|
|
|
if(sym != NoSymbol) Some( sym ) else { callback.superclassNotFound(name); None }
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
Some( global.definitions.getClass(newTermName(name)) )
|
|
|
|
|
}
|
|
|
|
|
catch { case fe: scala.tools.nsc.FatalError => callback.superclassNotFound(name); None }
|
|
|
|
|
}
|
2009-08-17 16:51:43 +02:00
|
|
|
private def classFile(sym: Symbol): Option[AbstractFile] =
|
|
|
|
|
{
|
|
|
|
|
import scala.tools.nsc.symtab.Flags
|
2010-01-26 15:10:42 +01:00
|
|
|
val name = flatname(sym, finder.classSeparator) + moduleSuffix(sym)
|
2009-11-02 03:21:59 +01:00
|
|
|
finder.findClass(name) orElse {
|
|
|
|
|
if(isTopLevelModule(sym))
|
|
|
|
|
{
|
2010-03-23 01:42:59 +01:00
|
|
|
val linked = linkedClass(sym)
|
2009-11-02 03:21:59 +01:00
|
|
|
if(linked == NoSymbol)
|
|
|
|
|
None
|
|
|
|
|
else
|
|
|
|
|
classFile(linked)
|
|
|
|
|
}
|
2009-08-17 16:51:43 +02:00
|
|
|
else
|
2009-11-02 03:21:59 +01:00
|
|
|
None
|
2009-08-17 16:51:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
2010-01-26 15:10:42 +01:00
|
|
|
// doesn't seem to be in 2.7.7, so copied from GenJVM to here
|
|
|
|
|
private def moduleSuffix(sym: Symbol) =
|
|
|
|
|
if (sym.hasFlag(Flags.MODULE) && !sym.isMethod && !sym.isImplClass && !sym.hasFlag(Flags.JAVA)) "$" else "";
|
|
|
|
|
private def flatname(s: Symbol, separator: Char) =
|
2010-03-23 01:42:59 +01:00
|
|
|
atPhase(currentRun.flattenPhase.next) { nameString(s, separator) }
|
2010-01-26 15:10:42 +01:00
|
|
|
|
2009-08-17 16:51:43 +02:00
|
|
|
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 =
|
2010-01-26 15:10:42 +01:00
|
|
|
new File(outputDirectory, flatname(s, File.separatorChar) + (if(separatorRequired) "$" else "") + ".class")
|
2009-08-19 05:25:34 +02:00
|
|
|
|
2009-11-02 03:21:59 +01:00
|
|
|
// required because the 2.8 way to find a class is:
|
|
|
|
|
// classPath.findClass(name).flatMap(_.binary)
|
|
|
|
|
// and the 2.7 way is:
|
|
|
|
|
// val entry = classPath.root.find(name, false)
|
|
|
|
|
// if(entry eq null) None else Some(entry.classFile)
|
|
|
|
|
private lazy val finder = try { new LegacyFinder } catch { case _ => new NewFinder }
|
|
|
|
|
private trait ClassFinder
|
|
|
|
|
{
|
2010-01-10 00:22:58 +01:00
|
|
|
def classSeparator: Char
|
2009-11-02 03:21:59 +01:00
|
|
|
def findClass(name: String): Option[AbstractFile]
|
|
|
|
|
}
|
|
|
|
|
private class NewFinder extends ClassFinder
|
|
|
|
|
{
|
2010-02-05 04:08:17 +01:00
|
|
|
private class Compat27 { def findClass(name: String) = this; def flatMap(f: Compat27 => AnyRef) = Predef.error("Should never be called"); def binary = None }
|
|
|
|
|
private implicit def compat27(any: AnyRef): Compat27 = new Compat27
|
2010-01-10 00:22:58 +01:00
|
|
|
|
|
|
|
|
def classSeparator = '.' // 2.8 uses . when searching for classes
|
2009-11-02 03:21:59 +01:00
|
|
|
def findClass(name: String): Option[AbstractFile] =
|
2010-01-10 00:22:58 +01:00
|
|
|
classPath.findClass(name).flatMap(_.binary.asInstanceOf[Option[AbstractFile]])
|
2009-11-02 03:21:59 +01:00
|
|
|
}
|
|
|
|
|
private class LegacyFinder extends ClassFinder
|
|
|
|
|
{
|
2010-02-05 04:08:17 +01:00
|
|
|
private class Compat28 { def root: Compat28 = invalid; def find(n: String, b: Boolean) = this; def classFile = invalid; def invalid = Predef.error("Should never be called") }
|
|
|
|
|
private implicit def compat28(any: AnyRef): Compat28 = new Compat28
|
2010-01-10 00:22:58 +01:00
|
|
|
|
|
|
|
|
def classSeparator = File.separatorChar // 2.7 uses / or \ when searching for classes
|
|
|
|
|
private val root = classPath.root
|
2009-11-02 03:21:59 +01:00
|
|
|
def findClass(name: String): Option[AbstractFile] =
|
|
|
|
|
{
|
2010-01-10 00:22:58 +01:00
|
|
|
val entry = root.find(name, false)
|
|
|
|
|
if(entry eq null) None else Some(entry.classFile)
|
2009-11-02 03:21:59 +01:00
|
|
|
}
|
|
|
|
|
}
|
2010-06-16 02:38:18 +02:00
|
|
|
}
|
|
|
|
|
abstract class Compat
|
|
|
|
|
{
|
|
|
|
|
val global: Global
|
|
|
|
|
import global._
|
|
|
|
|
def archive(s: ZipArchive#Entry): ZipFile = s.getArchive
|
|
|
|
|
def nameString(s: Symbol): String = s.fullNameString
|
|
|
|
|
def nameString(s: Symbol, sep: Char): String = s.fullNameString(sep)
|
|
|
|
|
def isExistential(s: Symbol): Boolean = s.isExistential
|
|
|
|
|
def isNonClassType(s: Symbol): Boolean = s.isTypeMember
|
2010-03-23 01:42:59 +01:00
|
|
|
|
2010-06-16 02:38:18 +02:00
|
|
|
def linkedClass(s: Symbol): Symbol = s.linkedClassOfModule
|
2010-02-05 04:08:17 +01:00
|
|
|
|
2010-06-16 02:38:18 +02:00
|
|
|
/** After 2.8.0.Beta1, fullNameString was renamed fullName.
|
|
|
|
|
* linkedClassOfModule was renamed companionClass. */
|
|
|
|
|
private implicit def symCompat(sym: Symbol): SymCompat = new SymCompat(sym)
|
|
|
|
|
private final class SymCompat(s: Symbol)
|
|
|
|
|
{
|
|
|
|
|
def fullNameString = s.fullName; def fullName = sourceCompatibilityOnly
|
|
|
|
|
def fullNameString(sep: Char) = s.fullName(sep); def fullName(sep: Char) = sourceCompatibilityOnly
|
|
|
|
|
|
|
|
|
|
def isExistential: Boolean = s.isExistentiallyBound; def isExistentiallyBound = sourceCompatibilityOnly
|
|
|
|
|
def isTypeMember: Boolean = s.isNonClassType; def isNonClassType = sourceCompatibilityOnly
|
|
|
|
|
|
|
|
|
|
def linkedClassOfModule = s.companionClass; def companionClass = sourceCompatibilityOnly
|
|
|
|
|
// In 2.8, hasAttribute is renamed to hasAnnotation
|
|
|
|
|
def hasAnnotation(a: Symbol) = s.hasAttribute(a); def hasAttribute(a: Symbol) = sourceCompatibilityOnly
|
|
|
|
|
}
|
2010-03-28 06:05:40 +02:00
|
|
|
|
2010-06-16 02:38:18 +02:00
|
|
|
def hasAnnotation(s: Symbol)(ann: Symbol) = atPhase(currentRun.typerPhase) { s.hasAnnotation(ann) }
|
2010-03-28 06:05:40 +02:00
|
|
|
|
2010-06-16 02:38:18 +02:00
|
|
|
/** After 2.8.0.Beta1, getArchive was renamed archive.*/
|
|
|
|
|
private implicit def zipCompat(z: ZipArchive#Entry): ZipCompat = new ZipCompat(z)
|
|
|
|
|
private final class ZipCompat(z: ZipArchive#Entry)
|
|
|
|
|
{
|
|
|
|
|
def getArchive = z.archive; def archive = sourceCompatibilityOnly
|
2010-03-23 01:42:59 +01:00
|
|
|
}
|
2010-07-02 12:57:03 +02:00
|
|
|
private def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.")
|
2009-08-17 16:51:43 +02:00
|
|
|
}
|