Merge pull request #1263 from sbt/wip/docs-and-cleanups

addPlugins => enablePlugins
This commit is contained in:
eugene yokota 2014-04-16 13:59:40 -04:00
commit 583391a069
5 changed files with 81 additions and 9 deletions

View File

@ -11,21 +11,51 @@ package sbt
import Scope.GlobalScope
import scala.annotation.tailrec
/**
* This file is responsible for compiling the .sbt files used to configure sbt builds.
*
* Compilation is done in three phases:
*
* 1. Parsing high-level constructs (definitions, settings, imports)
* 2. Compiling scala code into local .class files
* 3. Evaluating the expressions and obtaining in-memory objects of the results (Setting[_] instances, or val references).
*
*
*/
object EvaluateConfigurations
{
/**
* This represents the parsed expressions in a build sbt, as well as where they were defined.
*/
private[this] final class ParsedFile(val imports: Seq[(String,Int)], val definitions: Seq[(String,LineRange)], val settings: Seq[(String,LineRange)])
/** The keywords we look for when classifying a string as a definition. */
private[this] val DefinitionKeywords = Seq("lazy val ", "def ", "val ")
/** Using an evaluating instance of the scala compiler, a sequence of files and
* the default imports to use, this method will take a ClassLoader of sbt-classes and
* return a parsed, compiled + evaluated [[LoadedSbtFile]]. The result has
* raw sbt-types that can be accessed and used.
*/
def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): ClassLoader => LoadedSbtFile =
{
val loadFiles = srcs.sortBy(_.getName) map { src => evaluateSbtFile(eval, src, IO.readLines(src), imports, 0) }
loader => (LoadedSbtFile.empty /: loadFiles) { (loaded, load) => loaded merge load(loader) }
}
/**
* Reads a given .sbt file and evaluates it into a sequence of setting values.
*/
def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): ClassLoader => Seq[Setting[_]] =
evaluateConfiguration(eval, src, IO.readLines(src), imports, 0)
/**
* Parses a sequence of build.sbt lines into a [[ParsedFile]]. The result contains
* a fragmentation of all imports, settings and definitions.
*
* @param buildinImports The set of import statements to add to those parsed in the .sbt file.
*/
private[this] def parseConfiguration(lines: Seq[String], builtinImports: Seq[String], offset: Int): ParsedFile =
{
val (importStatements, settingsAndDefinitions) = splitExpressions(lines)
@ -34,12 +64,33 @@ object EvaluateConfigurations
new ParsedFile(allImports, definitions, settings)
}
/**
* Evaluates a parsed sbt configuration file.
*
* @param eval The evaluating scala compiler instance we use to handle evaluating scala configuration.
* @param file The file we've parsed
* @param imports The default imports to use in this .sbt configuration
* @param lines The lines of the configurtion we'd like to evaluate.
*
* @return Just the Setting[_] instances defined in the .sbt file.
*/
def evaluateConfiguration(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => Seq[Setting[_]] =
{
val l = evaluateSbtFile(eval, file, lines, imports, offset)
loader => l(loader).settings
}
/**
* Evaluates a parsed sbt configuration file.
*
* @param eval The evaluating scala compiler instance we use to handle evaluating scala configuration.
* @param file The file we've parsed
* @param lines The lines of the configurtion we'd like to evaluate.
* @param imports The default imports to use in this .sbt configuration.
*
* @return A function which can take an sbt classloader and return the raw types/configuratoin
* which was compiled/parsed for the given file.
*/
private[sbt] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => LoadedSbtFile =
{
val name = file.getPath
@ -58,6 +109,7 @@ object EvaluateConfigurations
val loadSettings = flatten(settings)
loader => new LoadedSbtFile(loadSettings(loader), projects(loader), importDefs)
}
/** move a project to be relative to this file after we've evaluated it. */
private[this] def resolveBase(f: File, p: Project) = p.copy(base = IO.resolve(f, p.base))
def flatten(mksettings: Seq[ClassLoader => Seq[Setting[_]]]): ClassLoader => Seq[Setting[_]] =
loader => mksettings.flatMap(_ apply loader)
@ -66,10 +118,26 @@ object EvaluateConfigurations
def addOffsetToRange(offset: Int, ranges: Seq[(String,LineRange)]): Seq[(String,LineRange)] =
ranges.map { case (s, r) => (s, r shift offset) }
/**
* The name of the class we cast DSL "setting" (vs. definition) lines to.
*/
val SettingsDefinitionName = {
val _ = classOf[sbt.Def.SettingsDefinition] // this line exists to try to provide a compile-time error when the following line needs to be changed
"sbt.Def.SettingsDefinition"
}
/**
* This actually compiles a scala expression which represents a Seq[Setting[_]], although the
* expression may be just a single setting.
*
* @param eval The mechanism to compile and evaluate Scala expressions.
* @param name The name for the thing we're compiling
* @param imports The scala imports to have in place when we compile the expression
* @param expression The scala expression we're compiling
* @param range The original position in source of the expression, for error messages.
*
* @return A method that given an sbt classloader, can return the actual Seq[Setting[_]] defined by
* the expression.
*/
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, range: LineRange): ClassLoader => Seq[Setting[_]] =
{
val result = try {
@ -86,6 +154,10 @@ object EvaluateConfigurations
private[this] def fstS(f: String => Boolean): ((String,Int)) => Boolean = { case (s,i) => f(s) }
private[this] def firstNonSpaceIs(lit: String) = (_: String).view.dropWhile(isSpace).startsWith(lit)
private[this] def or[A](a: A => Boolean, b: A => Boolean): A => Boolean = in => a(in) || b(in)
/**
* Splits a set of lines into (imports, expressions). That is,
* anything on the right of the tuple is a scala expression (definition or setting).
*/
def splitExpressions(lines: Seq[String]): (Seq[(String,Int)], Seq[(String,LineRange)]) =
{
val blank = (_: String).forall(isSpace)

View File

@ -41,11 +41,11 @@ Steps for users:
For example, given plugins Web and Javascript (perhaps provided by plugins added with addSbtPlugin),
<Project>.addPlugins( Web && Javascript )
<Project>.enablePlugins( Web && Javascript )
will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines
<Project>.addPlugins( Web && Javascript ).disablePlugins(MyPlugin)
<Project>.enablePlugins( Web && Javascript ).disablePlugins(MyPlugin)
then the `MyPlugin` settings (and anything that activates only when `MyPlugin` is activated) will not be added.

View File

@ -145,8 +145,8 @@ sealed trait Project extends ProjectDefinition[ProjectReference]
def setSbtFiles(files: File*): Project = copy(auto = AddSettings.append( AddSettings.clearSbtFiles(auto), AddSettings.sbtFiles(files: _*)) )
/** Sets the [[AutoPlugin]]s of this project.
A [[AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to add to a project. */
def addPlugins(ns: Plugins*): Project = setPlugins(ns.foldLeft(plugins)(Plugins.and))
A [[AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to enable on a project. */
def enablePlugins(ns: Plugins*): Project = setPlugins(ns.foldLeft(plugins)(Plugins.and))
/** Disable the given plugins on this project. */
def disablePlugins(ps: AutoPlugin*): Project =

View File

@ -1,17 +1,17 @@
// disablePlugins(Q) will prevent R from being auto-added
lazy val projA = project.addPlugins(A, B).disablePlugins(Q)
lazy val projA = project.enablePlugins(A, B).disablePlugins(Q)
// without B, Q is not added
lazy val projB = project.addPlugins(A)
lazy val projB = project.enablePlugins(A)
// with both A and B, Q is selected, which in turn selects R, but not S
lazy val projC = project.addPlugins(A, B)
lazy val projC = project.enablePlugins(A, B)
// with no natures defined, nothing is auto-added
lazy val projD = project
// with S selected, Q is loaded automatically, which in turn selects R
lazy val projE = project.addPlugins(S)
lazy val projE = project.enablePlugins(S)
check := {
val adel = (del in projA).?.value // should be None

View File

@ -23,5 +23,5 @@ object A extends AutoPlugin {
}
object B extends Build {
lazy val extra = project.addPlugins(bN)
lazy val extra = project.enablePlugins(bN)
}