From a12bccf4a37d670771c4582851bb36085b18c181 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 26 Sep 2019 12:06:50 -0700 Subject: [PATCH] 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. --- .../java/sbt/internal/FullScalaLoader.java | 36 ++++++ .../java/sbt/internal/MetaBuildLoader.java | 104 ++++++++++++++++++ .../sbt/internal/TestInterfaceLoader.java | 26 +++++ .../java/sbt/internal/XMainClassLoader.java | 34 ++++++ .../sbt/internal/XMainConfiguration.scala | 104 ++---------------- 5 files changed, 207 insertions(+), 97 deletions(-) create mode 100644 main/src/main/java/sbt/internal/FullScalaLoader.java create mode 100644 main/src/main/java/sbt/internal/MetaBuildLoader.java create mode 100644 main/src/main/java/sbt/internal/TestInterfaceLoader.java create mode 100644 main/src/main/java/sbt/internal/XMainClassLoader.java diff --git a/main/src/main/java/sbt/internal/FullScalaLoader.java b/main/src/main/java/sbt/internal/FullScalaLoader.java new file mode 100644 index 000000000..fd8d88236 --- /dev/null +++ b/main/src/main/java/sbt/internal/FullScalaLoader.java @@ -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(); + } +} diff --git a/main/src/main/java/sbt/internal/MetaBuildLoader.java b/main/src/main/java/sbt/internal/MetaBuildLoader.java new file mode 100644 index 000000000..8976f0808 --- /dev/null +++ b/main/src/main/java/sbt/internal/MetaBuildLoader.java @@ -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); + } +} diff --git a/main/src/main/java/sbt/internal/TestInterfaceLoader.java b/main/src/main/java/sbt/internal/TestInterfaceLoader.java new file mode 100644 index 000000000..1404c6bb6 --- /dev/null +++ b/main/src/main/java/sbt/internal/TestInterfaceLoader.java @@ -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(); + } +} diff --git a/main/src/main/java/sbt/internal/XMainClassLoader.java b/main/src/main/java/sbt/internal/XMainClassLoader.java new file mode 100644 index 000000000..2668a5db7 --- /dev/null +++ b/main/src/main/java/sbt/internal/XMainClassLoader.java @@ -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(); + } +} diff --git a/main/src/main/scala/sbt/internal/XMainConfiguration.scala b/main/src/main/scala/sbt/internal/XMainConfiguration.scala index a66e3a0dc..86a11ecd4 100644 --- a/main/src/main/scala/sbt/internal/XMainConfiguration.scala +++ b/main/src/main/scala/sbt/internal/XMainConfiguration.scala @@ -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 - } }