Use java to implement XMain classloaders

These classloaders which are created if sbt is launched with a legacy
launcher (or one that doesn't follow the current classloading hierarchy
convention), were implemented in scala, but that meant that they were
not parallel capable. I fix that by moving the implementations to java.
I also move the static method that creates a MetaBuildLoader into the
java class.
This commit is contained in:
Ethan Atkins 2019-09-26 12:06:50 -07:00
parent 8fd10bfb5f
commit a12bccf4a3
5 changed files with 207 additions and 97 deletions

View File

@ -0,0 +1,36 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal;
import java.net.URL;
import java.net.URLClassLoader;
final class FullScalaLoader extends URLClassLoader {
private final String jarString;
FullScalaLoader(final URL[] scalaRest, final URLClassLoader parent) {
super(scalaRest, parent);
final StringBuilder res = new StringBuilder();
int i = 0;
while (i < scalaRest.length) {
res.append(scalaRest[i].getPath());
res.append(", ");
i += 1;
}
jarString = res.toString();
}
@Override
public String toString() {
return "ScalaClassLoader(jars = " + jarString + ")";
}
static {
registerAsParallelCapable();
}
}

View File

@ -0,0 +1,104 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.regex.Pattern;
import xsbti.AppProvider;
import xsbti.ScalaProvider;
@SuppressWarnings("unused")
public final class MetaBuildLoader extends URLClassLoader {
private final URLClassLoader fullScalaLoader;
private final URLClassLoader libraryLoader;
private final URLClassLoader interfaceLoader;
MetaBuildLoader(
final URL[] urls,
final URLClassLoader fullScalaLoader,
final URLClassLoader libraryLoader,
final URLClassLoader interfaceLoader) {
super(urls, fullScalaLoader);
this.fullScalaLoader = fullScalaLoader;
this.libraryLoader = libraryLoader;
this.interfaceLoader = interfaceLoader;
}
@Override
public String toString() {
return "SbtMetaBuildClassLoader";
}
@Override
public void close() throws IOException {
super.close();
fullScalaLoader.close();
libraryLoader.close();
interfaceLoader.close();
}
static {
ClassLoader.registerAsParallelCapable();
}
/**
* Rearrange the classloaders so that test-interface is above the scala library. Implemented
* without using the scala standard library to minimize classloading.
*
* @param appProvider the appProvider that needs to be modified
* @return a ClassLoader with a URLClassLoader for the test-interface-1.0.jar above the
* scala library.
*/
public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException {
final Pattern pattern = Pattern.compile("test-interface-[0-9.]+\\.jar");
final File[] cp = appProvider.mainClasspath();
final URL[] interfaceURL = new URL[1];
final URL[] rest = new URL[cp.length - 1];
{
int i = 0;
int j = 0; // index into rest
while (i < cp.length) {
final File file = cp[i];
if (pattern.matcher(file.getName()).find()) {
interfaceURL[0] = file.toURI().toURL();
} else {
rest[j] = file.toURI().toURL();
j += 1;
}
i += 1;
}
}
final ScalaProvider scalaProvider = appProvider.scalaProvider();
final ClassLoader topLoader = scalaProvider.launcher().topLoader();
final TestInterfaceLoader interfaceLoader = new TestInterfaceLoader(interfaceURL, topLoader);
final File[] siJars = scalaProvider.jars();
final URL[] lib = new URL[1];
final URL[] scalaRest = new URL[siJars.length - 1];
{
int i = 0;
int j = 0; // index into scalaRest
while (i < siJars.length) {
final File file = siJars[i];
if (file.getName().equals("scala-library.jar")) {
lib[0] = file.toURI().toURL();
} else {
scalaRest[j] = file.toURI().toURL();
j += 1;
}
i += 1;
}
}
final ScalaLibraryClassLoader libraryLoader = new ScalaLibraryClassLoader(lib, interfaceLoader);
final FullScalaLoader fullScalaLoader = new FullScalaLoader(scalaRest, libraryLoader);
return new MetaBuildLoader(rest, fullScalaLoader, libraryLoader, interfaceLoader);
}
}

View File

@ -0,0 +1,26 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal;
import java.net.URL;
import java.net.URLClassLoader;
class TestInterfaceLoader extends URLClassLoader {
TestInterfaceLoader(final URL[] urls, final ClassLoader parent) {
super(urls, parent);
}
@Override
public String toString() {
return "SbtTestInterfaceClassLoader(" + getURLs()[0] + ")";
}
static {
registerAsParallelCapable();
}
}

View File

@ -0,0 +1,34 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal;
import java.net.URL;
import java.net.URLClassLoader;
class XMainClassLoader extends URLClassLoader {
XMainClassLoader(final URL[] urls, final ClassLoader parent) {
super(urls, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("sbt.internal.XMainConfiguration")) {
synchronized (getClassLoadingLock(name)) {
Class<?> result = findLoadedClass(name);
if (result == null) result = findClass(name);
if (resolve) resolveClass(result);
return result;
}
}
return super.loadClass(name, resolve);
}
static {
registerAsParallelCapable();
}
}

View File

@ -9,9 +9,8 @@ package sbt.internal
import java.io.File
import java.lang.reflect.InvocationTargetException
import java.net.{ URL, URLClassLoader }
import java.net.URL
import java.util.concurrent.{ ExecutorService, Executors }
import java.util.regex.Pattern
import sbt.plugins.{ CorePlugin, IvyPlugin, JvmPlugin }
import sbt.util.LogExchange
@ -93,28 +92,19 @@ private[sbt] class XMainConfiguration {
val topLoader = configuration.provider.scalaProvider.launcher.topLoader
// This loader doesn't have the scala library in it so it's critical that none of the code
// in this file use the scala library.
val modifiedLoader = new URLClassLoader(urlArray, topLoader) {
override def loadClass(name: String, resolve: Boolean): Class[_] = {
if (name.startsWith("sbt.internal.XMainConfiguration")) {
val clazz = findClass(name)
if (resolve) resolveClass(clazz)
clazz
} else {
super.loadClass(name, resolve)
}
}
}
val modifiedLoader = new XMainClassLoader(urlArray, topLoader)
val xMainConfigurationClass = modifiedLoader.loadClass("sbt.internal.XMainConfiguration")
val instance: AnyRef =
xMainConfigurationClass.getConstructor().newInstance().asInstanceOf[AnyRef]
val method = xMainConfigurationClass.getMethod("makeLoader", classOf[AppProvider])
val modifiedConfigurationClass =
modifiedLoader.loadClass("sbt.internal.XMainConfiguration$ModifiedConfiguration")
val metaBuildLoaderClass = modifiedLoader.loadClass("sbt.internal.MetaBuildLoader")
val method = metaBuildLoaderClass.getMethod("makeLoader", classOf[AppProvider])
val loader = method.invoke(instance, configuration.provider).asInstanceOf[ClassLoader]
val loader = method.invoke(null, configuration.provider).asInstanceOf[ClassLoader]
Thread.currentThread.setContextClassLoader(loader)
val modifiedConfigurationClass =
modifiedLoader.loadClass("sbt.internal.XMainConfiguration$ModifiedConfiguration")
val cons = modifiedConfigurationClass.getConstructors()(0)
close(configuration.provider.loader)
val scalaProvider = configuration.provider.scalaProvider
@ -187,84 +177,4 @@ private[sbt] class XMainConfiguration {
override def provider(): AppProvider = new ModifiedAppProvider(configuration.provider)
}
/**
* Rearrange the classloaders so that test-interface is above the scala library. Implemented
* without using the scala standard library to minimize classloading.
* @param appProvider the appProvider that needs to be modified
* @return a ClassLoader with a URLClassLoader for the test-interface-1.0.jar above the
* scala library.
*/
private[sbt] def makeLoader(appProvider: AppProvider): ClassLoader = {
val pattern = Pattern.compile("test-interface-[0-9.]+\\.jar")
val cp = appProvider.mainClasspath
val interfaceURL = new Array[URL](1)
val rest = new Array[URL](cp.length - 1)
{
var i = 0
var j = 0 // index into rest
while (i < cp.length) {
val file = cp(i)
if (pattern.matcher(file.getName).find()) {
interfaceURL(0) = file.toURI.toURL
} else {
rest(j) = file.toURI.toURL
j += 1
}
i += 1
}
}
val scalaProvider = appProvider.scalaProvider
val topLoader = scalaProvider.launcher.topLoader
class InterfaceLoader extends URLClassLoader(interfaceURL, topLoader) {
override def toString: String = "SbtTestInterfaceClassLoader(" + interfaceURL(0) + ")"
}
val interfaceLoader = new InterfaceLoader
val siJars = scalaProvider.jars
val lib = new Array[URL](1)
val scalaRest = new Array[URL](siJars.length - 1)
{
var i = 0
var j = 0 // index into scalaRest
while (i < siJars.length) {
val file = siJars(i)
if (file.getName.equals("scala-library.jar")) {
lib(0) = file.toURI.toURL
} else {
scalaRest(j) = file.toURI.toURL
j += 1
}
i += 1
}
}
class LibraryLoader extends URLClassLoader(lib, interfaceLoader) {
override def toString: String = "ScalaLibraryLoader( " + lib(0) + ")"
}
val libraryLoader = new LibraryLoader
class FullLoader extends URLClassLoader(scalaRest, libraryLoader) {
private val jarString: String = {
val res = new java.lang.StringBuilder
var i = 0
while (i < scalaRest.length) {
res.append(scalaRest(i).getPath)
res.append(", ")
i += 1
}
res.toString
}
override def toString: String = "ScalaClassLoader(jars = " + jarString + ")"
}
val fullLoader = new FullLoader
class MetaBuildLoader extends URLClassLoader(rest, fullLoader) {
override def toString: String = "SbtMetaBuildClassLoader"
override def close(): Unit = {
super.close()
libraryLoader.close()
fullLoader.close()
interfaceLoader.close()
}
}
new MetaBuildLoader
}
}