diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index fb85ae0db..4496f3cae 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -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) diff --git a/main/Defaults.scala b/main/Defaults.scala index 0cb46c944..6dd6f276e 100755 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -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._ diff --git a/main/Main.scala b/main/Main.scala index d4c0aa3fd..84ae3d741 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -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) diff --git a/main/command/BasicKeys.scala b/main/command/BasicKeys.scala index 80409672c..6447b36cf 100644 --- a/main/command/BasicKeys.scala +++ b/main/command/BasicKeys.scala @@ -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) } diff --git a/main/command/State.scala b/main/command/State.scala index f7f9310a0..f0ee55165 100644 --- a/main/command/State.scala +++ b/main/command/State.scala @@ -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. */ diff --git a/util/classpath/ClassLoaderCache.scala b/util/classpath/ClassLoaderCache.scala new file mode 100644 index 000000000..a5e640860 --- /dev/null +++ b/util/classpath/ClassLoaderCache.scala @@ -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]) diff --git a/util/classpath/ClasspathUtilities.scala b/util/classpath/ClasspathUtilities.scala index 6b4fe2f65..2c055da21 100644 --- a/util/classpath/ClasspathUtilities.scala +++ b/util/classpath/ClasspathUtilities.scala @@ -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)) diff --git a/util/classpath/ScalaInstance.scala b/util/classpath/ScalaInstance.scala index 2c4c5a0c2..fcb24e9cf 100644 --- a/util/classpath/ScalaInstance.scala +++ b/util/classpath/ScalaInstance.scala @@ -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) \ No newline at end of file