Resolve Scala version for projects in the normal `update` task.

1. Scala jars won't be copied to the boot directory, except for those needed to run sbt.
2. Scala SNAPSHOTs behave like normal SNAPSHOTs.  In particular, running `update` will properly re-resolve the dynamic revision.
3. Scala jars are resolved using the same repositories and configuration as other dependencies.
4. Classloaders (currently, Scala classloaders) are cached by the timestamps of entries instead of Scala class loaders being cached by version.

TODO: Support external dependency configuration
This commit is contained in:
Mark Harrah 2012-08-20 15:55:51 -04:00
parent 4cc5bece70
commit e47a357ab7
8 changed files with 120 additions and 24 deletions

View File

@ -347,7 +347,7 @@ object Configurations
def default: Seq[Configuration] = defaultMavenConfigurations def default: Seq[Configuration] = defaultMavenConfigurations
def defaultMavenConfigurations: Seq[Configuration] = Seq(Compile, Runtime, Test, Provided, Optional) def defaultMavenConfigurations: Seq[Configuration] = Seq(Compile, Runtime, Test, Provided, Optional)
def defaultInternal: Seq[Configuration] = Seq(CompileInternal, RuntimeInternal, TestInternal) def defaultInternal: Seq[Configuration] = Seq(CompileInternal, RuntimeInternal, TestInternal)
def auxiliary: Seq[Configuration] = Seq(Sources, Docs, Pom) def auxiliary: Seq[Configuration] = Seq(Sources, Docs, Pom, ScalaTool)
def names(cs: Seq[Configuration]) = cs.map(_.name) def names(cs: Seq[Configuration]) = cs.map(_.name)
lazy val RuntimeInternal = optionalInternal(Runtime) lazy val RuntimeInternal = optionalInternal(Runtime)
@ -379,6 +379,7 @@ object Configurations
lazy val Optional = config("optional") lazy val Optional = config("optional")
lazy val Pom = config("pom") lazy val Pom = config("pom")
lazy val ScalaTool = config("scala-tool") hide
lazy val CompilerPlugin = config("plugin") hide lazy val CompilerPlugin = config("plugin") hide
private[sbt] val DefaultMavenConfiguration = defaultConfiguration(true) private[sbt] val DefaultMavenConfiguration = defaultConfiguration(true)

View File

@ -35,9 +35,16 @@ object Defaults extends BuildCommon
{ {
final val CacheDirectoryName = "cache" final val CacheDirectoryName = "cache"
private[sbt] def scalaToolDependencies(org: String, version: String): Seq[ModuleID] = Seq(
scalaToolDependency(org, ScalaArtifacts.CompilerID, version),
scalaToolDependency(org, ScalaArtifacts.LibraryID, version)
)
private[this] def scalaToolDependency(org: String, id: String, version: String): ModuleID =
ModuleID(org, id, version, Some(Configurations.ScalaTool.name + "->default,optional(default)") )
def configSrcSub(key: SettingKey[File]): Initialize[File] = (key in ThisScope.copy(config = Global), configuration) { (src, conf) => src / nameForSrc(conf.name) } def configSrcSub(key: SettingKey[File]): Initialize[File] = (key in ThisScope.copy(config = Global), configuration) { (src, conf) => src / nameForSrc(conf.name) }
def nameForSrc(config: String) = if(config == "compile") "main" else config def nameForSrc(config: String) = if(config == Configurations.Compile.name) "main" else config
def prefix(config: String) = if(config == "compile") "" else config + "-" def prefix(config: String) = if(config == Configurations.Compile.name) "" else config + "-"
def lock(app: xsbti.AppConfiguration): xsbti.GlobalLock = app.provider.scalaProvider.launcher.globalLock def lock(app: xsbti.AppConfiguration): xsbti.GlobalLock = app.provider.scalaProvider.launcher.globalLock
@ -199,7 +206,7 @@ object Defaults extends BuildCommon
compilersSetting, compilersSetting,
javacOptions in GlobalScope :== Nil, javacOptions in GlobalScope :== Nil,
scalacOptions in GlobalScope :== Nil, scalacOptions in GlobalScope :== Nil,
scalaInstance <<= scalaInstanceSetting, scalaInstance <<= scalaInstanceTask,
scalaVersion in GlobalScope := appConfiguration.value.provider.scalaProvider.version, scalaVersion in GlobalScope := appConfiguration.value.provider.scalaProvider.version,
scalaBinaryVersion in GlobalScope := binaryScalaVersion(scalaVersion.value), scalaBinaryVersion in GlobalScope := binaryScalaVersion(scalaVersion.value),
crossVersion := (if(crossPaths.value) CrossVersion.binary else CrossVersion.Disabled), crossVersion := (if(crossPaths.value) CrossVersion.binary else CrossVersion.Disabled),
@ -270,13 +277,37 @@ object Defaults extends BuildCommon
} }
} }
} }
def scalaInstanceSetting = (appConfiguration, scalaOrganization, scalaVersion, scalaHome) map { (app, org, version, home) => @deprecated("Use scalaInstanceTask.", "0.13.0")
val launcher = app.provider.scalaProvider.launcher def scalaInstanceSetting = scalaInstanceTask
home match { def scalaInstanceTask: Initialize[Task[ScalaInstance]] = Def.taskDyn {
case None => ScalaInstance(org, version, launcher) scalaHome.value match {
case Some(h) => ScalaInstance(h, launcher) case Some(h) => scalaInstanceFromHome(h)
case None =>
val scalaProvider = appConfiguration.value.provider.scalaProvider
val version = scalaVersion.value
if(version == scalaProvider.version) // use the same class loader as the Scala classes used by sbt
Def.task( ScalaInstance(version, scalaProvider) )
else
scalaInstanceFromUpdate
} }
} }
def scalaInstanceFromUpdate: Initialize[Task[ScalaInstance]] = Def.task {
val toolReport = update.value.configuration(Configurations.ScalaTool.name) getOrElse error("Missing Scala tool configuration.")
def files(id: String) =
for { m <- toolReport.modules if m.module.name == id;
(art, file) <- m.artifacts if art.`type` == Artifact.DefaultType }
yield file
def file(id: String) = files(id).headOption getOrElse error(s"Missing ${id}.jar")
val allFiles = toolReport.modules.flatMap(_.artifacts.map(_._2))
val libraryJar = file(ScalaArtifacts.LibraryID)
val compilerJar = file(ScalaArtifacts.CompilerID)
val otherJars = allFiles.filterNot(x => x == libraryJar || x == compilerJar)
ScalaInstance(scalaVersion.value, libraryJar, compilerJar, otherJars : _*)(makeClassLoader(state.value))
}
def scalaInstanceFromHome(dir: File): Initialize[Task[ScalaInstance]] = Def.task {
ScalaInstance(dir)(makeClassLoader(state.value))
}
private[this] def makeClassLoader(state: State) = state.classLoaderCache.apply _
lazy val testTasks: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions(testQuick) ++ Seq( lazy val testTasks: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions(testQuick) ++ Seq(
testLoader := TestFramework.createTestLoader(data(fullClasspath.value), scalaInstance.value, IO.createUniqueDirectory(taskTemporaryDirectory.value)), testLoader := TestFramework.createTestLoader(data(fullClasspath.value), scalaInstance.value, IO.createUniqueDirectory(taskTemporaryDirectory.value)),
@ -807,16 +838,20 @@ object Classpaths
projectDependencies <<= projectDependenciesTask, projectDependencies <<= projectDependenciesTask,
dependencyOverrides in GlobalScope :== Set.empty, dependencyOverrides in GlobalScope :== Set.empty,
libraryDependencies in GlobalScope :== Nil, libraryDependencies in GlobalScope :== Nil,
libraryDependencies <++= (autoScalaLibrary, sbtPlugin, scalaVersion) apply autoLibraryDependency, libraryDependencies <++= (autoScalaLibrary, sbtPlugin, scalaOrganization, scalaVersion) apply autoLibraryDependency,
allDependencies <<= (projectDependencies,libraryDependencies,sbtPlugin,sbtDependency) map { (projDeps, libDeps, isPlugin, sbtDep) => allDependencies := {
val base = projDeps ++ libDeps val base = projectDependencies.value ++ libraryDependencies.value
if(isPlugin) sbtDep.copy(configurations = Some(Provided.name)) +: base else base val pluginAdjust = if(sbtPlugin.value) sbtDependency.value.copy(configurations = Some(Provided.name)) +: base else base
if(scalaHome.value.isDefined)
pluginAdjust
else
Defaults.scalaToolDependencies(scalaOrganization.value, scalaVersion.value) ++ pluginAdjust
}, },
ivyLoggingLevel in GlobalScope :== UpdateLogging.DownloadOnly, ivyLoggingLevel in GlobalScope :== UpdateLogging.DownloadOnly,
ivyXML in GlobalScope :== NodeSeq.Empty, ivyXML in GlobalScope :== NodeSeq.Empty,
ivyValidate in GlobalScope :== false, ivyValidate in GlobalScope :== false,
ivyScala <<= ivyScala or (scalaHome, scalaVersion in update, scalaBinaryVersion in update) { (sh,fv,bv) => ivyScala <<= ivyScala or (scalaHome, scalaVersion in update, scalaBinaryVersion in update) { (sh,fv,bv) =>
Some(new IvyScala(fv, bv, Nil, filterImplicit = true, checkExplicit = true, overrideScalaVersion = sh.isEmpty)) Some(new IvyScala(fv, bv, Nil, filterImplicit = false, checkExplicit = true, overrideScalaVersion = sh.isEmpty))
}, },
moduleConfigurations in GlobalScope :== Nil, moduleConfigurations in GlobalScope :== Nil,
publishTo in GlobalScope :== None, publishTo in GlobalScope :== None,
@ -852,11 +887,11 @@ object Classpaths
ivySbt <<= ivySbt0, ivySbt <<= ivySbt0,
ivyModule <<= (ivySbt, moduleSettings) map { (ivySbt, settings) => new ivySbt.Module(settings) }, ivyModule <<= (ivySbt, moduleSettings) map { (ivySbt, settings) => new ivySbt.Module(settings) },
transitiveUpdate <<= transitiveUpdateTask, transitiveUpdate <<= transitiveUpdateTask,
update <<= (ivyModule, thisProjectRef, updateConfiguration, cacheDirectory, scalaInstance, transitiveUpdate, executionRoots, resolvedScoped, skip in update, streams) map { update <<= (ivyModule, thisProjectRef, updateConfiguration, cacheDirectory, transitiveUpdate, executionRoots, resolvedScoped, skip in update, streams) map {
(module, ref, config, cacheDirectory, si, reports, roots, resolved, skip, s) => (module, ref, config, cacheDirectory, reports, roots, resolved, skip, s) =>
val depsUpdated = reports.exists(!_.stats.cached) val depsUpdated = reports.exists(!_.stats.cached)
val isRoot = roots contains resolved val isRoot = roots contains resolved
cachedUpdate(cacheDirectory / "update", Reference.display(ref), module, config, Some(si), skip = skip, force = isRoot, depsUpdated = depsUpdated, log = s.log) cachedUpdate(cacheDirectory / "update", Reference.display(ref), module, config, None, skip = skip, force = isRoot, depsUpdated = depsUpdated, log = s.log)
} tag(Tags.Update, Tags.Network), } tag(Tags.Update, Tags.Network),
update <<= (conflictWarning, update, streams) map { (config, report, s) => ConflictWarning(config, report, s.log); report }, update <<= (conflictWarning, update, streams) map { (config, report, s) => ConflictWarning(config, report, s.log); report },
transitiveClassifiers in GlobalScope :== Seq(SourceClassifier, DocClassifier), transitiveClassifiers in GlobalScope :== Seq(SourceClassifier, DocClassifier),
@ -1155,11 +1190,18 @@ object Classpaths
def modifyForPlugin(plugin: Boolean, dep: ModuleID): ModuleID = def modifyForPlugin(plugin: Boolean, dep: ModuleID): ModuleID =
if(plugin) dep.copy(configurations = Some(Provided.name)) else dep if(plugin) dep.copy(configurations = Some(Provided.name)) else dep
@deprecated("Explicitly specify the organization using the other variant.", "0.13.0")
def autoLibraryDependency(auto: Boolean, plugin: Boolean, version: String): Seq[ModuleID] = def autoLibraryDependency(auto: Boolean, plugin: Boolean, version: String): Seq[ModuleID] =
if(auto) if(auto)
modifyForPlugin(plugin, ScalaArtifacts.libraryDependency(version)) :: Nil modifyForPlugin(plugin, ScalaArtifacts.libraryDependency(version)) :: Nil
else else
Nil Nil
def autoLibraryDependency(auto: Boolean, plugin: Boolean, org: String, version: String): Seq[ModuleID] =
if(auto)
modifyForPlugin(plugin, ModuleID(org, ScalaArtifacts.LibraryID, version)) :: Nil
else
Nil
import DependencyFilter._ import DependencyFilter._
def managedJars(config: Configuration, jarTypes: Set[String], up: UpdateReport): Classpath = def managedJars(config: Configuration, jarTypes: Set[String], up: UpdateReport): Classpath =
@ -1183,6 +1225,7 @@ object Classpaths
if(autoCompilerPlugins.value) options ++ autoPlugins(update.value) else options if(autoCompilerPlugins.value) options ++ autoPlugins(update.value) else options
} }
) )
@deprecated("Doesn't properly handle non-standard Scala organizations.", "0.13.0")
def substituteScalaFiles(scalaInstance: ScalaInstance, report: UpdateReport): UpdateReport = def substituteScalaFiles(scalaInstance: ScalaInstance, report: UpdateReport): UpdateReport =
report.substitute { (configuration, module, arts) => report.substitute { (configuration, module, arts) =>
import ScalaArtifacts._ import ScalaArtifacts._

View File

@ -54,7 +54,9 @@ object StandardMain
def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State = def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State =
{ {
val commands = preCommands ++ configuration.arguments.map(_.trim) val commands = preCommands ++ configuration.arguments.map(_.trim)
State( configuration, initialDefinitions, Set.empty, None, commands, State.newHistory, BuiltinCommands.initialAttributes, initialGlobalLogging, State.Continue ) val initAttrs = BuiltinCommands.initialAttributes
val s = State( configuration, initialDefinitions, Set.empty, None, commands, State.newHistory, initAttrs, initialGlobalLogging, State.Continue )
s.initializeClassLoaderCache
} }
def initialGlobalLogging: GlobalLogging = def initialGlobalLogging: GlobalLogging =
GlobalLogging.initial((pw, glb) => MainLogging.globalDefault(pw,glb,console), File.createTempFile("sbt",".log"), console) GlobalLogging.initial((pw, glb) => MainLogging.globalDefault(pw,glb,console), File.createTempFile("sbt",".log"), console)

View File

@ -8,4 +8,5 @@ object BasicKeys
val shellPrompt = AttributeKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.", 10000) val shellPrompt = AttributeKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.", 10000)
val watch = AttributeKey[Watched]("watch", "Continuous execution configuration.", 1000) val watch = AttributeKey[Watched]("watch", "Continuous execution configuration.", 1000)
private[sbt] val interactive = AttributeKey[Boolean]("interactive", "True if commands are currently being entered from an interactive environment.", 10) private[sbt] val interactive = AttributeKey[Boolean]("interactive", "True if commands are currently being entered from an interactive environment.", 10)
private[sbt] val classLoaderCache = AttributeKey[classpath.ClassLoaderCache]("class-loader-cache", "Caches class loaders based on the classpath entries and last modified times.", 10)
} }

View File

@ -38,11 +38,13 @@ trait Identity {
override final def toString = super.toString override final def toString = super.toString
} }
/** StateOps methods to be merged at the next binary incompatible release. */ /** StateOps methods to be merged at the next binary incompatible release (0.13.0). */
private[sbt] trait NewStateOps private[sbt] trait NewStateOps
{ {
def interactive: Boolean def interactive: Boolean
def setInteractive(flag: Boolean): State def setInteractive(flag: Boolean): State
def classLoaderCache: classpath.ClassLoaderCache
def initializeClassLoaderCache: State
} }
/** Convenience methods for State transformations and operations. */ /** Convenience methods for State transformations and operations. */
@ -161,6 +163,9 @@ object State
private[sbt] implicit def newStateOps(s: State): NewStateOps = new NewStateOps { private[sbt] implicit def newStateOps(s: State): NewStateOps = new NewStateOps {
def interactive = s.get(BasicKeys.interactive).getOrElse(false) def interactive = s.get(BasicKeys.interactive).getOrElse(false)
def setInteractive(i: Boolean) = s.put(BasicKeys.interactive, i) def setInteractive(i: Boolean) = s.put(BasicKeys.interactive, i)
def classLoaderCache: classpath.ClassLoaderCache = s get BasicKeys.classLoaderCache getOrElse newClassLoaderCache
def initializeClassLoaderCache = s.put(BasicKeys.classLoaderCache, newClassLoaderCache)
private[this] def newClassLoaderCache = new classpath.ClassLoaderCache(s.configuration.provider.scalaProvider.launcher.topLoader)
} }
/** Provides operations and transformations on State. */ /** Provides operations and transformations on State. */

View File

@ -0,0 +1,36 @@
package sbt.classpath
import java.lang.ref.{Reference, SoftReference}
import java.io.File
import java.net.URLClassLoader
import java.util.HashMap
private[sbt] final class ClassLoaderCache(val commonParent: ClassLoader)
{
private[this] val delegate = new HashMap[List[File],Reference[CachedClassLoader]]
def apply(files: List[File]): ClassLoader =
{
val tstamps = files.map(_.lastModified)
getFromReference(files, tstamps, delegate.get(files))
}
private[this] def getFromReference(files: List[File], stamps: List[Long], existingRef: Reference[CachedClassLoader]) =
if(existingRef eq null)
newEntry(files, stamps)
else
get(files, stamps, existingRef.get)
private[this] def get(files: List[File], stamps: List[Long], existing: CachedClassLoader): ClassLoader =
if(existing == null || stamps != existing.timestamps)
newEntry(files, stamps)
else
existing.loader
private[this] def newEntry(files: List[File], stamps: List[Long]): ClassLoader =
{
val loader = new URLClassLoader(files.map(_.toURI.toURL).toArray, commonParent)
delegate.put(files, new SoftReference(new CachedClassLoader(loader, files, stamps)))
loader
}
}
private[sbt] final class CachedClassLoader(val loader: ClassLoader, val files: List[File], val timestamps: List[Long])

View File

@ -63,7 +63,7 @@ object ClasspathUtilities
def makeLoader[T](classpath: Seq[File], parent: ClassLoader, instance: ScalaInstance, nativeTemp: File): ClassLoader = def makeLoader[T](classpath: Seq[File], parent: ClassLoader, instance: ScalaInstance, nativeTemp: File): ClassLoader =
toLoader(classpath, parent, createClasspathResources(classpath, instance), nativeTemp) toLoader(classpath, parent, createClasspathResources(classpath, instance), nativeTemp)
private[sbt] def printSource(c: Class[_]) = private[sbt] def printSource(c: Class[_]) =
println(c.getName + " loader=" +c.getClassLoader + " location=" + IO.classLocationFile(c)) println(c.getName + " loader=" +c.getClassLoader + " location=" + IO.classLocationFile(c))

View File

@ -46,18 +46,27 @@ object ScalaInstance
def apply(scalaHome: File, launcher: xsbti.Launcher): ScalaInstance = def apply(scalaHome: File, launcher: xsbti.Launcher): ScalaInstance =
apply(libraryJar(scalaHome), compilerJar(scalaHome), launcher, extraJars(scalaHome): _*) apply(libraryJar(scalaHome), compilerJar(scalaHome), launcher, extraJars(scalaHome): _*)
def apply(scalaHome: File)(classLoader: List[File] => ClassLoader): ScalaInstance =
apply(libraryJar(scalaHome), compilerJar(scalaHome), extraJars(scalaHome): _*)(classLoader)
def apply(version: String, scalaHome: File, launcher: xsbti.Launcher): ScalaInstance = 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, extraJars(scalaHome) : _*)
def apply(libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance = def apply(libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
apply(libraryJar, compilerJar, extraJars : _*)( scalaLoader(launcher) )
def apply(libraryJar: File, compilerJar: File, extraJars: File*)(classLoader: List[File] => ClassLoader): ScalaInstance =
{ {
val loader = scalaLoader(launcher, libraryJar :: compilerJar :: extraJars.toList) val loader = classLoader(libraryJar :: compilerJar :: extraJars.toList)
val version = actualVersion(loader)(" (library jar " + libraryJar.getAbsolutePath + ")") val version = actualVersion(loader)(" (library jar " + libraryJar.getAbsolutePath + ")")
new ScalaInstance(version, loader, libraryJar, compilerJar, extraJars, None) new ScalaInstance(version, loader, libraryJar, compilerJar, extraJars, None)
} }
def apply(version: String, libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance = def apply(version: String, libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
apply(version, None, libraryJar, compilerJar, launcher, extraJars : _*) apply(version, None, libraryJar, compilerJar, launcher, extraJars : _*)
def apply(version: String, libraryJar: File, compilerJar: File, extraJars: File*)(classLoader: List[File] => ClassLoader): ScalaInstance =
apply(version, None, libraryJar, compilerJar, extraJars : _*)(classLoader)
def apply(version: String, explicitActual: Option[String], libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance = def apply(version: String, explicitActual: Option[String], libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
new ScalaInstance(version, scalaLoader(launcher, libraryJar :: compilerJar :: extraJars.toList), libraryJar, compilerJar, extraJars, explicitActual) apply(version, explicitActual, libraryJar, compilerJar, extraJars : _*)( scalaLoader(launcher) )
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)
def extraJars(scalaHome: File): Seq[File] = def extraJars(scalaHome: File): Seq[File] =
optScalaJar(scalaHome, "jline.jar") ++ optScalaJar(scalaHome, "jline.jar") ++
@ -94,9 +103,8 @@ object ScalaInstance
} }
finally stream.close() finally stream.close()
} }
import java.net.{URL, URLClassLoader} import java.net.{URL, URLClassLoader}
private def scalaLoader(launcher: xsbti.Launcher, jars: Seq[File]): ClassLoader = private def scalaLoader(launcher: xsbti.Launcher): Seq[File] => ClassLoader = jars =>
new URLClassLoader(jars.map(_.toURI.toURL).toArray[URL], launcher.topLoader) new URLClassLoader(jars.map(_.toURI.toURL).toArray[URL], launcher.topLoader)
} }
class InvalidScalaInstance(message: String, cause: Throwable) extends RuntimeException(message, cause) class InvalidScalaInstance(message: String, cause: Throwable) extends RuntimeException(message, cause)