sbt/util/classpath/ClasspathUtilities.scala

222 lines
8.4 KiB
Scala

/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt
package classpath
import java.io.File
import java.net.{URI, URL, URLClassLoader}
import java.util.Collections
import scala.collection.Set
import scala.collection.mutable.{HashSet, LinkedHashSet, ListBuffer}
import IO.{createTemporaryDirectory, write}
object ClasspathUtilities
{
def toClasspath(finder: PathFinder): Array[URL] = finder.getURLs
def toClasspath(paths: Iterable[Path]): Array[URL] = Path.getURLs(paths)
def toLoader(finder: PathFinder): ClassLoader = toLoader(finder.get)
def toLoader(finder: PathFinder, parent: ClassLoader): ClassLoader = toLoader(finder.get, parent)
def toLoader(paths: Iterable[Path]): ClassLoader = toLoader(paths, rootLoader)
def toLoader(paths: Iterable[Path], parent: ClassLoader): ClassLoader = new URLClassLoader(toClasspath(paths), parent)
lazy val rootLoader =
{
def parent(loader: ClassLoader): ClassLoader =
{
val p = loader.getParent
if(p eq null) loader else parent(p)
}
parent(getClass.getClassLoader)
}
final val AppClassPath = "app.class.path"
final val BootClassPath = "boot.class.path"
def createClasspathResources(classpath: Iterable[Path], instance: ScalaInstance, baseDir: Path): Iterable[Path] =
createClasspathResources(classpath ++ Path.fromFiles(instance.jars), Path.fromFiles(instance.jars), baseDir)
def createClasspathResources(appPaths: Iterable[Path], bootPaths: Iterable[Path], baseDir: Path): Iterable[Path] =
{
def writePaths(name: String, paths: Iterable[Path]): Unit =
write(baseDir / name asFile, Path.makeString(paths))
writePaths(AppClassPath, appPaths)
writePaths(BootClassPath, bootPaths)
appPaths ++ Seq(baseDir)
}
/** The client is responsible for cleaning up the temporary directory.*/
def makeLoader[T](classpath: Iterable[Path], instance: ScalaInstance): (ClassLoader, Path) =
makeLoader(classpath, instance.loader, instance)
/** The client is responsible for cleaning up the temporary directory.*/
def makeLoader[T](classpath: Iterable[Path], parent: ClassLoader, instance: ScalaInstance): (ClassLoader, Path) =
{
val dir = Path.fromFile(createTemporaryDirectory)
val modifiedClasspath = createClasspathResources(classpath, instance, dir)
(toLoader(modifiedClasspath, parent), dir)
}
private[sbt] def printSource(c: Class[_]) =
println(c.getName + " loader=" +c.getClassLoader + " location=" + IO.classLocationFile(c))
def isArchive(path: Path): Boolean = isArchive(path.asFile)
def isArchive(file: File): Boolean = isArchiveName(file.getName)
def isArchiveName(fileName: String) = fileName.endsWith(".jar") || fileName.endsWith(".zip")
// Partitions the given classpath into (jars, directories)
private[sbt] def separate(paths: Iterable[File]): (Iterable[File], Iterable[File]) = paths.partition(isArchive)
// Partitions the given classpath into (jars, directories)
private[sbt] def separatePaths(paths: Iterable[Path]) = separate(paths.map(_.asFile.getCanonicalFile))
private[sbt] def buildSearchPaths(classpath: Iterable[Path]): (collection.Set[File], collection.Set[File]) =
{
val (jars, dirs) = separatePaths(classpath)
(linkedSet(jars ++ extraJars.toList), linkedSet(dirs ++ extraDirs.toList))
}
private[sbt] def onClasspath(classpathJars: collection.Set[File], classpathDirectories: collection.Set[File], file: File): Boolean =
{
val f = file.getCanonicalFile
if(ClasspathUtilities.isArchive(f))
classpathJars(f)
else
classpathDirectories.toList.find(Path.relativize(_, f).isDefined).isDefined
}
/** Returns all entries in 'classpath' that correspond to a compiler plugin.*/
private[sbt] def compilerPlugins(classpath: Iterable[Path]): Iterable[File] =
{
import collection.JavaConversions._
val loader = new URLClassLoader(Path.getURLs(classpath))
loader.getResources("scalac-plugin.xml").toList.flatMap(asFile(true))
}
/** Converts the given URL to a File. If the URL is for an entry in a jar, the File for the jar is returned. */
private[sbt] def asFile(url: URL): List[File] = asFile(false)(url)
private[sbt] def asFile(jarOnly: Boolean)(url: URL): List[File] =
{
try
{
url.getProtocol match
{
case "file" if !jarOnly=> IO.toFile(url) :: Nil
case "jar" =>
val path = url.getPath
val end = path.indexOf('!')
new File(new URI(if(end == -1) path else path.substring(0, end))) :: Nil
case _ => Nil
}
}
catch { case e: Exception => Nil }
}
private lazy val (extraJars, extraDirs) =
{
import scala.tools.nsc.GenericRunnerCommand
val settings = (new GenericRunnerCommand(Nil, message => error(message))).settings
val bootPaths = IO.pathSplit(settings.bootclasspath.value).map(p => new File(p)).toList
val (bootJars, bootDirs) = separate(bootPaths)
val extJars =
{
val buffer = new ListBuffer[File]
def findJars(dir: File)
{
buffer ++= dir.listFiles(new SimpleFileFilter(isArchive))
for(dir <- dir.listFiles(DirectoryFilter))
findJars(dir)
}
for(path <- IO.pathSplit(settings.extdirs.value); val dir = new File(path) if dir.isDirectory)
findJars(dir)
buffer.readOnly.map(_.getCanonicalFile)
}
(linkedSet(extJars ++ bootJars), linkedSet(bootDirs))
}
private def linkedSet[T](s: Iterable[T]): Set[T] =
{
val set = new LinkedHashSet[T]
set ++= s
set
}
}
private class IntermediateLoader(urls: Array[URL], parent: ClassLoader) extends LoaderBase(urls, parent)
{
def doLoadClass(className: String): Class[_] =
{
// if this loader is asked to load an sbt class, it must be because the project we are building is sbt itself,
// so we want to load the version of classes on the project classpath, not the parent
if(className.startsWith(Loaders.SbtPackage))
findClass(className)
else
defaultLoadClass(className)
}
}
/** Delegates class loading to `parent` for all classes included by `filter`. An attempt to load classes excluded by `filter`
* results in a `ClassNotFoundException`.*/
class FilteredLoader(parent: ClassLoader, filter: ClassFilter) extends ClassLoader(parent)
{
require(parent != null) // included because a null parent is legitimate in Java
def this(parent: ClassLoader, excludePackages: Iterable[String]) = this(parent, new ExcludePackagesFilter(excludePackages))
@throws(classOf[ClassNotFoundException])
override final def loadClass(className: String, resolve: Boolean): Class[_] =
{
if(filter.include(className))
super.loadClass(className, resolve)
else
throw new ClassNotFoundException(className)
}
}
private class SelectiveLoader(urls: Array[URL], parent: ClassLoader, filter: ClassFilter) extends URLClassLoader(urls, parent)
{
require(parent != null) // included because a null parent is legitimate in Java
def this(urls: Array[URL], parent: ClassLoader, includePackages: Iterable[String]) = this(urls, parent, new IncludePackagesFilter(includePackages))
@throws(classOf[ClassNotFoundException])
override final def loadClass(className: String, resolve: Boolean): Class[_] =
{
if(filter.include(className))
super.loadClass(className, resolve)
else
{
val loaded = parent.loadClass(className)
if(resolve)
resolveClass(loaded)
loaded
}
}
}
trait ClassFilter
{
def include(className: String): Boolean
}
abstract class PackageFilter(packages: Iterable[String]) extends ClassFilter
{
require(packages.forall(_.endsWith(".")))
protected final def matches(className: String): Boolean = packages.exists(className.startsWith)
}
class ExcludePackagesFilter(exclude: Iterable[String]) extends PackageFilter(exclude)
{
def include(className: String): Boolean = !matches(className)
}
class IncludePackagesFilter(include: Iterable[String]) extends PackageFilter(include)
{
def include(className: String): Boolean = matches(className)
}
private[sbt] class LazyFrameworkLoader(runnerClassName: String, urls: Array[URL], parent: ClassLoader, grandparent: ClassLoader)
extends LoaderBase(urls, parent)
{
def doLoadClass(className: String): Class[_] =
{
if(Loaders.isNestedOrSelf(className, runnerClassName))
findClass(className)
else if(Loaders.isSbtClass(className)) // we circumvent the parent loader because we know that we want the
grandparent.loadClass(className) // version of sbt that is currently the builder (not the project being built)
else
parent.loadClass(className)
}
}
private object Loaders
{
val SbtPackage = "sbt."
def isNestedOrSelf(className: String, checkAgainst: String) =
className == checkAgainst || className.startsWith(checkAgainst + "$")
def isSbtClass(className: String) = className.startsWith(Loaders.SbtPackage)
}