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 defaultMavenConfigurations: Seq[Configuration] = Seq(Compile, Runtime, Test, Provided, Optional)
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)
lazy val RuntimeInternal = optionalInternal(Runtime)
@ -379,6 +379,7 @@ object Configurations
lazy val Optional = config("optional")
lazy val Pom = config("pom")
lazy val ScalaTool = config("scala-tool") hide
lazy val CompilerPlugin = config("plugin") hide
private[sbt] val DefaultMavenConfiguration = defaultConfiguration(true)

View File

@ -35,9 +35,16 @@ object Defaults extends BuildCommon
{
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 nameForSrc(config: String) = if(config == "compile") "main" else config
def prefix(config: String) = if(config == "compile") "" else config + "-"
def nameForSrc(config: String) = if(config == Configurations.Compile.name) "main" 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
@ -199,7 +206,7 @@ object Defaults extends BuildCommon
compilersSetting,
javacOptions in GlobalScope :== Nil,
scalacOptions in GlobalScope :== Nil,
scalaInstance <<= scalaInstanceSetting,
scalaInstance <<= scalaInstanceTask,
scalaVersion in GlobalScope := appConfiguration.value.provider.scalaProvider.version,
scalaBinaryVersion in GlobalScope := binaryScalaVersion(scalaVersion.value),
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) =>
val launcher = app.provider.scalaProvider.launcher
home match {
case None => ScalaInstance(org, version, launcher)
case Some(h) => ScalaInstance(h, launcher)
@deprecated("Use scalaInstanceTask.", "0.13.0")
def scalaInstanceSetting = scalaInstanceTask
def scalaInstanceTask: Initialize[Task[ScalaInstance]] = Def.taskDyn {
scalaHome.value match {
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(
testLoader := TestFramework.createTestLoader(data(fullClasspath.value), scalaInstance.value, IO.createUniqueDirectory(taskTemporaryDirectory.value)),
@ -807,16 +838,20 @@ object Classpaths
projectDependencies <<= projectDependenciesTask,
dependencyOverrides in GlobalScope :== Set.empty,
libraryDependencies in GlobalScope :== Nil,
libraryDependencies <++= (autoScalaLibrary, sbtPlugin, scalaVersion) apply autoLibraryDependency,
allDependencies <<= (projectDependencies,libraryDependencies,sbtPlugin,sbtDependency) map { (projDeps, libDeps, isPlugin, sbtDep) =>
val base = projDeps ++ libDeps
if(isPlugin) sbtDep.copy(configurations = Some(Provided.name)) +: base else base
libraryDependencies <++= (autoScalaLibrary, sbtPlugin, scalaOrganization, scalaVersion) apply autoLibraryDependency,
allDependencies := {
val base = projectDependencies.value ++ libraryDependencies.value
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,
ivyXML in GlobalScope :== NodeSeq.Empty,
ivyValidate in GlobalScope :== false,
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,
publishTo in GlobalScope :== None,
@ -852,11 +887,11 @@ object Classpaths
ivySbt <<= ivySbt0,
ivyModule <<= (ivySbt, moduleSettings) map { (ivySbt, settings) => new ivySbt.Module(settings) },
transitiveUpdate <<= transitiveUpdateTask,
update <<= (ivyModule, thisProjectRef, updateConfiguration, cacheDirectory, scalaInstance, transitiveUpdate, executionRoots, resolvedScoped, skip in update, streams) map {
(module, ref, config, cacheDirectory, si, reports, roots, resolved, skip, s) =>
update <<= (ivyModule, thisProjectRef, updateConfiguration, cacheDirectory, transitiveUpdate, executionRoots, resolvedScoped, skip in update, streams) map {
(module, ref, config, cacheDirectory, reports, roots, resolved, skip, s) =>
val depsUpdated = reports.exists(!_.stats.cached)
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),
update <<= (conflictWarning, update, streams) map { (config, report, s) => ConflictWarning(config, report, s.log); report },
transitiveClassifiers in GlobalScope :== Seq(SourceClassifier, DocClassifier),
@ -1155,11 +1190,18 @@ object Classpaths
def modifyForPlugin(plugin: Boolean, dep: ModuleID): ModuleID =
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] =
if(auto)
modifyForPlugin(plugin, ScalaArtifacts.libraryDependency(version)) :: Nil
else
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._
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
}
)
@deprecated("Doesn't properly handle non-standard Scala organizations.", "0.13.0")
def substituteScalaFiles(scalaInstance: ScalaInstance, report: UpdateReport): UpdateReport =
report.substitute { (configuration, module, arts) =>
import ScalaArtifacts._

View File

@ -54,7 +54,9 @@ object StandardMain
def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State =
{
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 =
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 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 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
}
/** 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
{
def interactive: Boolean
def setInteractive(flag: Boolean): State
def classLoaderCache: classpath.ClassLoaderCache
def initializeClassLoaderCache: State
}
/** Convenience methods for State transformations and operations. */
@ -161,6 +163,9 @@ object State
private[sbt] implicit def newStateOps(s: State): NewStateOps = new NewStateOps {
def interactive = s.get(BasicKeys.interactive).getOrElse(false)
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. */

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 =
toLoader(classpath, parent, createClasspathResources(classpath, instance), nativeTemp)
private[sbt] def printSource(c: Class[_]) =
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 =
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 =
apply(version, libraryJar(scalaHome), compilerJar(scalaHome), launcher, extraJars(scalaHome) : _*)
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 + ")")
new ScalaInstance(version, loader, libraryJar, compilerJar, extraJars, None)
}
def apply(version: String, libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
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 =
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] =
optScalaJar(scalaHome, "jline.jar") ++
@ -94,9 +103,8 @@ object ScalaInstance
}
finally stream.close()
}
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)
}
class InvalidScalaInstance(message: String, cause: Throwable) extends RuntimeException(message, cause)