Skip to content

Commit 9397b6a

Browse files
committed
include only scala-library into the boot classpath during run
Fixes sbt/sbt#3405 Ref scala/scala-xml#195 sbt's `run` is emulated using a classloader trick that includes ScalaInstance as the parent classloader under the classpath. The problem is the ScalaInstance classloader currently contains both compiler, library, and their transitive JARs: ```scala res0: Array[java.io.File] = Array(scala-library.jar, scala-compiler.jar, jline.jar, scala-reflect.jar, scala-xml_2.12.jar) ``` This could have been causing various issues, but most recently it showed up as wrong version of scala-xml getting prioritized over what's passed by the user. 1. new field loaderLibraryOnly is added to xsbti.ScalaInstance. 2. it is initialized to the library loader if the launcher creates it, otherwise create layered loader here. This aims to isolate the library loader, and retain the perf.
1 parent ef5b5fc commit 9397b6a

File tree

5 files changed

+86
-23
lines changed

5 files changed

+86
-23
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ lazy val compilerInterface = (project in internalPath / "compiler-interface")
337337
import com.typesafe.tools.mima.core.ProblemFilters._
338338
Seq(
339339
exclude[ReversedMissingMethodProblem]("xsbti.compile.ExternalHooks#Lookup.hashClasspath"),
340+
exclude[ReversedMissingMethodProblem]("xsbti.compile.ScalaInstance.loaderLibraryOnly"),
340341
)
341342
},
342343
)

internal/compiler-interface/src/main/java/xsbti/compile/ScalaInstance.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ public interface ScalaInstance {
3131
/** A class loader providing access to the classes and resources in the library and compiler jars. */
3232
ClassLoader loader();
3333

34-
/** @deprecated Only `jars` can be reliably provided for modularized Scala (since 0.13.0). */
35-
@Deprecated
34+
/** A class loader providing access to the classes and resources in the library. */
35+
ClassLoader loaderLibraryOnly();
36+
37+
/** Only `jars` can be reliably provided for modularized Scala. */
3638
File libraryJar();
3739

38-
/** @deprecated Only `jars` can be reliably provided for modularized Scala (since 0.13.0). */
39-
@Deprecated
40+
/** Only `jars` can be reliably provided for modularized Scala. */
4041
File compilerJar();
4142

4243
/** @deprecated Only `jars` can be reliably provided for modularized Scala (since 0.13.0). */

internal/zinc-classpath/src/main/scala/sbt/internal/inc/ScalaInstance.scala

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ package inc
1212
import java.io.File
1313
import xsbti.ArtifactInfo.ScalaOrganization
1414
import sbt.io.IO
15+
import scala.language.reflectiveCalls
16+
import sbt.internal.inc.classpath.ClasspathUtilities
1517

1618
/**
1719
* A Scala instance encapsulates all the information that is bound to a concrete
@@ -35,12 +37,33 @@ import sbt.io.IO
3537
final class ScalaInstance(
3638
val version: String,
3739
val loader: ClassLoader,
40+
val loaderLibraryOnly: ClassLoader,
3841
val libraryJar: File,
3942
val compilerJar: File,
4043
val allJars: Array[File],
4144
val explicitActual: Option[String]
4245
) extends xsbti.compile.ScalaInstance {
4346

47+
@deprecated("Use constructor with loaderLibraryOnly", "1.1.2")
48+
def this(
49+
version: String,
50+
loader: ClassLoader,
51+
libraryJar: File,
52+
compilerJar: File,
53+
allJars: Array[File],
54+
explicitActual: Option[String]
55+
) {
56+
this(
57+
version,
58+
loader,
59+
ClasspathUtilities.rootLoader,
60+
libraryJar,
61+
compilerJar,
62+
allJars,
63+
explicitActual
64+
)
65+
}
66+
4467
/**
4568
* Check whether `scalaInstance` comes from a managed (i.e. ivy-resolved)
4669
* scala **or** if it's a free-floating `ScalaInstance`, in which case we
@@ -75,7 +98,13 @@ final class ScalaInstance(
7598
override def toString: String =
7699
s"Scala instance { version label $version, actual version $actualVersion, $jarStrings }"
77100
}
101+
78102
object ScalaInstance {
103+
/*
104+
* Structural extention for the ScalaProvider post 1.0.3 launcher.
105+
* See https://github.com/sbt/zinc/pull/505.
106+
*/
107+
private type ScalaProvider2 = { def loaderLibraryOnly: ClassLoader }
79108

80109
/** Name of scala organisation to be used for artifact resolution. */
81110
val ScalaOrg = ScalaOrganization
@@ -123,30 +152,47 @@ object ScalaInstance {
123152
}
124153
}
125154
val jars = provider.jars
126-
val loader = provider.loader
127155
val libraryJar = findOrCrash(jars, "scala-library.jar")
128156
val compilerJar = findOrCrash(jars, "scala-compiler.jar")
129-
new ScalaInstance(version, loader, libraryJar, compilerJar, jars, None)
157+
def fallbackClassLoaders = {
158+
val l = ClasspathUtilities.toLoader(Vector(libraryJar))
159+
val c = scalaLoader(l)(jars.toVector filterNot { _ == libraryJar })
160+
(c, l)
161+
}
162+
// sbt launcher 1.0.3 will construct layered classloader. Use them if we find them.
163+
// otherwise, construct layered loaders manually.
164+
val (loader, loaderLibraryOnly) = {
165+
(try {
166+
provider match {
167+
case p: ScalaProvider2 @unchecked => Option((provider.loader, p.loaderLibraryOnly))
168+
}
169+
} catch {
170+
case _: NoSuchMethodError => None
171+
}) getOrElse fallbackClassLoaders
172+
}
173+
new ScalaInstance(version, loader, loaderLibraryOnly, libraryJar, compilerJar, jars, None)
130174
}
131175

132176
def apply(scalaHome: File, launcher: xsbti.Launcher): ScalaInstance =
133-
apply(scalaHome)(scalaLoader(launcher))
177+
apply(scalaHome)(scalaLibraryLoader(launcher))
134178

135179
def apply(scalaHome: File)(classLoader: List[File] => ClassLoader): ScalaInstance = {
136180
val all = allJars(scalaHome)
137-
val loader = classLoader(all.toList)
138181
val library = libraryJar(scalaHome)
182+
val loaderLibraryOnly = classLoader(List(library))
183+
val loader = scalaLoader(loaderLibraryOnly)(all.toVector filterNot { _ == library })
139184
val version = actualVersion(loader)(" (library jar " + library.getAbsolutePath + ")")
140185
val compiler = compilerJar(scalaHome)
141-
new ScalaInstance(version, loader, library, compiler, all.toArray, None)
186+
new ScalaInstance(version, loader, loaderLibraryOnly, library, compiler, all.toArray, None)
142187
}
143188

144189
def apply(version: String, scalaHome: File, launcher: xsbti.Launcher): ScalaInstance = {
145190
val all = allJars(scalaHome)
146-
val loader = scalaLoader(launcher)(all.toList)
147191
val library = libraryJar(scalaHome)
192+
val loaderLibraryOnly = scalaLibraryLoader(launcher)(List(library))
193+
val loader = scalaLoader(loaderLibraryOnly)(all.toVector)
148194
val compiler = compilerJar(scalaHome)
149-
new ScalaInstance(version, loader, library, compiler, all.toArray, None)
195+
new ScalaInstance(version, loader, loaderLibraryOnly, library, compiler, all.toArray, None)
150196
}
151197

152198
/** Return all the required Scala jars from a path `scalaHome`. */
@@ -209,12 +255,12 @@ object ScalaInstance {
209255
} finally stream.close()
210256
}
211257

212-
private def scalaLoader(launcher: xsbti.Launcher): Seq[File] => ClassLoader = { jars =>
213-
import java.net.{ URL, URLClassLoader }
214-
new URLClassLoader(
215-
jars.map(_.toURI.toURL).toArray[URL],
216-
launcher.topLoader
217-
)
258+
private def scalaLibraryLoader(launcher: xsbti.Launcher): Seq[File] => ClassLoader = { jars =>
259+
ClasspathUtilities.toLoader(jars, launcher.topLoader)
260+
}
261+
262+
private def scalaLoader(parent: ClassLoader): Seq[File] => ClassLoader = { jars =>
263+
ClasspathUtilities.toLoader(jars, parent)
218264
}
219265
}
220266

internal/zinc-classpath/src/main/scala/sbt/internal/inc/classpath/ClasspathUtilities.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ object ClasspathUtilities {
6363
final val AppClassPath = "app.class.path"
6464
final val BootClassPath = "boot.class.path"
6565

66-
def createClasspathResources(classpath: Seq[File], instance: ScalaInstance): Map[String, String] =
67-
createClasspathResources(classpath, instance.allJars)
66+
def createClasspathResources(classpath: Seq[File],
67+
instance: ScalaInstance): Map[String, String] = {
68+
createClasspathResources(classpath, Array(instance.libraryJar))
69+
}
6870

6971
def createClasspathResources(appPaths: Seq[File], bootPaths: Seq[File]): Map[String, String] = {
7072
def make(name: String, paths: Seq[File]) = name -> Path.makeString(paths)
@@ -74,11 +76,16 @@ object ClasspathUtilities {
7476
private[sbt] def filterByClasspath(classpath: Seq[File], loader: ClassLoader): ClassLoader =
7577
new ClasspathFilter(loader, xsbtiLoader, classpath.toSet)
7678

79+
/**
80+
* Creates a ClassLoader that contains the classpath and the scala-library from
81+
* the given instance.
82+
*/
7783
def makeLoader(classpath: Seq[File], instance: ScalaInstance): ClassLoader =
78-
filterByClasspath(classpath, makeLoader(classpath, instance.loader, instance))
84+
filterByClasspath(classpath, makeLoader(classpath, instance.loaderLibraryOnly, instance))
7985

8086
def makeLoader(classpath: Seq[File], instance: ScalaInstance, nativeTemp: File): ClassLoader =
81-
filterByClasspath(classpath, makeLoader(classpath, instance.loader, instance, nativeTemp))
87+
filterByClasspath(classpath,
88+
makeLoader(classpath, instance.loaderLibraryOnly, instance, nativeTemp))
8289

8390
def makeLoader(classpath: Seq[File], parent: ClassLoader, instance: ScalaInstance): ClassLoader =
8491
toLoader(classpath, parent, createClasspathResources(classpath, instance))

internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,19 @@ private[sbt] object ZincComponentCompiler {
140140
val scalaLibrary = scalaArtifacts.library
141141
val jarsToLoad = (scalaCompiler +: scalaLibrary +: scalaArtifacts.others).toArray
142142
assert(jarsToLoad.forall(_.exists), "One or more jar(s) in the Scala instance do not exist.")
143-
val loader = new URLClassLoader(toURLs(jarsToLoad), ClasspathUtilities.rootLoader)
143+
val loaderLibraryOnly = ClasspathUtilities.toLoader(Vector(scalaLibrary))
144+
val loader = ClasspathUtilities.toLoader(jarsToLoad.toVector filterNot { _ == scalaLibrary },
145+
loaderLibraryOnly)
144146
val properties = ResourceLoader.getSafePropertiesFor("compiler.properties", loader)
145147
val loaderVersion = Option(properties.getProperty("version.number"))
146148
val scalaV = loaderVersion.getOrElse("unknown")
147-
new ScalaInstance(scalaV, loader, scalaLibrary, scalaCompiler, jarsToLoad, loaderVersion)
149+
new ScalaInstance(scalaV,
150+
loader,
151+
loaderLibraryOnly,
152+
scalaLibrary,
153+
scalaCompiler,
154+
jarsToLoad,
155+
loaderVersion)
148156
}
149157
}
150158

0 commit comments

Comments
 (0)