mirror of https://github.com/sbt/sbt.git
222 lines
8.4 KiB
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)
|
|
} |