Add support for isolate ClassLoaders

This commit is contained in:
Alexandre Archambault 2016-01-10 21:32:29 +01:00
parent f02d26c3bf
commit 1ebced021b
3 changed files with 123 additions and 14 deletions

View File

@ -101,6 +101,10 @@ case class Launch(
@Short("M")
@Short("main")
mainClass: String,
@ExtraName("I")
isolated: List[String],
@ExtraName("i")
isolateTarget: List[String],
@Recurse
common: CommonOptions
) extends CoursierCommand {
@ -116,24 +120,102 @@ case class Launch(
val helper = new Helper(
common.copy(forceVersion = common.forceVersion),
rawDependencies
rawDependencies ++ isolated
)
// FIXME Some duplication with similar things in Helper
val (splitIsolated, malformedIsolated) = isolated
.toVector
.map(_.split(":", 3).toSeq)
.partition(_.length == 3)
if (malformedIsolated.nonEmpty) {
if (malformedIsolated.nonEmpty) {
Console.err.println("Malformed dependency(ies), should be like org:name:version")
for (s <- malformedIsolated)
Console.err.println(s" ${s.mkString(":")}")
}
sys.exit(1)
}
val isolatedModuleVersions = splitIsolated.map{
case Seq(org, namePart, version) =>
val p = namePart.split(';')
val name = p.head
val splitAttributes = p.tail.map(_.split("=", 2).toSeq).toSeq
val malformedAttributes = splitAttributes.filter(_.length != 2)
if (malformedAttributes.nonEmpty) {
Console.err.println(s"Malformed attributes in ${splitIsolated.mkString(":")}")
// :(
sys.exit(255)
}
val attributes = splitAttributes.collect {
case Seq(k, v) => k -> v
}
(Module(org, name, attributes.toMap), version)
}
val isolatedDeps = isolatedModuleVersions.map{case (mod, ver) =>
Dependency(mod, ver, configuration = "runtime")
}
val isolateTargets = {
val l = isolateTarget.flatMap(_.split(',')).filter(_.nonEmpty)
if (l.isEmpty)
Array("default")
else
l.toArray
}
val files0 = helper.fetch(sources = false, javadoc = false)
val cl = new URLClassLoader(
files0.map(_.toURI.toURL).toArray,
new ClasspathFilter(
Thread.currentThread().getContextClassLoader,
Coursier.baseCp.map(new File(_)).toSet,
exclude = true
)
val parentLoader0 = new ClasspathFilter(
Thread.currentThread().getContextClassLoader,
Coursier.baseCp.map(new File(_)).toSet,
exclude = true
)
val (parentLoader, filteredFiles) =
if (isolated.isEmpty)
(parentLoader0, files0)
else {
// FIXME These were already fetched above
val isolatedFiles =
helper.fetch(sources = false, javadoc = false, subset = isolatedDeps.toSet)
val isolatedLoader = new IsolatedClassLoader(
isolatedFiles.map(_.toURI.toURL).toArray,
parentLoader0,
isolateTargets
)
val filteredFiles0 = files0.filterNot(isolatedFiles.toSet)
if (common.verbose0 >= 1) {
Console.err.println(s"Isolated loader files:")
for (f <- isolatedFiles.map(_.toString).sorted)
Console.err.println(s" $f")
Console.err.println(s"Remaining files:")
for (f <- filteredFiles0.map(_.toString).sorted)
Console.err.println(s" $f")
}
(isolatedLoader, filteredFiles0)
}
val loader = new URLClassLoader(
filteredFiles.map(_.toURI.toURL).toArray,
parentLoader
)
val mainClass0 =
if (mainClass.nonEmpty) mainClass
else {
val mainClasses = Helper.mainClasses(cl)
val mainClasses = Helper.mainClasses(loader)
val mainClass =
if (mainClasses.isEmpty) {
@ -166,7 +248,7 @@ case class Launch(
}
val cls =
try cl.loadClass(mainClass0)
try loader.loadClass(mainClass0)
catch { case e: ClassNotFoundException =>
Helper.errPrintln(s"Error: class $mainClass0 not found")
sys.exit(255)
@ -183,7 +265,7 @@ case class Launch(
else if (common.verbose0 == 0)
Helper.errPrintln(s"Launching")
Thread.currentThread().setContextClassLoader(cl)
Thread.currentThread().setContextClassLoader(loader)
method.invoke(null, extraArgs.toArray)
}

View File

@ -226,7 +226,12 @@ class Helper(
if (verbose0 >= 0)
errPrintln(s"Result:\n${indent(Print.dependenciesUnknownConfigs(trDeps))}")
def fetch(sources: Boolean, javadoc: Boolean): Seq[File] = {
def fetch(
sources: Boolean,
javadoc: Boolean,
subset: Set[Dependency] = null
): Seq[File] = {
if (verbose0 >= 0) {
val msg = cachePolicies match {
case Seq(CachePolicy.LocalOnly) =>
@ -237,6 +242,9 @@ class Helper(
errPrintln(msg)
}
val res0 = Option(subset).fold(res)(res.subset)
val artifacts =
if (sources || javadoc) {
var classifiers = Seq.empty[String]
@ -245,9 +253,9 @@ class Helper(
if (javadoc)
classifiers = classifiers :+ "javadoc"
res.classifiersArtifacts(classifiers)
res0.classifiersArtifacts(classifiers)
} else
res.artifacts
res0.artifacts
val logger =
if (verbose0 >= 0)

View File

@ -0,0 +1,19 @@
package coursier.cli
import java.net.{ URL, URLClassLoader }
class IsolatedClassLoader(
urls: Array[URL],
parent: ClassLoader,
isolationTargets: Array[String]
) extends URLClassLoader(urls, parent) {
/**
* Applications wanting to access an isolated `ClassLoader` should inspect the hierarchy of
* loaders, and look into each of them for this method, by reflection. Then they should
* call it (still by reflection), and look for an agreed in advance target in it. If it is found,
* then the corresponding `ClassLoader` is the one with isolated dependencies.
*/
def getIsolationTargets: Array[String] = isolationTargets
}