Main part of integrating natures into project loading.

This commit is contained in:
Mark Harrah 2014-01-14 12:38:06 -05:00
parent 322f6de655
commit b8619f4aae
13 changed files with 363 additions and 75 deletions

View File

@ -4,6 +4,9 @@ package sbt
import Def.Setting
import Natures._
/** Marks a top-level object so that sbt will wildcard import it for .sbt files, `consoleProject`, and `set`. */
trait AutoImport
/**
An AutoPlugin defines a group of settings and the conditions that the settings are automatically added to a build (called "activation").
The `select` method defines the conditions,
@ -59,15 +62,15 @@ abstract class AutoPlugin
def projectConfigurations: Seq[Configuration] = Nil
/** The [[Setting]]s to add in the scope of each project that activates this AutoPlugin. */
def projectSettings: Seq[Setting[_]] = Nil
def projectSettings: Seq[Setting[_]] = Nil
/** The [[Setting]]s to add to the build scope for each project that activates this AutoPlugin.
* The settings returned here are guaranteed to be added to a given build scope only once
* regardless of how many projects for that build activate this AutoPlugin. */
def buildSettings: Seq[Setting[_]] = Nil
def buildSettings: Seq[Setting[_]] = Nil
/** The [[Setting]]s to add to the global scope exactly once if any project activates this AutoPlugin. */
def globalSettings: Seq[Setting[_]] = Nil
def globalSettings: Seq[Setting[_]] = Nil
// TODO?: def commands: Seq[Command]
}
@ -82,19 +85,30 @@ sealed trait Natures {
final case class Nature(label: String) extends Basic {
/** Constructs a Natures matcher that excludes this Nature. */
def unary_! : Basic = Exclude(this)
override def toString = label
}
object Natures
{
// TODO: allow multiple AutoPlugins to provide the same Nature?
// TODO: translate error messages
/** Select the AutoPlugins to include according to the user-specified natures in `requested` and all discovered AutoPlugins in `defined`.*/
def evaluate(requested: Natures, defined: List[AutoPlugin]): Seq[AutoPlugin] =
{
val byAtom = defined.map(x => (Atom(x.provides.label), x)).toMap
val clauses = Clauses( defined.map(d => asClause(d)) )
val results = Logic.reduce(clauses, flatten(requested).toSet)
results.ordered.map(byAtom)
def compile(defined: List[AutoPlugin]): Natures => Seq[AutoPlugin] =
if(defined.isEmpty)
Types.const(Nil)
else
{
val byAtom = defined.map(x => (Atom(x.provides.label), x)).toMap
val clauses = Clauses( defined.map(d => asClause(d)) )
requestedNatures => {
val results = Logic.reduce(clauses, flatten(requestedNatures).toSet)
results.ordered.flatMap(a => byAtom.get(a).toList)
}
}
def empty: Natures = Empty
private[sbt] final object Empty extends Natures {
def &&(o: Basic): Natures = o
override def toString = "<none>"
}
/** An included or excluded Nature. TODO: better name than Basic. */
@ -103,9 +117,16 @@ object Natures
}
private[sbt] final case class Exclude(n: Nature) extends Basic {
def unary_! : Nature = n
override def toString = s"!$n"
}
private[sbt] final case class And(natures: List[Basic]) extends Natures {
def &&(o: Basic): Natures = And(o :: natures)
override def toString = natures.mkString(", ")
}
private[sbt] def and(a: Natures, b: Natures) = b match {
case Empty => a
case And(ns) => (a /: ns)(_ && _)
case b: Basic => a && b
}
private[sbt] def asClause(ap: AutoPlugin): Clause =
@ -114,11 +135,13 @@ object Natures
private[this] def flatten(n: Natures): Seq[Literal] = n match {
case And(ns) => convertAll(ns)
case b: Basic => convertBasic(b) :: Nil
case Empty => Nil
}
private[this] def convert(n: Natures): Formula = n match {
case And(ns) => convertAll(ns).reduce[Formula](_ && _)
case b: Basic => convertBasic(b)
case Empty => Formula.True
}
private[this] def convertBasic(b: Basic): Literal = b match {
case Exclude(n) => !convertBasic(n)

View File

@ -18,6 +18,7 @@ trait Build
* If None, the root project is the first project in the build's root directory or just the first project if none are in the root directory.*/
def rootProject: Option[Project] = None
}
// TODO 0.14.0: decide if Plugin should be deprecated in favor of AutoPlugin
trait Plugin
{
@deprecated("Override projectSettings or buildSettings instead.", "0.12.0")

View File

@ -41,11 +41,35 @@ final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, Resolv
override def toString = unit.toString
}
// TODO: figure out how to deprecate and drop buildNames
final class LoadedDefinitions(val base: File, val target: Seq[File], val loader: ClassLoader, val builds: Seq[Build], val projects: Seq[Project], val buildNames: Seq[String])
final class LoadedPlugins(val base: File, val pluginData: PluginData, val loader: ClassLoader, val plugins: Seq[Plugin], val pluginNames: Seq[String])
final class DetectedModules[T](val modules: Seq[(String, T)]) {
def names: Seq[String] = modules.map(_._1)
def values: Seq[T] = modules.map(_._2)
}
final class DetectedPlugins(val plugins: DetectedModules[Plugin], val autoImports: DetectedModules[AutoImport], val autoPlugins: DetectedModules[AutoPlugin], val builds: DetectedModules[Build])
{
lazy val imports: Seq[String] = BuildUtil.getImports(plugins.names ++ builds.names ++ autoImports.names)
lazy val compileNatures: Natures => Seq[AutoPlugin] = Natures.compile(autoPlugins.values.toList)
}
final class LoadedPlugins(val base: File, val pluginData: PluginData, val loader: ClassLoader, val detected: DetectedPlugins)
{
/*
// TODO: uncomment before COMMIT for compatibility
@deprecated("Use the primary constructor.", "0.13.2")
def this(base: File, pluginData: PluginData, loader: ClassLoader, plugins: Seq[Plugin], pluginNames: Seq[String]) =
this(base, pluginData, loader, DetectedPlugins(DetectedModules(pluginNames zip plugins), DetectedModules(Nil), DetectedModules(Nil), DetectedModules(Nil)))
@deprecated("Use detected.plugins.values.", "0.13.2")
val plugins = detected.plugins.values
@deprecated("Use detected.plugins.names.", "0.13.2")
val pluginNames = detected.plugins.names
*/
def fullClasspath: Seq[Attributed[File]] = pluginData.classpath
def classpath = data(fullClasspath)
}
final class BuildUnit(val uri: URI, val localBase: File, val definitions: LoadedDefinitions, val plugins: LoadedPlugins)
{
@ -57,6 +81,7 @@ final class LoadedBuild(val root: URI, val units: Map[URI, LoadedBuildUnit])
BuildUtil.checkCycles(units)
def allProjectRefs: Seq[(ProjectRef, ResolvedProject)] = for( (uri, unit) <- units.toSeq; (id, proj) <- unit.defined ) yield ProjectRef(uri, id) -> proj
def extra(data: Settings[Scope])(keyIndex: KeyIndex): BuildUtil[ResolvedProject] = BuildUtil(root, units, keyIndex, data)
private[sbt] def autos = GroupedAutoPlugins(units)
}
final class PartBuild(val root: URI, val units: Map[URI, PartBuildUnit])
sealed trait BuildUnitBase { def rootProjects: Seq[String]; def buildSettings: Seq[Setting[_]] }

View File

@ -35,7 +35,7 @@ final class BuildUtil[Proj](
case _ => None
}
val configurationsForAxis: Option[ResolvedReference] => Seq[String] =
val configurationsForAxis: Option[ResolvedReference] => Seq[String] =
refOpt => configurations(projectForAxis(refOpt)).map(_.name)
}
object BuildUtil
@ -60,8 +60,14 @@ object BuildUtil
}
}
def baseImports: Seq[String] = "import sbt._, Keys._" :: Nil
def getImports(unit: BuildUnit): Seq[String] = getImports(unit.plugins.pluginNames, unit.definitions.buildNames)
def getImports(pluginNames: Seq[String], buildNames: Seq[String]): Seq[String] = baseImports ++ importAllRoot(pluginNames ++ buildNames)
def getImports(unit: BuildUnit): Seq[String] = unit.plugins.detected.imports
@deprecated("Use getImports(Seq[String]).", "0.13.2")
def getImports(pluginNames: Seq[String], buildNames: Seq[String]): Seq[String] = getImports(pluginNames ++ buildNames)
def getImports(names: Seq[String]): Seq[String] = baseImports ++ importAllRoot(names)
def importAll(values: Seq[String]): Seq[String] = if(values.isEmpty) Nil else values.map( _ + "._" ).mkString("import ", ", ", "") :: Nil
def importAllRoot(values: Seq[String]): Seq[String] = importAll(values map rootedName)
def rootedName(s: String): String = if(s contains '.') "_root_." + s else s

View File

@ -0,0 +1,20 @@
package sbt
import Def.Setting
import java.net.URI
final class GroupedAutoPlugins(val all: Seq[AutoPlugin], val byBuild: Map[URI, Seq[AutoPlugin]])
{
def globalSettings: Seq[Setting[_]] = all.flatMap(_.globalSettings)
def buildSettings(uri: URI): Seq[Setting[_]] = byBuild.getOrElse(uri, Nil).flatMap(_.buildSettings)
}
object GroupedAutoPlugins
{
private[sbt] def apply(units: Map[URI, LoadedBuildUnit]): GroupedAutoPlugins =
{
val byBuild: Map[URI, Seq[AutoPlugin]] = units.mapValues(unit => unit.defined.values.flatMap(_.autoPlugins).toSeq.distinct).toMap
val all: Seq[AutoPlugin] = byBuild.values.toSeq.flatten.distinct
new GroupedAutoPlugins(all, byBuild)
}
}

View File

@ -180,7 +180,7 @@ object Load
val keys = Index.allKeys(settings)
val attributeKeys = Index.attributeKeys(data) ++ keys.map(_.key)
val scopedKeys = keys ++ data.allKeys( (s,k) => ScopedKey(s,k))
val projectsMap = projects.mapValues(_.defined.keySet)
val projectsMap = projects.mapValues(_.defined.keySet).toMap
val keyIndex = KeyIndex(scopedKeys, projectsMap)
val aggIndex = KeyIndex.aggregate(scopedKeys, extra(keyIndex), projectsMap)
new sbt.StructureIndex(Index.stringToKeyMap(attributeKeys), Index.taskToKeyMap(data), Index.triggers(data), keyIndex, aggIndex)
@ -201,10 +201,10 @@ object Load
{
((loadedBuild in GlobalScope :== loaded) +:
transformProjectOnly(loaded.root, rootProject, injectSettings.global)) ++
inScope(GlobalScope)( pluginGlobalSettings(loaded) ) ++
inScope(GlobalScope)( pluginGlobalSettings(loaded) ++ loaded.autos.globalSettings ) ++
loaded.units.toSeq.flatMap { case (uri, build) =>
val plugins = build.unit.plugins.plugins
val pluginBuildSettings = plugins.flatMap(_.buildSettings)
val plugins = build.unit.plugins.detected.plugins.values
val pluginBuildSettings = plugins.flatMap(_.buildSettings) ++ loaded.autos.buildSettings(uri)
val pluginNotThis = plugins.flatMap(_.settings) filterNot isProjectThis
val projectSettings = build.defined flatMap { case (id, project) =>
val ref = ProjectRef(uri, id)
@ -220,9 +220,10 @@ object Load
buildSettings ++ projectSettings
}
}
@deprecated("Does not account for AutoPlugins and will be made private.", "0.13.2")
def pluginGlobalSettings(loaded: sbt.LoadedBuild): Seq[Setting[_]] =
loaded.units.toSeq flatMap { case (_, build) =>
build.unit.plugins.plugins flatMap { _.globalSettings }
build.unit.plugins.detected.plugins.values flatMap { _.globalSettings }
}
@deprecated("No longer used.", "0.13.0")
@ -368,10 +369,11 @@ object Load
def resolveProjects(loaded: sbt.PartBuild): sbt.LoadedBuild =
{
val rootProject = getRootProject(loaded.units)
new sbt.LoadedBuild(loaded.root, loaded.units map { case (uri, unit) =>
val units = loaded.units map { case (uri, unit) =>
IO.assertAbsolute(uri)
(uri, resolveProjects(uri, unit, rootProject))
})
}
new sbt.LoadedBuild(loaded.root, units)
}
def resolveProjects(uri: URI, unit: sbt.PartBuildUnit, rootProject: URI => String): sbt.LoadedBuildUnit =
{
@ -399,10 +401,10 @@ object Load
def getBuild[T](map: Map[URI, T], uri: URI): T =
map.getOrElse(uri, noBuild(uri))
def emptyBuild(uri: URI) = sys.error("No root project defined for build unit '" + uri + "'")
def noBuild(uri: URI) = sys.error("Build unit '" + uri + "' not defined.")
def noProject(uri: URI, id: String) = sys.error("No project '" + id + "' defined in '" + uri + "'.")
def noConfiguration(uri: URI, id: String, conf: String) = sys.error("No configuration '" + conf + "' defined in project '" + id + "' in '" + uri +"'")
def emptyBuild(uri: URI) = sys.error(s"No root project defined for build unit '$uri'")
def noBuild(uri: URI) = sys.error(s"Build unit '$uri' not defined.")
def noProject(uri: URI, id: String) = sys.error(s"No project '$id' defined in '$uri'.")
def noConfiguration(uri: URI, id: String, conf: String) = sys.error(s"No configuration '$conf' defined in project '$id' in '$uri'")
def loadUnit(uri: URI, localBase: File, s: State, config: sbt.LoadBuildConfiguration): sbt.BuildUnit =
{
@ -410,15 +412,13 @@ object Load
val defDir = projectStandard(normBase)
val plugs = plugins(defDir, s, config.copy(pluginManagement = config.pluginManagement.forPlugin))
val defNames = analyzed(plugs.fullClasspath) flatMap findDefinitions
val defsScala = if(defNames.isEmpty) Nil else loadDefinitions(plugs.loader, defNames)
val imports = BuildUtil.getImports(plugs.pluginNames, defNames)
val defsScala = plugs.detected.builds.values
lazy val eval = mkEval(plugs.classpath, defDir, plugs.pluginData.scalacOptions)
val initialProjects = defsScala.flatMap(b => projectsFromBuild(b, normBase))
val memoSettings = new mutable.HashMap[File, LoadedSbtFile]
def loadProjects(ps: Seq[Project]) = loadTransitive(ps, normBase, imports, plugs, () => eval, config.injectSettings, Nil, memoSettings)
def loadProjects(ps: Seq[Project]) = loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings)
val loadedProjectsRaw = loadProjects(initialProjects)
val hasRoot = loadedProjectsRaw.exists(_.base == normBase) || defsScala.exists(_.rootProject.isDefined)
val (loadedProjects, defaultBuildIfNone) =
@ -434,7 +434,7 @@ object Load
}
val defs = if(defsScala.isEmpty) defaultBuildIfNone :: Nil else defsScala
val loadedDefs = new sbt.LoadedDefinitions(defDir, Nil, plugs.loader, defs, loadedProjects, defNames)
val loadedDefs = new sbt.LoadedDefinitions(defDir, Nil, plugs.loader, defs, loadedProjects, plugs.detected.builds.names)
new sbt.BuildUnit(uri, normBase, loadedDefs, plugs)
}
@ -460,16 +460,19 @@ object Load
private[this] def projectsFromBuild(b: Build, base: File): Seq[Project] =
b.projectDefinitions(base).map(resolveBase(base))
private[this] def loadTransitive(newProjects: Seq[Project], buildBase: File, imports: Seq[String], plugins: sbt.LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, acc: Seq[Project], memoSettings: mutable.Map[File, LoadedSbtFile]): Seq[Project] =
private[this] def loadTransitive(newProjects: Seq[Project], buildBase: File, plugins: sbt.LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, acc: Seq[Project], memoSettings: mutable.Map[File, LoadedSbtFile]): Seq[Project] =
{
def loadSbtFiles(auto: AddSettings, base: File): LoadedSbtFile =
loadSettings(auto, base, imports, plugins, eval, injectSettings, memoSettings)
def loadSbtFiles(auto: AddSettings, base: File, autoPlugins: Seq[AutoPlugin]): LoadedSbtFile =
loadSettings(auto, base, plugins, eval, injectSettings, memoSettings, autoPlugins)
def loadForProjects = newProjects map { project =>
val loadedSbtFiles = loadSbtFiles(project.auto, project.base)
val transformed = project.copy(settings = (project.settings: Seq[Setting[_]]) ++ loadedSbtFiles.settings)
val autoPlugins = plugins.detected.compileNatures(project.natures)
val autoConfigs = autoPlugins.flatMap(_.projectConfigurations)
val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins)
val newSettings = (project.settings: Seq[Setting[_]]) ++ loadedSbtFiles.settings
val transformed = project.copy(settings = newSettings).setAutoPlugins(autoPlugins).overrideConfigs(autoConfigs : _*)
(transformed, loadedSbtFiles.projects)
}
def defaultLoad = loadSbtFiles(AddSettings.defaultSbtFiles, buildBase).projects
def defaultLoad = loadSbtFiles(AddSettings.defaultSbtFiles, buildBase, Nil).projects
val (nextProjects, loadedProjects) =
if(newProjects.isEmpty) // load the .sbt files in the root directory to look for Projects
(defaultLoad, acc)
@ -481,10 +484,10 @@ object Load
if(nextProjects.isEmpty)
loadedProjects
else
loadTransitive(nextProjects, buildBase, imports, plugins, eval, injectSettings, loadedProjects, memoSettings)
loadTransitive(nextProjects, buildBase, plugins, eval, injectSettings, loadedProjects, memoSettings)
}
private[this] def loadSettings(auto: AddSettings, projectBase: File, buildImports: Seq[String], loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile]): LoadedSbtFile =
private[this] def loadSettings(auto: AddSettings, projectBase: File, loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile], autoPlugins: Seq[AutoPlugin]): LoadedSbtFile =
{
lazy val defaultSbtFiles = configurationSources(projectBase)
def settings(ss: Seq[Setting[_]]) = new LoadedSbtFile(ss, Nil, Nil)
@ -499,14 +502,20 @@ object Load
lf
}
def loadSettingsFile(src: File): LoadedSbtFile =
EvaluateConfigurations.evaluateSbtFile(eval(), src, IO.readLines(src), buildImports, 0)(loader)
EvaluateConfigurations.evaluateSbtFile(eval(), src, IO.readLines(src), loadedPlugins.detected.imports, 0)(loader)
import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence}
def pluginSettings(f: Plugins) = {
val included = loadedPlugins.detected.plugins.values.filter(f.include) // don't apply the filter to AutoPlugins, only Plugins
val oldStyle = included.flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings)
val autoStyle = autoPlugins.flatMap(_.projectSettings)
oldStyle ++ autoStyle
}
def expand(auto: AddSettings): LoadedSbtFile = auto match {
case User => settings(injectSettings.projectLoaded(loader))
case sf: SbtFiles => loadSettings( sf.files.map(f => IO.resolve(projectBase, f)))
case sf: DefaultSbtFiles => loadSettings( defaultSbtFiles.filter(sf.include))
case f: Plugins => settings(loadedPlugins.plugins.filter(f.include).flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings))
case p: Plugins => settings(pluginSettings(p))
case q: Sequence => (LoadedSbtFile.empty /: q.sequence) { (b,add) => b.merge( expand(add) ) }
}
expand(auto)
@ -599,27 +608,48 @@ object Load
config.evalPluginDef(pluginDef, pluginState)
}
/*
// TODO: UNCOMMENT BEFORE COMMIT
@deprecated("Use ModuleUtilities.getCheckedObjects[Build].", "0.13.2")
def loadDefinitions(loader: ClassLoader, defs: Seq[String]): Seq[Build] =
defs map { definition => loadDefinition(loader, definition) }
@deprecated("Use ModuleUtilities.getCheckedObject[Build].", "0.13.2")
def loadDefinition(loader: ClassLoader, definition: String): Build =
ModuleUtilities.getObject(definition, loader).asInstanceOf[Build]
*/
def loadPlugins(dir: File, data: PluginData, loader: ClassLoader): sbt.LoadedPlugins =
{
val (pluginNames, plugins) = if(data.classpath.isEmpty) (Nil, Nil) else {
val names = getPluginNames(data.classpath, loader)
val loaded =
try loadPlugins(loader, names)
catch {
case e: ExceptionInInitializerError =>
val cause = e.getCause
if(cause eq null) throw e else throw cause
case e: LinkageError => incompatiblePlugins(data, e)
}
(names, loaded)
}
new sbt.LoadedPlugins(dir, data, loader, plugins, pluginNames)
// TODO: binary detection for builds, autoImports, autoPlugins
import AutoBinaryResource._
val plugins = detectModules[Plugin](data, loader, Plugins)
val builds = detectModules[Build](data, loader, Builds)
val autoImports = detectModules[AutoImport](data, loader, AutoImports)
val autoPlugins = detectModules[AutoPlugin](data, loader, AutoPlugins)
val detected = new DetectedPlugins(plugins, autoImports, autoPlugins, builds)
new sbt.LoadedPlugins(dir, data, loader, detected)
}
private[this] def detectModules[T](data: PluginData, loader: ClassLoader, resourceName: String)(implicit mf: reflect.ClassManifest[T]): DetectedModules[T] =
{
val classpath = data.classpath
val namesAndValues = if(classpath.isEmpty) Nil else {
val names = discoverModuleNames(classpath, loader, resourceName, mf.erasure.getName)
loadModules[T](data, names, loader)
}
new DetectedModules(namesAndValues)
}
private[this] def loadModules[T: ClassManifest](data: PluginData, names: Seq[String], loader: ClassLoader): Seq[(String,T)] =
try ModuleUtilities.getCheckedObjects[T](names, loader)
catch {
case e: ExceptionInInitializerError =>
val cause = e.getCause
if(cause eq null) throw e else throw cause
case e: LinkageError => incompatiblePlugins(data, e)
}
private[this] def incompatiblePlugins(data: PluginData, t: LinkageError): Nothing =
{
val evicted = data.report.toList.flatMap(_.configurations.flatMap(_.evicted))
@ -629,26 +659,54 @@ object Load
val msgExtra = if(evictedStrings.isEmpty) "" else "\nNote that conflicts were resolved for some dependencies:\n\t" + evictedStrings.mkString("\n\t")
throw new IncompatiblePluginsException(msgBase + msgExtra, t)
}
def getPluginNames(classpath: Seq[Attributed[File]], loader: ClassLoader): Seq[String] =
( binaryPlugins(data(classpath), loader) ++ (analyzed(classpath) flatMap findPlugins) ).distinct
def discoverModuleNames(classpath: Seq[Attributed[File]], loader: ClassLoader, resourceName: String, moduleTypes: String*): Seq[String] =
(
binaryPlugins(data(classpath), loader, resourceName) ++
(analyzed(classpath) flatMap (a => discover(a, moduleTypes : _*)))
).distinct
@deprecated("Replaced by the more general discoverModuleNames and will be made private.", "0.13.2")
def getPluginNames(classpath: Seq[Attributed[File]], loader: ClassLoader): Seq[String] =
discoverModuleNames(classpath, loader, AutoBinaryResource.Plugins, classOf[Plugin].getName)
/*
TODO: UNCOMMENT BEFORE COMMIT
@deprecated("Explicitly specify the resource name.", "0.13.2")
def binaryPlugins(classpath: Seq[File], loader: ClassLoader): Seq[String] =
binaryPlugins(classpath, loader, AutoBinaryResource.Plugins)
*/
object AutoBinaryResource {
final val AutoPlugins = "sbt/sbt.autoplugins"
final val Plugins = "sbt/sbt.plugins"
final val Builds = "sbt/sbt.builds"
final val AutoImports = "sbt/sbt.autoimports"
}
def binaryPlugins(classpath: Seq[File], loader: ClassLoader, resourceName: String): Seq[String] =
{
import collection.JavaConversions._
loader.getResources("sbt/sbt.plugins").toSeq.filter(onClasspath(classpath)) flatMap { u =>
loader.getResources(resourceName).toSeq.filter(onClasspath(classpath)) flatMap { u =>
IO.readLinesURL(u).map( _.trim).filter(!_.isEmpty)
}
}
def onClasspath(classpath: Seq[File])(url: URL): Boolean =
IO.urlAsFile(url) exists (classpath.contains _)
/*
// TODO: UNCOMMENT BEFORE COMMIT
@deprecated("Use ModuleUtilities.getCheckedObjects[Plugin].", "0.13.2")
def loadPlugins(loader: ClassLoader, pluginNames: Seq[String]): Seq[Plugin] =
pluginNames.map(pluginName => loadPlugin(pluginName, loader))
ModuleUtilities.getCheckedObjects[Plugin](loader, pluginNames)
@deprecated("Use ModuleUtilities.getCheckedObject[Plugin].", "0.13.2")
def loadPlugin(pluginName: String, loader: ClassLoader): Plugin =
ModuleUtilities.getObject(pluginName, loader).asInstanceOf[Plugin]
ModuleUtilities.getCheckedObject[Plugin](pluginName, loader)
@deprecated("No longer used.", "0.13.2")
def findPlugins(analysis: inc.Analysis): Seq[String] = discover(analysis, "sbt.Plugin")
*/
def findDefinitions(analysis: inc.Analysis): Seq[String] = discover(analysis, "sbt.Build")
def discover(analysis: inc.Analysis, subclasses: String*): Seq[String] =
{

View File

@ -125,7 +125,8 @@ object BuiltinCommands
def aboutPlugins(e: Extracted): String =
{
val allPluginNames = e.structure.units.values.flatMap(_.unit.plugins.pluginNames).toSeq.distinct
def list(b: BuildUnit) = b.plugins.detected.autoPlugins.values.map(_.provides) ++ b.plugins.detected.plugins.names
val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct
if(allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "")
}
def aboutScala(s: State, e: Extracted): String =

View File

@ -50,33 +50,52 @@ sealed trait ProjectDefinition[PR <: ProjectReference]
/** Configures the sources of automatically appended settings.*/
def auto: AddSettings
/** The [[Natures]] associated with this project.
A [[Nature]] is a common label that is used by plugins to determine what settings, if any, to add to a project. */
def natures: Natures
/** The [[AutoPlugin]]s enabled for this project. This value is only available on a loaded Project. */
private[sbt] def autoPlugins: Seq[AutoPlugin]
override final def hashCode: Int = id.hashCode ^ base.hashCode ^ getClass.hashCode
override final def equals(o: Any) = o match {
case p: ProjectDefinition[_] => p.getClass == this.getClass && p.id == id && p.base == base
case _ => false
}
override def toString = "Project(id: " + id + ", base: " + base + ", aggregate: " + aggregate + ", dependencies: " + dependencies + ", configurations: " + configurations + ")"
override def toString =
{
val agg = ifNonEmpty("aggregate", aggregate)
val dep = ifNonEmpty("dependencies", dependencies)
val conf = ifNonEmpty("configurations", configurations)
val autos = ifNonEmpty("autoPlugins", autoPlugins.map(_.provides))
val fields = s"id $id" :: s"base: $base" :: agg ::: dep ::: conf ::: (s"natures: List($natures)" :: autos)
s"Project(${fields.mkString(", ")})"
}
private[this] def ifNonEmpty[T](label: String, ts: Iterable[T]): List[String] = if(ts.isEmpty) Nil else s"$label: $ts" :: Nil
}
sealed trait Project extends ProjectDefinition[ProjectReference]
{
// TODO: add parameters for natures and autoPlugins in 0.14.0 (not reasonable to do in a binary compatible way in 0.13)
def copy(id: String = id, base: File = base, aggregate: => Seq[ProjectReference] = aggregate, dependencies: => Seq[ClasspathDep[ProjectReference]] = dependencies,
delegates: => Seq[ProjectReference] = delegates, settings: => Seq[Setting[_]] = settings, configurations: Seq[Configuration] = configurations,
auto: AddSettings = auto): Project =
Project(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto)
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, natures, autoPlugins)
def resolve(resolveRef: ProjectReference => ProjectRef): ResolvedProject =
{
def resolveRefs(prs: Seq[ProjectReference]) = prs map resolveRef
def resolveDeps(ds: Seq[ClasspathDep[ProjectReference]]) = ds map resolveDep
def resolveDep(d: ClasspathDep[ProjectReference]) = ResolvedClasspathDependency(resolveRef(d.project), d.configuration)
resolved(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates), settings, configurations, auto)
resolved(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates),
settings, configurations, auto, natures, autoPlugins)
}
def resolveBuild(resolveRef: ProjectReference => ProjectReference): Project =
{
def resolveRefs(prs: Seq[ProjectReference]) = prs map resolveRef
def resolveDeps(ds: Seq[ClasspathDep[ProjectReference]]) = ds map resolveDep
def resolveDep(d: ClasspathDep[ProjectReference]) = ClasspathDependency(resolveRef(d.project), d.configuration)
apply(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates), settings, configurations, auto)
unresolved(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates),
settings, configurations, auto, natures, autoPlugins)
}
/** Applies the given functions to this Project.
@ -116,8 +135,24 @@ sealed trait Project extends ProjectDefinition[ProjectReference]
/** Sets the list of .sbt files to parse for settings to be appended to this project's settings.
* Any configured .sbt files are removed from this project's list.*/
def setSbtFiles(files: File*): Project = copy(auto = AddSettings.append( AddSettings.clearSbtFiles(auto), AddSettings.sbtFiles(files: _*)) )
/** Sets the [[Natures]] of this project.
A [[Nature]] is a common label that is used by plugins to determine what settings, if any, to add to a project. */
def addNatures(ns: Natures): Project = {
// TODO: for 0.14.0, use copy when it has the additional `natures` parameter
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, Natures.and(natures, ns), autoPlugins)
}
/** Definitively set the [[AutoPlugin]]s for this project. */
private[sbt] def setAutoPlugins(autos: Seq[AutoPlugin]): Project = {
// TODO: for 0.14.0, use copy when it has the additional `autoPlugins` parameter
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, natures, autos)
}
}
sealed trait ResolvedProject extends ProjectDefinition[ProjectRef] {
/** The [[AutoPlugin]]s enabled for this project as computed from [[natures]].*/
def autoPlugins: Seq[AutoPlugin]
}
sealed trait ResolvedProject extends ProjectDefinition[ProjectRef]
sealed trait ClasspathDep[PR <: ProjectReference] { def project: PR; def configuration: Option[String] }
final case class ResolvedClasspathDependency(project: ProjectRef, configuration: Option[String]) extends ClasspathDep[ProjectRef]
@ -150,23 +185,22 @@ object Project extends ProjectExtra
Def.showRelativeKey( ProjectRef(loaded.root, loaded.units(loaded.root).rootProjects.head), loaded.allProjectRefs.size > 1, keyNameColor)
private abstract class ProjectDef[PR <: ProjectReference](val id: String, val base: File, aggregate0: => Seq[PR], dependencies0: => Seq[ClasspathDep[PR]],
delegates0: => Seq[PR], settings0: => Seq[Def.Setting[_]], val configurations: Seq[Configuration], val auto: AddSettings) extends ProjectDefinition[PR]
delegates0: => Seq[PR], settings0: => Seq[Def.Setting[_]], val configurations: Seq[Configuration], val auto: AddSettings,
val natures: Natures, val autoPlugins: Seq[AutoPlugin]) extends ProjectDefinition[PR]
{
lazy val aggregate = aggregate0
lazy val dependencies = dependencies0
lazy val delegates = delegates0
lazy val settings = settings0
Dag.topologicalSort(configurations)(_.extendsConfigs) // checks for cyclic references here instead of having to do it in Scope.delegates
}
// TODO: add parameter for natures in 0.14.0
def apply(id: String, base: File, aggregate: => Seq[ProjectReference] = Nil, dependencies: => Seq[ClasspathDep[ProjectReference]] = Nil,
delegates: => Seq[ProjectReference] = Nil, settings: => Seq[Def.Setting[_]] = defaultSettings, configurations: Seq[Configuration] = Configurations.default,
auto: AddSettings = AddSettings.allDefaults): Project =
{
validProjectID(id).foreach(errMsg => sys.error("Invalid project ID: " + errMsg))
new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto) with Project
}
unresolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Natures.empty, Nil)
/** Returns None if `id` is a valid Project ID or Some containing the parser error message if it is not.*/
def validProjectID(id: String): Option[String] = DefaultParsers.parse(id, DefaultParsers.ID).left.toOption
@ -185,9 +219,23 @@ object Project extends ProjectExtra
* This is a best effort implementation, since valid characters are not documented or consistent.*/
def normalizeModuleID(id: String): String = normalizeBase(id)
@deprecated("Will be removed.", "0.13.2")
def resolved(id: String, base: File, aggregate: => Seq[ProjectRef], dependencies: => Seq[ResolvedClasspathDependency], delegates: => Seq[ProjectRef],
settings: Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings): ResolvedProject =
new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations, auto) with ResolvedProject
resolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Natures.empty, Nil)
private def resolved(id: String, base: File, aggregate: => Seq[ProjectRef], dependencies: => Seq[ClasspathDep[ProjectRef]],
delegates: => Seq[ProjectRef], settings: Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings,
natures: Natures, autoPlugins: Seq[AutoPlugin]): ResolvedProject =
new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations, auto, natures, autoPlugins) with ResolvedProject
private def unresolved(id: String, base: File, aggregate: => Seq[ProjectReference], dependencies: => Seq[ClasspathDep[ProjectReference]],
delegates: => Seq[ProjectReference], settings: => Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings,
natures: Natures, autoPlugins: Seq[AutoPlugin]): Project =
{
validProjectID(id).foreach(errMsg => sys.error("Invalid project ID: " + errMsg))
new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto, natures, autoPlugins) with Project
}
def defaultSettings: Seq[Def.Setting[_]] = Defaults.defaultSettings
@ -307,7 +355,7 @@ object Project extends ProjectExtra
def details(structure: BuildStructure, actual: Boolean, scope: Scope, key: AttributeKey[_])(implicit display: Show[ScopedKey[_]]): String =
{
val scoped = ScopedKey(scope,key)
val data = scopedKeyData(structure, scope, key) map {_.description} getOrElse {"No entry for key."}
val description = key.description match { case Some(desc) => "Description:\n\t" + desc + "\n"; case None => "" }
@ -413,7 +461,7 @@ object Project extends ProjectExtra
import DefaultParsers._
val loadActionParser = token(Space ~> ("plugins" ^^^ Plugins | "return" ^^^ Return)) ?? Current
val ProjectReturn = AttributeKey[List[File]]("project-return", "Maintains a stack of builds visited using reload.")
def projectReturn(s: State): List[File] = getOrNil(s, ProjectReturn)
def inPluginProject(s: State): Boolean = projectReturn(s).toList.length > 1

View File

@ -73,7 +73,7 @@ object Sbt extends Build
lazy val datatypeSub = baseProject(utilPath /"datatype", "Datatype Generator") dependsOn(ioSub)
// cross versioning
lazy val crossSub = baseProject(utilPath / "cross", "Cross") settings(inConfig(Compile)(Transform.crossGenSettings): _*)
// A monotonic logic that includes restricted negation as failure
// A logic with restricted negation as failure for a unique, stable model
lazy val logicSub = baseProject(utilPath / "logic", "Logic").dependsOn(collectionSub, relationSub)
/* **** Intermediate-level Modules **** */

View File

@ -0,0 +1,34 @@
// !C will exclude C, and thus D, from being auto-added
lazy val a = project.addNatures(A && B && !C)
// without B, C is not added
lazy val b = project.addNatures(A)
// with both A and B, C is selected, which in turn selects D
lazy val c = project.addNatures(A && B)
// with no natures defined, nothing is auto-added
lazy val d = project
check := {
val ddel = (del in d).?.value // should be None
same(ddel, None, "del in d")
val bdel = (del in b).?.value // should be None
same(bdel, None, "del in b")
val adel = (del in a).?.value // should be None
same(adel, None, "del in a")
//
val buildValue = (demo in ThisBuild).value
same(buildValue, "build 0", "demo in ThisBuild")
val globalValue = (demo in Global).value
same(globalValue, "global 0", "demo in Global")
val projValue = (demo in c).value
same(projValue, "project c Q R", "demo in c")
val qValue = (del in c in q).value
same(qValue, " Q R", "del in c in q")
}
def same[T](actual: T, expected: T, label: String) {
assert(actual == expected, s"Expected '$expected' for `$label`, got '$actual'")
}

View File

@ -0,0 +1,65 @@
import sbt._
import sbt.Keys.{name, resolvedScoped}
import java.util.concurrent.atomic.{AtomicInteger => AInt}
object AI extends AutoImport
{
lazy val A = Nature("A")
lazy val B = Nature("B")
lazy val C = Nature("C")
lazy val D = Nature("D")
lazy val E = Nature("E")
lazy val q = config("q")
lazy val p = config("p").extend(q)
lazy val demo = settingKey[String]("A demo setting.")
lazy val del = settingKey[String]("Another demo setting.")
lazy val check = settingKey[Unit]("Verifies settings are as they should be.")
}
import AI._
object Q extends AutoPlugin
{
def select: Natures = A && B
def provides = C
override def projectConfigurations: Seq[Configuration] =
p ::
q ::
Nil
override def projectSettings: Seq[Setting[_]] =
(demo := s"project ${name.value}") ::
(del in q := " Q") ::
Nil
override def buildSettings: Seq[Setting[_]] =
(demo := s"build ${buildCount.getAndIncrement}") ::
Nil
override def globalSettings: Seq[Setting[_]] =
(demo := s"global ${globalCount.getAndIncrement}") ::
Nil
// used to ensure the build-level and global settings are only added once
private[this] val buildCount = new AInt(0)
private[this] val globalCount = new AInt(0)
}
object R extends AutoPlugin
{
def select = C && !D
def provides = E
override def projectSettings = Seq(
// tests proper ordering: R requires C, so C settings should come first
del in q += " R",
// tests that configurations are properly registered, enabling delegation from p to q
demo += (del in p).value
)
}

View File

@ -0,0 +1 @@
> check

View File

@ -6,7 +6,7 @@ package sbt
object ModuleUtilities
{
/** Reflectively loads and returns the companion object for top-level class `className` from `loader`.
* The class name should not include the `$` that scalac appends to the underlying jvm class for
* The class name should not include the `$` that scalac appends to the underlying jvm class for
* a companion object. */
def getObject(className: String, loader: ClassLoader): AnyRef =
{
@ -14,4 +14,10 @@ object ModuleUtilities
val singletonField = obj.getField("MODULE$")
singletonField.get(null)
}
def getCheckedObject[T](className: String, loader: ClassLoader)(implicit mf: reflect.ClassManifest[T]): T =
mf.erasure.cast(getObject(className, loader)).asInstanceOf[T]
def getCheckedObjects[T](classNames: Seq[String], loader: ClassLoader)(implicit mf: reflect.ClassManifest[T]): Seq[(String,T)] =
classNames.map(name => (name, getCheckedObject(name, loader)))
}