Replace Scala jars in UpdateReport with ScalaProvider jars in more situations. Fixes #661.

Specifically, when the Scala version for sbt is the same as that for the project being built,
the jars in UpdateReport should be the same as those in ScalaProvider.  This is because the
loader will come from the ScalaProvider, which uses jars in the boot directory instead of the
cache.  The first part of the fix for #661 checks that loaded classes come from the classpath
and so they need to line up.
This commit is contained in:
Mark Harrah 2013-02-21 20:44:26 -05:00
parent bd0f208302
commit 5b5577a187
5 changed files with 86 additions and 33 deletions

View File

@ -17,13 +17,16 @@ public interface ScalaInstance
/** A class loader providing access to the classes and resources in the library and compiler jars. */
ClassLoader loader();
/** The library jar file.*/
/**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */
@Deprecated
File libraryJar();
/** The compiler jar file.*/
/**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */
@Deprecated
File compilerJar();
/** Jars provided by this Scala instance other than the compiler and library jars. */
/**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */
@Deprecated
File[] otherJars();
/** All jar files provided by this Scala instance.*/

View File

@ -13,10 +13,17 @@ public interface ScalaProvider
public ClassLoader loader();
/** Returns the scala-library.jar and scala-compiler.jar for this version of Scala. */
public File[] jars();
/**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */
@Deprecated
public File libraryJar();
/**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */
@Deprecated
public File compilerJar();
/** Creates an application provider that will use 'loader()' as the parent ClassLoader for
* the application given by 'id'. This method will retrieve the application if it has not already
* been retrieved.*/
public AppProvider app(ApplicationID id);
}
}

View File

@ -647,7 +647,8 @@ object Defaults extends BuildCommon
def consoleTask(classpath: TaskKey[Classpath], task: TaskKey[_]): Initialize[Task[Unit]] =
(compilers in task, classpath in task, scalacOptions in task, initialCommands in task, cleanupCommands in task, taskTemporaryDirectory in task, scalaInstance in task, streams) map {
(cs, cp, options, initCommands, cleanup, temp, si, s) =>
val loader = sbt.classpath.ClasspathUtilities.makeLoader(data(cp), si, IO.createUniqueDirectory(temp))
val fullcp = (data(cp) ++ si.jars).distinct
val loader = sbt.classpath.ClasspathUtilities.makeLoader(fullcp, si, IO.createUniqueDirectory(temp))
(new Console(cs.scalac))(data(cp), options, loader, initCommands, cleanup)()(s.log).foreach(msg => error(msg))
println()
}
@ -1027,12 +1028,20 @@ object Classpaths
val depsUpdated = transitiveUpdate.value.exists(!_.stats.cached)
val isRoot = executionRoots.value contains resolvedScoped.value
val s = streams.value
val si = Defaults.unmanagedScalaInstanceOnly.value.map(si => (si, scalaOrganization.value))
val subScalaJars: Seq[File] = Defaults.unmanagedScalaInstanceOnly.value match {
case Some(si) => si.jars
case None =>
val scalaProvider = appConfiguration.value.provider.scalaProvider
// substitute the Scala jars from the provider so that when the provider's loader is used,
// the jars on the classpath match the jars used by the loader
if(scalaProvider.version == scalaVersion.value) scalaProvider.jars else Nil
}
val transform: UpdateReport => UpdateReport = if(subScalaJars.isEmpty) idFun else r => substituteScalaFiles(subScalaJars, scalaOrganization.value, r)
val show = Reference.display(thisProjectRef.value)
cachedUpdate(s.cacheDirectory, show, ivyModule.value, updateConfiguration.value, si, skip = (skip in update).value, force = isRoot, depsUpdated = depsUpdated, log = s.log)
cachedUpdate(s.cacheDirectory, show, ivyModule.value, updateConfiguration.value, transform, skip = (skip in update).value, force = isRoot, depsUpdated = depsUpdated, log = s.log)
}
def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration, scalaInstance: Option[(ScalaInstance, String)], skip: Boolean, force: Boolean, depsUpdated: Boolean, log: Logger): UpdateReport =
def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration, transform: UpdateReport=>UpdateReport, skip: Boolean, force: Boolean, depsUpdated: Boolean, log: Logger): UpdateReport =
{
implicit val updateCache = updateIC
type In = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil
@ -1040,7 +1049,7 @@ object Classpaths
log.info("Updating " + label + "...")
val r = IvyActions.update(module, config, log)
log.info("Done updating.")
scalaInstance match { case Some((si,scalaOrg)) => substituteScalaFiles(si, scalaOrg, r); case None => r }
transform(r)
}
def uptodate(inChanged: Boolean, out: UpdateReport): Boolean =
!force &&
@ -1263,6 +1272,7 @@ object Classpaths
def unmanagedScalaLibrary: Initialize[Task[Seq[File]]] =
Def.taskDyn {
if(autoScalaLibrary.value && scalaHome.value.isDefined)
// TODO: what goes here when Scala library is modularized?
Def.task { scalaInstance.value.libraryJar :: Nil }
else
Def.task { Nil }
@ -1293,23 +1303,21 @@ object Classpaths
@deprecated("Doesn't properly handle non-standard Scala organizations.", "0.13.0")
def substituteScalaFiles(scalaInstance: ScalaInstance, report: UpdateReport): UpdateReport =
substituteScalaFiles(scalaInstance, ScalaArtifacts.Organization, report)
def substituteScalaFiles(scalaInstance: ScalaInstance, ScalaOrg: String, report: UpdateReport): UpdateReport =
{
val scalaJars = scalaInstance.jars
@deprecated("Directly provide the jar files.", "0.13.0")
def substituteScalaFiles(scalaInstance: ScalaInstance, scalaOrg: String, report: UpdateReport): UpdateReport =
substituteScalaFiles(scalaInstance.jars, scalaOrg, report)
def substituteScalaFiles(scalaJars: Seq[File], scalaOrg: String, report: UpdateReport): UpdateReport =
report.substitute { (configuration, module, arts) =>
import ScalaArtifacts._
(module.organization, module.name) match
{
case (ScalaOrg, LibraryID) => (Artifact(LibraryID), scalaInstance.libraryJar) :: Nil
case (ScalaOrg, CompilerID) => (Artifact(CompilerID), scalaInstance.compilerJar) :: Nil
case (ScalaOrg, id) =>
val jarName = id + ".jar"
val replaceWith = scalaJars.filter(_.getName == jarName).map(f => (Artifact(f.getName), f))
if(replaceWith.isEmpty) arts else replaceWith
case _ => arts
}
if(module.organization == scalaOrg) {
val jarName = module.name + ".jar"
val replaceWith = scalaJars.filter(_.getName == jarName).map(f => (Artifact(f.getName.stripSuffix(".jar")), f))
if(replaceWith.isEmpty) arts else replaceWith
} else
arts
}
}
// try/catch for supporting earlier launchers
def bootIvyHome(app: xsbti.AppConfiguration): Option[File] =

View File

@ -4,15 +4,27 @@
package sbt
import java.io.File
import xsbti.ArtifactInfo.{ScalaCompilerID, ScalaLibraryID, ScalaOrganization}
/** Represents the source for Scala classes for a given version. The reason both a ClassLoader and the jars are required
* is that the compiler requires the location of the library/compiler jars on the (boot)classpath and the loader is used
* for the compiler itself.
* The 'version' field is the version used to obtain the Scala classes. This is typically the version for the maven repository.
* The 'actualVersion' field should be used to uniquely identify the compiler. It is obtained from the compiler.properties file.*/
final class ScalaInstance(val version: String, val loader: ClassLoader, val libraryJar: File, val compilerJar: File, val extraJars: Seq[File], val explicitActual: Option[String]) extends xsbti.compile.ScalaInstance
* The 'actualVersion' field should be used to uniquely identify the compiler. It is obtained from the compiler.properties file.
*
* This should be constructed via the ScalaInstance.apply methods. The primary constructor is deprecated.
**/
final class ScalaInstance(val version: String, val loader: ClassLoader,
@deprecated("Only `allJars` and `jars` can be reliably provided for modularized Scala.", "0.13.0")
val libraryJar: File,
@deprecated("Only `allJars` and `jars` can be reliably provided for modularized Scala.", "0.13.0")
val compilerJar: File,
@deprecated("Only `allJars` and `jars` can be reliably provided for modularized Scala.", "0.13.0")
val extraJars: Seq[File],
val explicitActual: Option[String]) extends xsbti.compile.ScalaInstance
{
// These are to implement xsbti.ScalaInstance
@deprecated("Only `allJars` and `jars` can be reliably provided for modularized Scala.", "0.13.0")
def otherJars: Array[File] = extraJars.toArray
def allJars: Array[File] = jars.toArray
@ -25,7 +37,7 @@ final class ScalaInstance(val version: String, val loader: ClassLoader, val libr
}
object ScalaInstance
{
val ScalaOrg = "org.scala-lang"
val ScalaOrg = ScalaOrganization
val VersionPrefix = "version "
def apply(org: String, version: String, launcher: xsbti.Launcher): ScalaInstance =
@ -35,7 +47,7 @@ object ScalaInstance
else try {
apply(version, launcher.getScala(version, "", org))
} catch {
case x: NoSuchMethodError => error("Incompatible version of the xsbti.Launcher interface. Use sbt-0.12.x launcher instead.")
case x: NoSuchMethodError => error("Incompatible version of the xsbti.Launcher interface. Use an sbt 0.12+ launcher instead.")
}
/** Creates a ScalaInstance using the given provider to obtain the jars and loader.*/
@ -45,38 +57,59 @@ object ScalaInstance
new ScalaInstance(version, provider.loader, provider.libraryJar, provider.compilerJar, (provider.jars.toSet - provider.libraryJar - provider.compilerJar).toSeq, None)
def apply(scalaHome: File, launcher: xsbti.Launcher): ScalaInstance =
apply(libraryJar(scalaHome), compilerJar(scalaHome), launcher, extraJars(scalaHome): _*)
apply(libraryJar(scalaHome), compilerJar(scalaHome), launcher, allJars(scalaHome): _*)
def apply(scalaHome: File)(classLoader: List[File] => ClassLoader): ScalaInstance =
apply(libraryJar(scalaHome), compilerJar(scalaHome), extraJars(scalaHome): _*)(classLoader)
apply(libraryJar(scalaHome), compilerJar(scalaHome), allJars(scalaHome): _*)(classLoader)
def apply(version: String, scalaHome: File, launcher: xsbti.Launcher): ScalaInstance =
apply(version, libraryJar(scalaHome), compilerJar(scalaHome), launcher, extraJars(scalaHome) : _*)
apply(version, libraryJar(scalaHome), compilerJar(scalaHome), launcher, allJars(scalaHome) : _*)
@deprecated("Does not handle modularized Scala. Use a variant that only accepts all jars.", "0.13.0")
def apply(libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
apply(libraryJar, compilerJar, extraJars : _*)( scalaLoader(launcher) )
@deprecated("Does not handle modularized Scala. Use a variant that only accepts all jars.", "0.13.0")
def apply(libraryJar: File, compilerJar: File, extraJars: File*)(classLoader: List[File] => ClassLoader): ScalaInstance =
{
val loader = classLoader(libraryJar :: compilerJar :: extraJars.toList)
val version = actualVersion(loader)(" (library jar " + libraryJar.getAbsolutePath + ")")
new ScalaInstance(version, loader, libraryJar, compilerJar, extraJars, None)
}
@deprecated("Does not handle modularized Scala. Use a variant that only accepts all jars.", "0.13.0")
def apply(version: String, libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
apply(version, None, libraryJar, compilerJar, launcher, extraJars : _*)
@deprecated("Does not handle modularized Scala. Use a variant that only accepts all jars.", "0.13.0")
def apply(version: String, libraryJar: File, compilerJar: File, extraJars: File*)(classLoader: List[File] => ClassLoader): ScalaInstance =
apply(version, None, libraryJar, compilerJar, extraJars : _*)(classLoader)
@deprecated("Does not handle modularized Scala. Use a variant that only accepts all jars.", "0.13.0")
def apply(version: String, explicitActual: Option[String], libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
apply(version, explicitActual, libraryJar, compilerJar, extraJars : _*)( scalaLoader(launcher) )
@deprecated("Does not handle modularized Scala. Use a variant that only accepts all jars.", "0.13.0")
def apply(version: String, explicitActual: Option[String], libraryJar: File, compilerJar: File, extraJars: File*)(classLoader: List[File] => ClassLoader): ScalaInstance =
new ScalaInstance(version, classLoader(libraryJar :: compilerJar :: extraJars.toList), libraryJar, compilerJar, extraJars, explicitActual)
@deprecated("Cannot be reliably provided for modularized Scala.", "0.13.0")
def extraJars(scalaHome: File): Seq[File] =
optScalaJar(scalaHome, "jline.jar") ++
optScalaJar(scalaHome, "fjbg.jar") ++
optScalaJar(scalaHome, "scala-reflect.jar")
def allJars(scalaHome: File): Seq[File] = IO.listFiles(scalaLib(scalaHome)).filter(f => !blacklist(f.getName))
private[this] def scalaLib(scalaHome: File): File = new File(scalaHome, "lib")
private[this] val blacklist: Set[String] = Set("scala-actors.jar", "scalacheck.jar", "scala-partest.jar", "scala-partest-javaagent.jar", "scalap.jar", "scala-swing.jar")
private def compilerJar(scalaHome: File) = scalaJar(scalaHome, "scala-compiler.jar")
private def libraryJar(scalaHome: File) = scalaJar(scalaHome, "scala-library.jar")
@deprecated("No longer used.", "0.13.0")
def scalaJar(scalaHome: File, name: String) = new File(scalaHome, "lib" + File.separator + name)
@deprecated("No longer used.", "0.13.0")
def optScalaJar(scalaHome: File, name: String): List[File] =
{
val jar = scalaJar(scalaHome, name)

View File

@ -56,9 +56,11 @@ final class ClasspathFilter(parent: ClassLoader, root: ClassLoader, classpath: S
throw new ClassNotFoundException(className)
}
private[this] def fromClasspath(c: Class[_]): Boolean =
try { onClasspath(IO.classLocation(c)) }
catch { case e: RuntimeException => false }
{
val codeSource = c.getProtectionDomain.getCodeSource
(codeSource eq null) ||
onClasspath(codeSource.getLocation)
}
private[this] def onClasspath(src: URL): Boolean =
(src eq null) || (
IO.urlAsFile(src) match {