2009-07-27 01:15:02 +02:00
|
|
|
/* sbt -- Simple Build Tool
|
2010-06-14 04:59:29 +02:00
|
|
|
* Copyright 2009, 2010 Mark Harrah
|
2009-07-27 01:15:02 +02:00
|
|
|
*/
|
2010-06-14 04:59:29 +02:00
|
|
|
package sbt
|
|
|
|
|
package classfile
|
2009-07-27 01:15:02 +02:00
|
|
|
|
|
|
|
|
import scala.collection.mutable
|
|
|
|
|
import mutable.{ArrayBuffer, Buffer}
|
2011-05-28 02:42:46 +02:00
|
|
|
import scala.annotation.tailrec
|
2009-07-27 01:15:02 +02:00
|
|
|
import java.io.File
|
2010-03-28 06:05:40 +02:00
|
|
|
import java.lang.annotation.Annotation
|
|
|
|
|
import java.lang.reflect.Method
|
|
|
|
|
import java.lang.reflect.Modifier.{STATIC, PUBLIC, ABSTRACT}
|
2009-07-27 01:15:02 +02:00
|
|
|
|
2009-07-27 21:38:09 +02:00
|
|
|
private[sbt] object Analyze
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
2011-05-15 00:21:41 +02:00
|
|
|
def apply[T](outputDirectory: File, sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit)(compile: => Unit)
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
2011-05-28 02:42:46 +02:00
|
|
|
val sourceMap = sources.toSet[File].groupBy(_.getName)
|
2011-05-15 00:21:41 +02:00
|
|
|
val classesFinder = PathFinder(outputDirectory) ** "*.class"
|
2009-07-27 01:15:02 +02:00
|
|
|
val existingClasses = classesFinder.get
|
2010-03-28 06:05:40 +02:00
|
|
|
|
2010-07-15 01:24:50 +02:00
|
|
|
def load(tpe: String, errMsg: => Option[String]): Option[Class[_]] =
|
2010-03-28 06:05:40 +02:00
|
|
|
try { Some(Class.forName(tpe, false, loader)) }
|
2010-07-15 01:24:50 +02:00
|
|
|
catch { case e => errMsg.foreach(msg => log.warn(msg + " : " +e.toString)); None }
|
2010-03-28 06:05:40 +02:00
|
|
|
|
2009-07-27 01:15:02 +02:00
|
|
|
// runs after compilation
|
|
|
|
|
def analyze()
|
|
|
|
|
{
|
2011-05-15 00:21:41 +02:00
|
|
|
val allClasses = Set(classesFinder.get: _*)
|
2010-07-15 01:24:50 +02:00
|
|
|
val newClasses = allClasses -- existingClasses
|
2009-07-27 01:15:02 +02:00
|
|
|
|
2011-05-15 00:21:41 +02:00
|
|
|
val productToSource = new mutable.HashMap[File, File]
|
|
|
|
|
val sourceToClassFiles = new mutable.HashMap[File, Buffer[ClassFile]]
|
2010-03-28 06:05:40 +02:00
|
|
|
|
2009-07-27 01:15:02 +02:00
|
|
|
// parse class files and assign classes to sources. This must be done before dependencies, since the information comes
|
|
|
|
|
// as class->class dependencies that must be mapped back to source->class dependencies using the source+class assignment
|
|
|
|
|
for(newClass <- newClasses;
|
2011-05-15 00:21:41 +02:00
|
|
|
classFile = Parser(newClass);
|
|
|
|
|
sourceFile <- classFile.sourceFile orElse guessSourceName(newClass.getName);
|
2011-03-10 00:04:53 +01:00
|
|
|
source <- guessSourcePath(sourceMap, classFile, log))
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
|
|
|
|
analysis.beginSource(source)
|
2011-05-15 00:21:41 +02:00
|
|
|
analysis.generatedClass(source, newClass)
|
|
|
|
|
productToSource(newClass) = source
|
2009-07-27 01:15:02 +02:00
|
|
|
sourceToClassFiles.getOrElseUpdate(source, new ArrayBuffer[ClassFile]) += classFile
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get class to class dependencies and map back to source to class dependencies
|
|
|
|
|
for( (source, classFiles) <- sourceToClassFiles )
|
|
|
|
|
{
|
2009-07-27 15:52:38 +02:00
|
|
|
def processDependency(tpe: String)
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
2010-06-14 04:59:29 +02:00
|
|
|
trapAndLog(log)
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
2010-07-15 01:24:50 +02:00
|
|
|
val loaded = load(tpe, Some("Problem processing dependencies of source " + source))
|
2010-06-14 04:59:29 +02:00
|
|
|
for(clazz <- loaded; file <- ErrorHandling.convert(IO.classLocationFile(clazz)).right)
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
2010-09-18 03:38:40 +02:00
|
|
|
val name = clazz.getName
|
2009-07-27 17:13:55 +02:00
|
|
|
if(file.isDirectory)
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
2009-07-27 17:13:55 +02:00
|
|
|
val resolved = resolveClassFile(file, tpe)
|
|
|
|
|
assume(resolved.exists, "Resolved class file " + resolved + " from " + source + " did not exist")
|
2011-05-15 00:21:41 +02:00
|
|
|
if(file == outputDirectory)
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
2011-05-15 00:21:41 +02:00
|
|
|
productToSource.get(resolved) match
|
2009-07-27 17:13:55 +02:00
|
|
|
{
|
|
|
|
|
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source)
|
2010-09-18 03:38:40 +02:00
|
|
|
case None => analysis.binaryDependency(resolved, clazz.getName, source)
|
2009-07-27 17:13:55 +02:00
|
|
|
}
|
2009-07-27 01:15:02 +02:00
|
|
|
}
|
2009-07-27 17:13:55 +02:00
|
|
|
else
|
2010-09-18 03:38:40 +02:00
|
|
|
analysis.binaryDependency(resolved, name, source)
|
2009-07-27 01:15:02 +02:00
|
|
|
}
|
|
|
|
|
else
|
2010-09-18 03:38:40 +02:00
|
|
|
analysis.binaryDependency(file, name, source)
|
2009-07-27 01:15:02 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
classFiles.flatMap(_.types).foreach(processDependency)
|
2011-05-15 00:21:41 +02:00
|
|
|
readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") )))
|
2009-07-27 01:15:02 +02:00
|
|
|
analysis.endSource(source)
|
|
|
|
|
}
|
2011-06-15 01:32:36 +02:00
|
|
|
|
|
|
|
|
for( source <- sources filterNot sourceToClassFiles.keySet ) {
|
|
|
|
|
analysis.beginSource(source)
|
|
|
|
|
analysis.api(source, new xsbti.api.SourceAPI(Array(), Array()))
|
|
|
|
|
analysis.endSource(source)
|
|
|
|
|
}
|
2009-07-27 01:15:02 +02:00
|
|
|
}
|
|
|
|
|
|
2010-07-15 01:24:50 +02:00
|
|
|
compile
|
|
|
|
|
analyze()
|
|
|
|
|
}
|
|
|
|
|
private def trapAndLog(log: Logger)(execute: => Unit)
|
|
|
|
|
{
|
|
|
|
|
try { execute }
|
|
|
|
|
catch { case e => log.trace(e); log.error(e.toString) }
|
2009-07-27 01:15:02 +02:00
|
|
|
}
|
2010-05-11 00:52:22 +02:00
|
|
|
private def guessSourceName(name: String) = Some( takeToDollar(trimClassExt(name)) )
|
|
|
|
|
private def takeToDollar(name: String) =
|
|
|
|
|
{
|
|
|
|
|
val dollar = name.indexOf('$')
|
|
|
|
|
if(dollar < 0) name else name.substring(0, dollar)
|
|
|
|
|
}
|
|
|
|
|
private final val ClassExt = ".class"
|
|
|
|
|
private def trimClassExt(name: String) = if(name.endsWith(ClassExt)) name.substring(0, name.length - ClassExt.length) else name
|
|
|
|
|
private def resolveClassFile(file: File, className: String): File = (file /: (className.replace('.','/') + ClassExt).split("/"))(new File(_, _))
|
2011-05-28 02:42:46 +02:00
|
|
|
private def guessSourcePath(sourceNameMap: Map[String, Set[File]], classFile: ClassFile, log: Logger) =
|
2009-07-27 01:15:02 +02:00
|
|
|
{
|
2009-07-27 14:51:29 +02:00
|
|
|
val classNameParts = classFile.className.split("""\.""")
|
2011-03-10 00:04:53 +01:00
|
|
|
val pkg = classNameParts.init
|
|
|
|
|
val simpleClassName = classNameParts.last
|
2009-07-27 14:51:29 +02:00
|
|
|
val sourceFileName = classFile.sourceFile.getOrElse(simpleClassName.takeWhile(_ != '$').mkString("", "", ".java"))
|
2011-03-10 00:04:53 +01:00
|
|
|
val candidates = findSource(sourceNameMap, pkg.toList, sourceFileName)
|
2009-07-27 01:15:02 +02:00
|
|
|
candidates match
|
|
|
|
|
{
|
2009-07-27 14:51:29 +02:00
|
|
|
case Nil => log.warn("Could not determine source for class " + classFile.className)
|
2009-07-27 01:15:02 +02:00
|
|
|
case head :: Nil => ()
|
2011-03-10 00:04:53 +01:00
|
|
|
case _ => log.warn("Multiple sources matched for class " + classFile.className + ": " + candidates.mkString(", "))
|
2009-07-27 01:15:02 +02:00
|
|
|
}
|
|
|
|
|
candidates
|
|
|
|
|
}
|
2011-03-10 00:04:53 +01:00
|
|
|
private def findSource(sourceNameMap: Map[String, Iterable[File]], pkg: List[String], sourceFileName: String): List[File] =
|
2011-05-28 02:42:46 +02:00
|
|
|
refine( (sourceNameMap get sourceFileName).toList.flatten.map { x => (x,x.getParentFile) }, pkg.reverse)
|
2011-03-10 00:04:53 +01:00
|
|
|
|
2011-05-28 02:42:46 +02:00
|
|
|
@tailrec private def refine(sources: List[(File, File)], pkgRev: List[String]): List[File] =
|
2011-03-10 00:04:53 +01:00
|
|
|
{
|
|
|
|
|
def make = sources.map(_._1)
|
|
|
|
|
if(sources.isEmpty || sources.tail.isEmpty)
|
|
|
|
|
make
|
|
|
|
|
else
|
|
|
|
|
pkgRev match
|
|
|
|
|
{
|
|
|
|
|
case Nil => shortest(make)
|
|
|
|
|
case x :: xs =>
|
|
|
|
|
val retain = sources flatMap { case (src, pre) =>
|
|
|
|
|
if(pre != null && pre.getName == x)
|
|
|
|
|
(src, pre.getParentFile) :: Nil
|
|
|
|
|
else
|
|
|
|
|
Nil
|
|
|
|
|
}
|
|
|
|
|
refine(retain, xs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private def shortest(files: List[File]): List[File] =
|
|
|
|
|
if(files.isEmpty) files
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
val fs = files.groupBy(distanceToRoot(0))
|
|
|
|
|
fs(fs.keys.min)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def distanceToRoot(acc: Int)(file: File): Int =
|
|
|
|
|
if(file == null) acc else distanceToRoot(acc + 1)(file.getParentFile)
|
|
|
|
|
|
2009-07-27 01:15:02 +02:00
|
|
|
private def isTopLevel(classFile: ClassFile) = classFile.className.indexOf('$') < 0
|
2010-03-28 06:05:40 +02:00
|
|
|
private lazy val unit = classOf[Unit]
|
|
|
|
|
private lazy val strArray = List(classOf[Array[String]])
|
|
|
|
|
|
|
|
|
|
private def isMain(method: Method): Boolean =
|
|
|
|
|
method.getName == "main" &&
|
|
|
|
|
isMain(method.getModifiers) &&
|
|
|
|
|
method.getReturnType == unit &&
|
|
|
|
|
method.getParameterTypes.toList == strArray
|
|
|
|
|
private def isMain(modifiers: Int): Boolean = (modifiers & mainModifiers) == mainModifiers && (modifiers & notMainModifiers) == 0
|
|
|
|
|
|
|
|
|
|
private val mainModifiers = STATIC | PUBLIC
|
|
|
|
|
private val notMainModifiers = ABSTRACT
|
2009-07-27 01:15:02 +02:00
|
|
|
}
|