From 5b5577a1878dcdbce36f84514c3ea6c0d04389bb Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 21 Feb 2013 20:44:26 -0500 Subject: [PATCH] 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. --- .../java/xsbti/compile/ScalaInstance.java | 9 ++-- .../src/main/java/xsbti/ScalaProvider.java | 9 +++- main/src/main/scala/sbt/Defaults.scala | 46 ++++++++++-------- .../src/main/scala/sbt/ScalaInstance.scala | 47 ++++++++++++++++--- .../scala/sbt/classpath/ClassLoaders.scala | 8 ++-- 5 files changed, 86 insertions(+), 33 deletions(-) diff --git a/interface/src/main/java/xsbti/compile/ScalaInstance.java b/interface/src/main/java/xsbti/compile/ScalaInstance.java index 4e41e1ca2..c7f3984e3 100644 --- a/interface/src/main/java/xsbti/compile/ScalaInstance.java +++ b/interface/src/main/java/xsbti/compile/ScalaInstance.java @@ -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.*/ diff --git a/launch/interface/src/main/java/xsbti/ScalaProvider.java b/launch/interface/src/main/java/xsbti/ScalaProvider.java index 381985c61..435625eee 100644 --- a/launch/interface/src/main/java/xsbti/ScalaProvider.java +++ b/launch/interface/src/main/java/xsbti/ScalaProvider.java @@ -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); -} \ No newline at end of file +} diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index b68e9e2c3..f5ce27e6e 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -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] = diff --git a/util/classpath/src/main/scala/sbt/ScalaInstance.scala b/util/classpath/src/main/scala/sbt/ScalaInstance.scala index fcb24e9cf..66ab2f243 100644 --- a/util/classpath/src/main/scala/sbt/ScalaInstance.scala +++ b/util/classpath/src/main/scala/sbt/ScalaInstance.scala @@ -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) diff --git a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala index 507b506a2..228228030 100644 --- a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala +++ b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala @@ -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 {