Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ lazy val compilerInterface = (project in internalPath / "compiler-interface")
import com.typesafe.tools.mima.core.ProblemFilters._
Seq(
exclude[ReversedMissingMethodProblem]("xsbti.compile.ExternalHooks#Lookup.hashClasspath"),
exclude[ReversedMissingMethodProblem]("xsbti.compile.ScalaInstance.loaderLibraryOnly"),
)
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ public interface ScalaInstance {
/** A class loader providing access to the classes and resources in the library and compiler jars. */
ClassLoader loader();

/** @deprecated Only `jars` can be reliably provided for modularized Scala (since 0.13.0). */
@Deprecated
/** A class loader providing access to the classes and resources in the library. */
ClassLoader loaderLibraryOnly();

/** Only `jars` can be reliably provided for modularized Scala. */
File libraryJar();

/** @deprecated Only `jars` can be reliably provided for modularized Scala (since 0.13.0). */
@Deprecated
/** Only `jars` can be reliably provided for modularized Scala. */
File compilerJar();

/** @deprecated Only `jars` can be reliably provided for modularized Scala (since 0.13.0). */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ package inc
import java.io.File
import xsbti.ArtifactInfo.ScalaOrganization
import sbt.io.IO
import scala.language.reflectiveCalls
import sbt.internal.inc.classpath.ClasspathUtilities

/**
* A Scala instance encapsulates all the information that is bound to a concrete
Expand All @@ -35,12 +37,33 @@ import sbt.io.IO
final class ScalaInstance(
val version: String,
val loader: ClassLoader,
val loaderLibraryOnly: ClassLoader,
val libraryJar: File,
val compilerJar: File,
val allJars: Array[File],
val explicitActual: Option[String]
) extends xsbti.compile.ScalaInstance {

@deprecated("Use constructor with loaderLibraryOnly", "1.1.2")
def this(
version: String,
loader: ClassLoader,
libraryJar: File,
compilerJar: File,
allJars: Array[File],
explicitActual: Option[String]
) {
this(
version,
loader,
ClasspathUtilities.rootLoader,
libraryJar,
compilerJar,
allJars,
explicitActual
)
}

/**
* Check whether `scalaInstance` comes from a managed (i.e. ivy-resolved)
* scala **or** if it's a free-floating `ScalaInstance`, in which case we
Expand Down Expand Up @@ -75,7 +98,13 @@ final class ScalaInstance(
override def toString: String =
s"Scala instance { version label $version, actual version $actualVersion, $jarStrings }"
}

object ScalaInstance {
/*
* Structural extention for the ScalaProvider post 1.0.3 launcher.
* See https://github.com/sbt/zinc/pull/505.
*/
private type ScalaProvider2 = { def loaderLibraryOnly: ClassLoader }

/** Name of scala organisation to be used for artifact resolution. */
val ScalaOrg = ScalaOrganization
Expand Down Expand Up @@ -123,30 +152,47 @@ object ScalaInstance {
}
}
val jars = provider.jars
val loader = provider.loader
val libraryJar = findOrCrash(jars, "scala-library.jar")
val compilerJar = findOrCrash(jars, "scala-compiler.jar")
new ScalaInstance(version, loader, libraryJar, compilerJar, jars, None)
def fallbackClassLoaders = {
val l = ClasspathUtilities.toLoader(Vector(libraryJar))
val c = scalaLoader(l)(jars.toVector filterNot { _ == libraryJar })
(c, l)
}
// sbt launcher 1.0.3 will construct layered classloader. Use them if we find them.
// otherwise, construct layered loaders manually.
val (loader, loaderLibraryOnly) = {
(try {
provider match {
case p: ScalaProvider2 @unchecked => Option((provider.loader, p.loaderLibraryOnly))
}
} catch {
case _: NoSuchMethodError => None
}) getOrElse fallbackClassLoaders
}
new ScalaInstance(version, loader, loaderLibraryOnly, libraryJar, compilerJar, jars, None)
}

def apply(scalaHome: File, launcher: xsbti.Launcher): ScalaInstance =
apply(scalaHome)(scalaLoader(launcher))
apply(scalaHome)(scalaLibraryLoader(launcher))

def apply(scalaHome: File)(classLoader: List[File] => ClassLoader): ScalaInstance = {
val all = allJars(scalaHome)
val loader = classLoader(all.toList)
val library = libraryJar(scalaHome)
val loaderLibraryOnly = classLoader(List(library))
val loader = scalaLoader(loaderLibraryOnly)(all.toVector filterNot { _ == library })
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)
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member Author

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)

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member Author

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please do 👍

Copy link
Copy Markdown
Member Author

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 apply(version: String, scalaHome: File, launcher: xsbti.Launcher): ScalaInstance = {
val all = allJars(scalaHome)
val loader = scalaLoader(launcher)(all.toList)
val library = libraryJar(scalaHome)
val loaderLibraryOnly = scalaLibraryLoader(launcher)(List(library))
val loader = scalaLoader(loaderLibraryOnly)(all.toVector)
val compiler = compilerJar(scalaHome)
new ScalaInstance(version, loader, library, compiler, all.toArray, None)
new ScalaInstance(version, loader, loaderLibraryOnly, library, compiler, all.toArray, None)
}

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

private def scalaLoader(launcher: xsbti.Launcher): Seq[File] => ClassLoader = { jars =>
import java.net.{ URL, URLClassLoader }
new URLClassLoader(
jars.map(_.toURI.toURL).toArray[URL],
launcher.topLoader
)
private def scalaLibraryLoader(launcher: xsbti.Launcher): Seq[File] => ClassLoader = { jars =>
ClasspathUtilities.toLoader(jars, launcher.topLoader)
}

private def scalaLoader(parent: ClassLoader): Seq[File] => ClassLoader = { jars =>
ClasspathUtilities.toLoader(jars, parent)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ object ClasspathUtilities {
final val AppClassPath = "app.class.path"
final val BootClassPath = "boot.class.path"

def createClasspathResources(classpath: Seq[File], instance: ScalaInstance): Map[String, String] =
createClasspathResources(classpath, instance.allJars)
def createClasspathResources(classpath: Seq[File],
instance: ScalaInstance): Map[String, String] = {
createClasspathResources(classpath, Array(instance.libraryJar))
}

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

/**
* Creates a ClassLoader that contains the classpath and the scala-library from
* the given instance.
*/
def makeLoader(classpath: Seq[File], instance: ScalaInstance): ClassLoader =
filterByClasspath(classpath, makeLoader(classpath, instance.loader, instance))
filterByClasspath(classpath, makeLoader(classpath, instance.loaderLibraryOnly, instance))
Copy link
Copy Markdown
Member

@retronym retronym Mar 13, 2018

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.

Copy link
Copy Markdown
Member Author

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.


def makeLoader(classpath: Seq[File], instance: ScalaInstance, nativeTemp: File): ClassLoader =
filterByClasspath(classpath, makeLoader(classpath, instance.loader, instance, nativeTemp))
filterByClasspath(classpath,
makeLoader(classpath, instance.loaderLibraryOnly, instance, nativeTemp))

def makeLoader(classpath: Seq[File], parent: ClassLoader, instance: ScalaInstance): ClassLoader =
toLoader(classpath, parent, createClasspathResources(classpath, instance))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,19 @@ private[sbt] object ZincComponentCompiler {
val scalaLibrary = scalaArtifacts.library
val jarsToLoad = (scalaCompiler +: scalaLibrary +: scalaArtifacts.others).toArray
assert(jarsToLoad.forall(_.exists), "One or more jar(s) in the Scala instance do not exist.")
val loader = new URLClassLoader(toURLs(jarsToLoad), ClasspathUtilities.rootLoader)
val loaderLibraryOnly = ClasspathUtilities.toLoader(Vector(scalaLibrary))
val loader = ClasspathUtilities.toLoader(jarsToLoad.toVector filterNot { _ == scalaLibrary },
loaderLibraryOnly)
val properties = ResourceLoader.getSafePropertiesFor("compiler.properties", loader)
val loaderVersion = Option(properties.getProperty("version.number"))
val scalaV = loaderVersion.getOrElse("unknown")
new ScalaInstance(scalaV, loader, scalaLibrary, scalaCompiler, jarsToLoad, loaderVersion)
new ScalaInstance(scalaV,
loader,
loaderLibraryOnly,
scalaLibrary,
scalaCompiler,
jarsToLoad,
loaderVersion)
}
}

Expand Down