Proper isolation of build definition classes. Fixes #536, #511.

This commit is contained in:
Mark Harrah 2012-08-30 16:37:56 -04:00
parent 9d39b5d9e1
commit ed902ea565
7 changed files with 78 additions and 10 deletions

View File

@ -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

View File

@ -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)
{

View File

@ -0,0 +1,5 @@
name := "project-runtime"
scalaVersion := "2.9.1"
crossPaths := false

View File

@ -0,0 +1,5 @@
name := "project"
scalaVersion := "2.9.1"
crossPaths := false

View File

@ -0,0 +1,7 @@
import sbt._
import Keys._
object B extends Build {
lazy val project = Project(id = "project",
base = file("."))
}

View File

@ -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")
}

View File

@ -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