-
Notifications
You must be signed in to change notification settings - Fork 121
include only scala-library into the classpath during run #505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
/** | ||
* Include only the scala-library.jar, if any, into the boot classpath. | ||
* 1. Supposedly using boot classpath here is for performance reason: | ||
* https://groups.google.com/d/msg/scala-internals/tT1pjH5GECE/dtgRj3w7ovIJ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"boot classpath" isn't an accurate description of what's happening here. The performance trick is to cache and share libraryLoader
in ScalaInstance
, rather than forming new classpaths containing scala-library.jar
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
createClasspathResources(cp, instance)
does map things into BootClassPath
.
lazy val libraryLoader: ClassLoader = { | ||
classpath.ClasspathUtilities.toLibraryLoader(this) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be preferable to do this way in in ScalaProvider
, ie change the way it creates loader
, and then just accept libraryLoader
and fullLoader
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your change will end up with scala-library
loaded separates in loader
and libraryLoader
. That's a functional problem if you ever need interop between programs running in those loaders. It is also a small performance problem in space and time for the VM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could sort of cheat without changing the interface of ScalaProvider
and the intervening call chain.
The launcer could create the layered classloaders as I described, but still only pass the full one into ScalaInstance
. Here, you could look at loader.getParent
to get the libraryLoader
(perhaps verifying that isIsntanceOf[LauncherLibraryClassloader]
to know that the secret handshake is complete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the secret handshake wasn't made (ie, we are being called by an old launcher), you could either do as you've done here, or just let the bug persist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's in launcher land. I would rather not make ppl reinstall launchers. (Assuming bunch of ppl are using official sbt package, not sbt-extra)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's what I've got in mind: https://github.com/retronym/launcher/tree/topic/library-loader
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could deliver both fixes: the launcher fix (that makes this part of the code trivial), and the existing fix you've got here (to fix the bug when using older launchers)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd need to deal with the xsbti.ExtendedScalaProvider
interface reflectively in SBT to avoid a LinkageError
with older launchers. This isn't much fun, but is a pretty standard way of evolving interfaces and implementations that require compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the way to go -- being able to recycle JVM optimizations that have already happened for the Scala library is important, and classloading the library again goes against that.
I agree. I think I am now starting to see the light of what Jason is saying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
x.getName.startsWith("scala-library") | ||
}).getOrElse { | ||
throw new InvalidScalaProvider(s"Couldn't find scala-library") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess to future proof this could find the transitive dependencies of scala-library
, which could be computed in launcher
, but would need some extra metadata (the scala build would need to add this to our distribution) when loading from scalaHome
.
val (loader, loaderLibraryOnly) = | ||
(try { | ||
provider match { | ||
case p: ScalaProvider2 @unchecked => Option((provider.loader, p.loaderLibraryOnly)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated to try the new loader first refectively, and fall back to manual creation.
The validator has checked the following projects against Scala 2.12,
✅ The result is: SUCCESS |
Addressed the review comments. |
Cannot make the review today, but you'll have it by next Monday! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just a couple of comments. Could you link to the PR that will integrate with this work over sbt/sbt?
new ScalaInstance(version, loader, libraryJar, compilerJar, jars, None) | ||
// sbt launcher 1.0.3 will construct layered classloader. Use them if we find them. | ||
// otherwise, construct layered loaders manually. | ||
val (loader, loaderLibraryOnly) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpicks: can we wrap all this logic in curly braces and separate the computation of the default value (what's in getOrElse
) from the rest of the logic?
val version = actualVersion(loader)(" (library jar " + library.getAbsolutePath + ")") | ||
val compiler = compilerJar(scalaHome) | ||
new ScalaInstance(version, loader, library, compiler, all.toArray, None) | ||
new ScalaInstance(version, loader, loaderLibraryOnly, library, compiler, all.toArray, None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to break quite a bit of code. I would honestly consider cutting a major breaking release... xsbti.compile
is quite core to the Zinc API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to break quite a bit of code.
How so? Do you think there would be observable changes to sbt or other Zinc downstream users? (Besides being able to use the correct scala-xml version)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't mean behaviour-wise, but API-wise 😄. I think ScalaInstance
is used pretty much everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean. Maybe I can try to add another constructor that is bincompat and deprecate it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please do 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. Addressed the bincompat.
def makeLoader(classpath: Seq[File], instance: ScalaInstance): ClassLoader = | ||
filterByClasspath(classpath, makeLoader(classpath, instance.loader, instance)) | ||
filterByClasspath(classpath, makeLoader(classpath, instance.loaderLibraryOnly, instance)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually should make even the addition of loaderLibraryOnly
conditional on autoScalaLibrary := true
. In the Scala build, we have that setting disabled (because the scala-library we want on the classpath is actually a regular sub-project in our build, not the one in our reference Scala version).
Here's an example of how we get the unwanted class on our classpath: scala/scala#6300 (comment). So far, we have worked around by forking tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. That sounds like a bug on sbt's Run or TestFramework. By the time someone called makeLoader(...)
with instance
I think it's ok to include loaderLibraryOnly
. If autoScalaLibrary
is false
then we have to be careful not to create ScalaInstance
based loader basically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have anything else to add, 👍
Cool. I'll squash the history and merge. |
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.
I think I've made a mistake in #505 by using NoSuchMethodError. When trying to use Zinc 1.1.2, I ran into `java.lang.NoSuchMethodException`. This is because `NoSuchMethodException` would be thrown for reflective calls, not `NoSuchMethodError`.
I think I've made a mistake in sbt#505 by using NoSuchMethodError. When trying to use Zinc 1.1.2, I ran into `java.lang.NoSuchMethodException`. This is because `NoSuchMethodException` would be thrown for reflective calls, not `NoSuchMethodError`.
thanks @eed3si9n , so glad you took this on 👏👏👏 |
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: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.