diff --git a/main/EvaluateTask.scala b/main/EvaluateTask.scala index 247f27f5c..45d4a6485 100644 --- a/main/EvaluateTask.scala +++ b/main/EvaluateTask.scala @@ -12,7 +12,17 @@ package sbt import scala.Console.RED final case class EvaluateConfig(cancelable: Boolean, restrictions: Seq[Tags.Rule], checkCycles: Boolean = false) -final case class PluginData(classpath: Seq[Attributed[File]], resolvers: Option[Seq[Resolver]], report: Option[UpdateReport]) +final case class PluginData(dependencyClasspath: Seq[Attributed[File]], definitionClasspath: Seq[Attributed[File]], resolvers: Option[Seq[Resolver]], report: Option[UpdateReport]) +{ + val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath +} +object PluginData +{ + @deprecated("Use the alternative that specifies the specific classpaths.", "0.13.0") + def apply(classpath: Seq[Attributed[File]], resolvers: Option[Seq[Resolver]], report: Option[UpdateReport]): PluginData = + PluginData(classpath, Nil, resolvers, report) +} + object EvaluateTask { import Load.BuildStructure diff --git a/main/Load.scala b/main/Load.scala index 8d2fe7f9e..d7186bfe6 100755 --- a/main/Load.scala +++ b/main/Load.scala @@ -15,7 +15,7 @@ package sbt import inc.{FileValueCache, Locate} import Project.{inScope, ScopedKey, ScopeLocal, Setting} import Keys.{appConfiguration, baseDirectory, configuration, fullResolvers, fullClasspath, pluginData, streams, Streams, thisProject, thisProjectRef, update} - import Keys.{isDummy, loadedBuild, parseResult, resolvedScoped, taskDefinitionKey} + import Keys.{exportedProducts, isDummy, loadedBuild, parseResult, resolvedScoped, taskDefinitionKey} import tools.nsc.reporters.ConsoleReporter import Build.{analyzed, data} import Scope.{GlobalScope, ThisScope} @@ -448,9 +448,17 @@ object Load } val autoPluginSettings: Seq[Setting[_]] = inScope(GlobalScope in LocalRootProject)(Seq( Keys.sbtPlugin :== true, - pluginData <<= (fullClasspath in Configurations.Runtime, update, fullResolvers) map ( (cp, rep, rs) => PluginData(cp, Some(rs), Some(rep)) ), + pluginData <<= (exportedProducts in Configurations.Runtime, fullClasspath in Configurations.Runtime, update, fullResolvers) map ( (prod, cp, rep, rs) => + PluginData(removeEntries(cp, prod), prod, Some(rs), Some(rep)) + ), Keys.onLoadMessage <<= Keys.baseDirectory("Loading project definition from " + _) )) + private[this] def removeEntries(cp: Seq[Attributed[File]], remove: Seq[Attributed[File]]): Seq[Attributed[File]] = + { + val files = data(remove).toSet + cp filter { f => !files.contains(f.data) } + } + def enableSbtPlugin(config: LoadBuildConfiguration): LoadBuildConfiguration = config.copy(injectSettings = config.injectSettings.copy( global = autoPluginSettings ++ config.injectSettings.global, @@ -480,15 +488,32 @@ object Load def loadPluginDefinition(dir: File, config: LoadBuildConfiguration, pluginData: PluginData): LoadedPlugins = { - val (definitionClasspath, pluginLoader) = pluginDefinitionLoader(config, pluginData.classpath) - loadPlugins(dir, pluginData.copy(classpath = definitionClasspath), pluginLoader) + val (definitionClasspath, pluginLoader) = pluginDefinitionLoader(config, pluginData) + loadPlugins(dir, pluginData.copy(dependencyClasspath = definitionClasspath), pluginLoader) } - def pluginDefinitionLoader(config: LoadBuildConfiguration, pluginClasspath: Seq[Attributed[File]]): (Seq[Attributed[File]], ClassLoader) = + def pluginDefinitionLoader(config: LoadBuildConfiguration, dependencyClasspath: Seq[Attributed[File]]): (Seq[Attributed[File]], ClassLoader) = + pluginDefinitionLoader(config, dependencyClasspath, Nil) + def pluginDefinitionLoader(config: LoadBuildConfiguration, pluginData: PluginData): (Seq[Attributed[File]], ClassLoader) = + pluginDefinitionLoader(config, pluginData.dependencyClasspath, pluginData.definitionClasspath) + def pluginDefinitionLoader(config: LoadBuildConfiguration, depcp: Seq[Attributed[File]], defcp: Seq[Attributed[File]]): (Seq[Attributed[File]], ClassLoader) = { - val definitionClasspath = if(pluginClasspath.isEmpty) config.classpath else (pluginClasspath ++ config.classpath).distinct + val definitionClasspath = + if(depcp.isEmpty) + config.classpath + else + (depcp ++ config.classpath).distinct val pm = config.pluginManagement - def addToLoader() = pm.loader add Path.toURLs(data(pluginClasspath)) - val pluginLoader = if(pluginClasspath.isEmpty) pm.initialLoader else { addToLoader(); pm.loader } + // only the dependencyClasspath goes in the common plugin class loader ... + def addToLoader() = pm.loader add Path.toURLs(data(depcp)) + + val parentLoader = if(depcp.isEmpty) pm.initialLoader else { addToLoader(); pm.loader } + val pluginLoader = + if(defcp.isEmpty) + parentLoader + else { + // ... the build definition classes get their own loader so that they don't conflict with other build definitions (#511) + ClasspathUtilities.toLoader(data(defcp), parentLoader) + } (definitionClasspath, pluginLoader) } def buildPluginDefinition(dir: File, s: State, config: LoadBuildConfiguration): PluginData = @@ -607,7 +632,7 @@ object Load final class LoadedPlugins(val base: File, val pluginData: PluginData, val loader: ClassLoader, val plugins: Seq[Plugin], val pluginNames: Seq[String]) { def fullClasspath: Seq[Attributed[File]] = pluginData.classpath - def classpath = data(fullClasspath) + def classpath: Seq[File] = data(fullClasspath) } final class BuildUnit(val uri: URI, val localBase: File, val definitions: LoadedDefinitions, val plugins: LoadedPlugins) { diff --git a/sbt/src/sbt-test/project/isolated-build-definitions/build.sbt b/sbt/src/sbt-test/project/isolated-build-definitions/build.sbt new file mode 100644 index 000000000..76d6c9554 --- /dev/null +++ b/sbt/src/sbt-test/project/isolated-build-definitions/build.sbt @@ -0,0 +1,5 @@ +name := "project-runtime" + +scalaVersion := "2.9.1" + +crossPaths := false diff --git a/sbt/src/sbt-test/project/isolated-build-definitions/generator/build.sbt b/sbt/src/sbt-test/project/isolated-build-definitions/generator/build.sbt new file mode 100644 index 000000000..7a1a4ec44 --- /dev/null +++ b/sbt/src/sbt-test/project/isolated-build-definitions/generator/build.sbt @@ -0,0 +1,5 @@ +name := "project" + +scalaVersion := "2.9.1" + +crossPaths := false diff --git a/sbt/src/sbt-test/project/isolated-build-definitions/generator/project/Build.scala b/sbt/src/sbt-test/project/isolated-build-definitions/generator/project/Build.scala new file mode 100644 index 000000000..4a292aff1 --- /dev/null +++ b/sbt/src/sbt-test/project/isolated-build-definitions/generator/project/Build.scala @@ -0,0 +1,7 @@ +import sbt._ +import Keys._ + +object B extends Build { +lazy val project = Project(id = "project", + base = file(".")) +} diff --git a/sbt/src/sbt-test/project/isolated-build-definitions/project/Build.scala b/sbt/src/sbt-test/project/isolated-build-definitions/project/Build.scala new file mode 100644 index 000000000..cb69381c6 --- /dev/null +++ b/sbt/src/sbt-test/project/isolated-build-definitions/project/Build.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object B extends Build +{ + lazy val projectRuntime = Project(id = "project-runtime", base = file(".")). + dependsOn(projectGenerator % "optional") + + + lazy val projectGenerator = ProjectRef(uri("generator/"),"project") +} diff --git a/sbt/src/sbt-test/project/isolated-build-definitions/test b/sbt/src/sbt-test/project/isolated-build-definitions/test new file mode 100644 index 000000000..d5766ca5d --- /dev/null +++ b/sbt/src/sbt-test/project/isolated-build-definitions/test @@ -0,0 +1,5 @@ +# This test checks loading successfully. +# There are two builds that define a Build with the same identifier. +# If the build definitions are incorrectly in the same class loader, one will shadow the other. +> {generator/}project/clean +> project-runtime/clean