Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

support defining Projects in .sbt files

vals of type Project are added to the Build
  • Loading branch information...
commit d4fd136192e6bfd622477c9ce5ebd3d10f102744 1 parent 9a79af6
@harrah authored
Showing with 300 additions and 148 deletions.
  1. +0 −2  .gitignore
  2. +102 −44 compile/src/main/scala/sbt/compiler/Eval.scala
  3. +22 −1 compile/src/test/scala/EvalTest.scala
  4. +1 −0  main/src/main/scala/sbt/Build.scala
  5. +0 −2  main/src/main/scala/sbt/BuildPaths.scala
  6. +1 −1  main/src/main/scala/sbt/BuildStructure.scala
  7. +6 −5 main/src/main/scala/sbt/BuildUtil.scala
  8. +27 −13 main/src/main/scala/sbt/EvaluateConfigurations.scala
  9. +50 −72 main/src/main/scala/sbt/Load.scala
  10. +17 −0 main/src/main/scala/sbt/LoadedSbtFile.scala
  11. +1 −1  main/src/main/scala/sbt/Script.scala
  12. +3 −3 sbt/src/sbt-test/project/Class.forName/test
  13. 0  sbt/src/sbt-test/project/plugins/project/{plugins → }/p.sbt
  14. +1 −0  sbt/src/sbt-test/project/sbt-file-projects/a/A.scala
  15. +4 −0 sbt/src/sbt-test/project/sbt-file-projects/a/a.sbt
  16. +4 −0 sbt/src/sbt-test/project/sbt-file-projects/b/build.sbt
  17. +9 −0 sbt/src/sbt-test/project/sbt-file-projects/build.sbt
  18. +6 −0 sbt/src/sbt-test/project/sbt-file-projects/changes/Basic.scala
  19. +7 −0 sbt/src/sbt-test/project/sbt-file-projects/changes/Restricted.scala
  20. +1 −0  sbt/src/sbt-test/project/sbt-file-projects/other.sbt
  21. +29 −0 sbt/src/sbt-test/project/sbt-file-projects/test
  22. +2 −3 sbt/src/sbt-test/project/src-plugins/plugin/build.sbt
  23. +1 −1  sbt/src/sbt-test/project/src-plugins/project/plugins/project/P.scala
  24. +6 −0 sbt/src/sbt-test/project/src-plugins/project/project/P.scala
View
2  .gitignore
@@ -1,4 +1,2 @@
target/
-project/boot/
-.release.sbt
__pycache__
View
146 compile/src/main/scala/sbt/compiler/Eval.scala
@@ -12,11 +12,33 @@ import Tokens.{EOF, NEWLINE, NEWLINES, SEMI}
import java.io.File
import java.nio.ByteBuffer
import java.net.URLClassLoader
+import Eval.{getModule, getValue, WrapValName}
// TODO: provide a way to cleanup backing directory
final class EvalImports(val strings: Seq[(String,Int)], val srcName: String)
+
+/** The result of evaluating a Scala expression. The inferred type of the expression is given by `tpe`.
+* The value may be obtained from `getValue` by providing a parent class loader that provides the classes from the classpath
+* this expression was compiled against. Each call to `getValue` constructs a new class loader and loads
+* the module from that class loader. `generated` contains the compiled classes and cache files related
+* to the expression. The name of the auto-generated module wrapping the expression is `enclosingModule`. */
final class EvalResult(val tpe: String, val getValue: ClassLoader => Any, val generated: Seq[File], val enclosingModule: String)
+
+/** The result of evaluating a group of Scala definitions. The definitions are wrapped in an auto-generated,
+* top-level module named `enclosingModule`. `generated` contains the compiled classes and cache files related to the definitions.
+* A new class loader containing the module may be obtained from `loader` by passing the parent class loader providing the classes
+* from the classpath that the definitions were compiled against. The list of vals with the requested types is `valNames`.
+* The values for these may be obtained by providing the parent class loader to `values` as is done with `loader`.*/
+final class EvalDefinitions(val loader: ClassLoader => ClassLoader, val generated: Seq[File], val enclosingModule: String, val valNames: Seq[String])
+{
+ def values(parent: ClassLoader): Seq[Any] = {
+ val module = getModule(enclosingModule, loader(parent))
+ for(n <- valNames) yield
+ module.getClass.getMethod(n).invoke(module)
+ }
+}
+
final class EvalException(msg: String) extends RuntimeException(msg)
// not thread safe, since it reuses a Global instance
final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Settings => Reporter, backing: Option[File])
@@ -46,39 +68,49 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
private[this] var toUnlinkLater = List[Symbol]()
private[this] def unlink(sym: Symbol) = sym.owner.info.decls.unlink(sym)
-
+
def eval(expression: String, imports: EvalImports = noImports, tpeName: Option[String] = None, srcName: String = "<setting>", line: Int = DefaultStartLine): EvalResult =
{
- val ev = new EvalType {
+ val ev = new EvalType[String] {
def makeUnit = mkUnit(srcName, line, expression)
def unlink = true
- def load(moduleName: String, loader: ClassLoader): Any = getValue[Any](moduleName, loader)
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
val (parser, tree) = parse(unit, settingErrorStrings, _.expr())
val tpt: Tree = expectedType(tpeName)
augment(parser, importTrees, tree, tpt, moduleName)
}
+ def extra(run: Run, unit: CompilationUnit) = atPhase(run.typerPhase.next) { (new TypeExtractor).getType(unit.body) }
+ def read(file: File) = IO.read(file)
+ def write(value: String, f: File) = IO.write(f, value)
}
- evalCommon(expression :: Nil, imports, tpeName, ev)
+ val i = evalCommon(expression :: Nil, imports, tpeName, ev)
+ val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl))
+ new EvalResult(i.extra, value, i.generated, i.enclosingModule)
}
- private[sbt] def evalDefinitions(definitions: Seq[(String,Range)], imports: EvalImports, srcName: String): EvalResult =
+ def evalDefinitions(definitions: Seq[(String,Range)], imports: EvalImports, srcName: String, valTypes: Seq[String]): EvalDefinitions =
{
require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.")
- val ev = new EvalType {
+ val ev = new EvalType[Seq[String]] {
lazy val (fullUnit, defUnits) = mkDefsUnit(srcName, definitions)
def makeUnit = fullUnit
def unlink = false
- def load(moduleName: String, loader: ClassLoader): Any = getModule(moduleName, loader)
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
val fullParser = new syntaxAnalyzer.UnitParser(unit)
val trees = defUnits flatMap parseDefinitions
syntheticModule(fullParser, importTrees, trees.toList, moduleName)
}
+ def extra(run: Run, unit: CompilationUnit) = {
+ val tpes = valTypes.map(tpe => rootMirror.getRequiredClass(tpe).tpe)
+ atPhase(run.typerPhase.next) { (new ValExtractor(tpes)).getVals(unit.body) }
+ }
+ def read(file: File) = IO.readLines(file)
+ def write(value: Seq[String], file: File) = IO.writeLines(file, value)
}
- evalCommon(definitions.map(_._1), imports, Some(""), ev)
+ val i = evalCommon(definitions.map(_._1), imports, Some(""), ev)
+ new EvalDefinitions(i.loader, i.generated, i.enclosingModule, i.extra)
}
- private[this] def evalCommon(content: Seq[String], imports: EvalImports, tpeName: Option[String], ev: EvalType): EvalResult =
+ private[this] def evalCommon[T](content: Seq[String], imports: EvalImports, tpeName: Option[String], ev: EvalType[T]): EvalIntermediate[T] =
{
import Eval._
val hash = Hash.toHex(Hash(bytes( stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) ::
@@ -94,21 +126,22 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
}
def unlinkAll(): Unit = for( (sym, _) <- run.symSource ) if(ev.unlink) unlink(sym) else toUnlinkLater ::= sym
- val (tpe, value) =
- (tpeName, backing) match {
- case (Some(tpe), Some(back)) if classExists(back, moduleName) =>
- val loader = (parent: ClassLoader) => ev.load(moduleName, new URLClassLoader(Array(back.toURI.toURL), parent))
- (tpe, loader)
- case _ =>
- try { compileAndLoad(run, unit, imports, backing, moduleName, ev) }
- finally { unlinkAll() }
- }
+ val (extra, loader) = backing match {
+ case Some(back) if classExists(back, moduleName) =>
+ val loader = (parent: ClassLoader) => new URLClassLoader(Array(back.toURI.toURL), parent)
+ val extra = ev.read(cacheFile(back,moduleName))
+ (extra, loader)
+ case _ =>
+ try { compileAndLoad(run, unit, imports, backing, moduleName, ev) }
+ finally { unlinkAll() }
+ }
val classFiles = getClassFiles(backing, moduleName)
- new EvalResult(tpe, value, classFiles, moduleName)
+ new EvalIntermediate(extra, loader, classFiles, moduleName)
}
-
- private[this] def compileAndLoad(run: Run, unit: CompilationUnit, imports: EvalImports, backing: Option[File], moduleName: String, ev: EvalType): (String, ClassLoader => Any) =
+ // location of the cached type or definition information
+ private[this] def cacheFile(base: File, moduleName: String): File = new File(base, moduleName + ".cache")
+ private[this] def compileAndLoad[T](run: Run, unit: CompilationUnit, imports: EvalImports, backing: Option[File], moduleName: String, ev: EvalType[T]): (T, ClassLoader => ClassLoader) =
{
val dir = outputDirectory(backing)
settings.outputDirs setSingleOutput dir
@@ -130,10 +163,11 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
compile(run.namerPhase)
checkError("Type error in expression")
- val tpe = atPhase(run.typerPhase.next) { (new TypeExtractor).getType(unit.body) }
- val loader = (parent: ClassLoader) => ev.load(moduleName, new AbstractFileClassLoader(dir, parent))
- (tpe, loader)
+ val extra = ev.extra(run, unit)
+ for(f <- backing) ev.write(extra, cacheFile(f, moduleName))
+ val loader = (parent: ClassLoader) => new AbstractFileClassLoader(dir, parent)
+ (extra, loader)
}
private[this] def expectedType(tpeName: Option[String]): Tree =
@@ -148,7 +182,6 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
def load(dir: AbstractFile, moduleName: String): ClassLoader => Any = parent => getValue[Any](moduleName, new AbstractFileClassLoader(dir, parent))
def loadPlain(dir: File, moduleName: String): ClassLoader => Any = parent => getValue[Any](moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent))
- val WrapValName = "$sbtdef"
//wrap tree in object objectName { def WrapValName = <tree> }
def augment(parser: global.syntaxAnalyzer.UnitParser, imports: Seq[Tree], tree: Tree, tpt: Tree, objectName: String): Tree =
{
@@ -173,20 +206,7 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
parser.makePackaging(0, emptyPkg, (imports :+ moduleDef).toList)
}
- def getValue[T](objectName: String, loader: ClassLoader): T =
- {
- val module = getModule(objectName, loader)
- val accessor = module.getClass.getMethod(WrapValName)
- val value = accessor.invoke(module)
- value.asInstanceOf[T]
- }
- private[this] def getModule(moduleName: String, loader: ClassLoader): Any =
- {
- val clazz = Class.forName(moduleName + "$", true, loader)
- clazz.getField("MODULE$").get(null)
- }
-
- final class TypeExtractor extends Traverser {
+ private[this] final class TypeExtractor extends Traverser {
private[this] var result = ""
def getType(t: Tree) = { result = ""; traverse(t); result }
override def traverse(tree: Tree): Unit = tree match {
@@ -194,6 +214,18 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
case _ => super.traverse(tree)
}
}
+ /** Tree traverser that obtains the names of vals in a top-level module whose type is a subtype of one of `types`.*/
+ private[this] final class ValExtractor(types: Seq[Type]) extends Traverser {
+ private[this] var vals = List[String]()
+ def getVals(t: Tree): List[String] = { vals = Nil; traverse(t); vals }
+ override def traverse(tree: Tree): Unit = tree match {
+ case ValDef(_, n, actualTpe, _) if tree.symbol.owner.isTopLevelModule && types.exists(_ <:< actualTpe.tpe) =>
+ vals ::= nme.localToGetter(n).encoded
+ case _ => super.traverse(tree)
+ }
+ }
+ private[this] final class EvalIntermediate[T](val extra: T, val loader: ClassLoader => ClassLoader, val generated: Seq[File], val enclosingModule: String)
+
private[this] def classExists(dir: File, name: String) = (new File(dir, name + ".class")).exists
// TODO: use the code from Analyzer
private[this] def getClassFiles(backing: Option[File], moduleName: String): Seq[File] =
@@ -276,7 +308,17 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
defs
}
- private[this] trait EvalType {
+ private[this] trait EvalType[T]
+ {
+ /** Extracts additional information after the compilation unit is evaluated.*/
+ def extra(run: Run, unit: CompilationUnit): T
+
+ /** Deserializes the extra information for unchanged inputs from a cache file.*/
+ def read(file: File): T
+
+ /** Serializes the extra information to a cache file, where it can be `read` back if inputs haven't changed.*/
+ def write(value: T, file: File): Unit
+
/** Constructs the full compilation unit for this evaluation.
* This is used for error reporting during compilation.
* The `unitBody` method actually does the parsing and may parse the Tree from another source. */
@@ -284,10 +326,6 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
/** If true, all top-level symbols from this evaluation will be unlinked.*/
def unlink: Boolean
-
- /** Gets the value for this evaluation.
- * The enclosing `moduleName` and the `parent` class loader containing classes on the classpath are provided. */
- def load(moduleName: String, parent: ClassLoader): Any
/** Constructs the Tree to be compiled. The full compilation `unit` from `makeUnit` is provided along with the
* parsed imports `importTrees` to be used. `moduleName` should be name of the enclosing module.
@@ -367,5 +405,25 @@ private object Eval
buffer.array
}
+ /** The name of the synthetic val in the synthetic module that an expression is assigned to. */
+ final val WrapValName = "$sbtdef"
+
+ /** Gets the value of the expression wrapped in module `objectName`, which is accessible via `loader`.
+ * The module name should not include the trailing `$`. */
+ def getValue[T](objectName: String, loader: ClassLoader): T =
+ {
+ val module = getModule(objectName, loader)
+ val accessor = module.getClass.getMethod(WrapValName)
+ val value = accessor.invoke(module)
+ value.asInstanceOf[T]
+ }
+
+ /** Gets the top-level module `moduleName` from the provided class `loader`. The module name should not include the trailing `$`.*/
+ def getModule(moduleName: String, loader: ClassLoader): Any =
+ {
+ val clazz = Class.forName(moduleName + "$", true, loader)
+ clazz.getField("MODULE$").get(null)
+ }
+
private val classDirFilter: FileFilter = DirectoryFilter || GlobFilter("*.class")
}
View
23 compile/src/test/scala/EvalTest.scala
@@ -43,6 +43,27 @@ object EvalTest extends Properties("eval")
}
}
+ val ValTestNames = Set("x", "a")
+ val ValTestContent = """
+val x: Int = {
+ val y: Int = 4
+ y
+}
+val z: Double = 3.0
+val a = 9
+val p = {
+ object B { val i = 3 }
+ class C { val j = 4 }
+ "asdf"
+}
+"""
+
+ property("val test") = secure {
+ val defs = (ValTestContent, 1 to 7) :: Nil
+ val res = eval.evalDefinitions(defs, new EvalImports(Nil, ""), "<defs>", "scala.Int" :: Nil)
+ label("Val names", res.valNames) |: (res.valNames.toSet == ValTestNames)
+ }
+
property("explicit import") = forAll(testImport("import math.abs" :: Nil))
property("wildcard import") = forAll(testImport("import math._" :: Nil))
@@ -53,7 +74,7 @@ object EvalTest extends Properties("eval")
value(eval.eval("abs("+i+")", new EvalImports(imports.zipWithIndex, "imp"))) == math.abs(i)
private[this] def local(i: Int) = "{ class ETest(val i: Int); new ETest(" + i + ") }"
- val LocalType = "java.lang.Object with ScalaObject{val i: Int}"
+ val LocalType = "Object{val i: Int}"
private[this] def value(r: EvalResult) = r.getValue(getClass.getClassLoader)
private[this] def hasErrors(line: Int, src: String) =
View
1  main/src/main/scala/sbt/Build.scala
@@ -36,6 +36,7 @@ trait Plugin
object Build
{
val default: Build = new Build { override def projectDefinitions(base: File) = defaultProject(base) :: Nil }
+
def defaultID(base: File): String = "default-" + Hash.trimHashString(base.getAbsolutePath, 6)
def defaultProject(base: File): Project = Project(defaultID(base), base).settings(
// if the user has overridden the name, use the normal organization that is derived from the name.
View
2  main/src/main/scala/sbt/BuildPaths.scala
@@ -39,13 +39,11 @@ object BuildPaths
private[this] def defaultStaging(globalBase: File) = globalBase / "staging"
private[this] def defaultGlobalPlugins(globalBase: File) = globalBase / PluginsDirectoryName
- def definitionSources(base: File): Seq[File] = (base * "*.scala").get
def configurationSources(base: File): Seq[File] = (base * (GlobFilter("*.sbt") - ".sbt")).get
def pluginDirectory(definitionBase: File) = definitionBase / PluginsDirectoryName
def evalOutputDirectory(base: File) = outputDirectory(base) / "config-classes"
def outputDirectory(base: File) = base / DefaultTargetName
- def buildOutputDirectory(base: File, scalaInstance: xsbti.compile.ScalaInstance) = crossPath(outputDirectory(base), scalaInstance)
def projectStandard(base: File) = base / "project"
def projectHidden(base: File) = base / ConfigDirectoryName
View
2  main/src/main/scala/sbt/BuildStructure.scala
@@ -43,7 +43,7 @@ final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, Resolv
override def toString = unit.toString
}
-final class LoadedDefinitions(val base: File, val target: Seq[File], val loader: ClassLoader, val builds: Seq[Build], val buildNames: Seq[String])
+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])
{
def fullClasspath: Seq[Attributed[File]] = pluginData.classpath
View
11 main/src/main/scala/sbt/BuildUtil.scala
@@ -59,11 +59,12 @@ object BuildUtil
deps(proj)(_.aggregate)
}
}
- def baseImports = "import sbt._, Process._, Keys._" :: Nil
- def getImports(unit: BuildUnit) = baseImports ++ importAllRoot(unit.plugins.pluginNames ++ unit.definitions.buildNames)
- def importAll(values: Seq[String]) = if(values.isEmpty) Nil else values.map( _ + "._" ).mkString("import ", ", ", "") :: Nil
- def importAllRoot(values: Seq[String]) = importAll(values map rootedName)
- def rootedName(s: String) = if(s contains '.') "_root_." + s else s
+ def baseImports: Seq[String] = "import sbt._, Process._, 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 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
def aggregationRelation(units: Map[URI, LoadedBuildUnit]): Relation[ProjectRef, ProjectRef] =
{
View
40 main/src/main/scala/sbt/EvaluateConfigurations.scala
@@ -14,14 +14,17 @@ package sbt
object EvaluateConfigurations
{
private[this] final class ParsedFile(val imports: Seq[(String,Int)], val definitions: Seq[(String,LineRange)], val settings: Seq[(String,LineRange)])
- private[this] final class Definitions(val loader: ClassLoader => ClassLoader, val moduleNames: Seq[String])
private[this] val DefinitionKeywords = Seq("lazy val ", "def ", "val ")
- def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): ClassLoader => Seq[Setting[_]] =
- flatten(srcs.sortBy(_.getName) map { src => evaluateConfiguration(eval, src, imports) })
+ 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) }
+ }
+
def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): ClassLoader => Seq[Setting[_]] =
- evaluateConfiguration(eval, src.getPath, IO.readLines(src), imports, 0)
+ evaluateConfiguration(eval, src, IO.readLines(src), imports, 0)
private[this] def parseConfiguration(lines: Seq[String], builtinImports: Seq[String], offset: Int): ParsedFile =
{
@@ -31,20 +34,31 @@ object EvaluateConfigurations
new ParsedFile(allImports, definitions, settings)
}
- def evaluateConfiguration(eval: Eval, name: String, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => Seq[Setting[_]] =
+ 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
+ }
+
+ private[this] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => LoadedSbtFile =
{
+ val name = file.getPath
val parsed = parseConfiguration(lines, imports, offset)
- val importDefs = if(parsed.definitions.isEmpty) Nil else {
+ val (importDefs, projects) = if(parsed.definitions.isEmpty) (Nil, (l: ClassLoader) => Nil) else {
val definitions = evaluateDefinitions(eval, name, parsed.imports, parsed.definitions)
- Load.importAllRoot(definitions.moduleNames).map(s => (s, -1))
+ val imp = Load.importAllRoot(definitions.enclosingModule :: Nil)
+ val projs = (loader: ClassLoader) => definitions.values(loader).map(p => resolveBase(file.getParentFile, p.asInstanceOf[Project]))
+ (imp, projs)
}
- val allImports = importDefs ++ parsed.imports
+ val allImports = importDefs.map(s => (s, -1)) ++ parsed.imports
val settings = parsed.settings map { case (settingExpression,range) =>
evaluateSetting(eval, name, allImports, settingExpression, range)
}
eval.unlinkDeferred()
- flatten(settings)
+ val loadSettings = flatten(settings)
+ loader => new LoadedSbtFile(loadSettings(loader), projects(loader), importDefs)
}
+ 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)
def addOffset(offset: Int, lines: Seq[(String,Int)]): Seq[(String,Int)] =
@@ -53,7 +67,7 @@ object EvaluateConfigurations
ranges.map { case (s, r) => (s, r shift offset) }
val SettingsDefinitionName = {
- val _ = classOf[SettingsDefinition] // this line exists to try to provide a compile-time error when the following line needs to be changed
+ 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"
}
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, range: LineRange): ClassLoader => Seq[Setting[_]] =
@@ -104,11 +118,11 @@ object EvaluateConfigurations
val trimmed = line.trim
DefinitionKeywords.exists(trimmed startsWith _)
}
- private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String,Int)], definitions: Seq[(String,LineRange)]): Definitions =
+ private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String,Int)], definitions: Seq[(String,LineRange)]) =
{
val convertedRanges = definitions.map { case (s, r) => (s, r.start to r.end) }
- val res = eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name)
- new Definitions(loader => res.getValue(loader).getClass.getClassLoader, res.enclosingModule :: Nil)
+ val findTypes = (classOf[Project] :: /*classOf[Build] :: */ Nil).map(_.getName)
+ eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name, findTypes)
}
}
object Index
View
122 main/src/main/scala/sbt/Load.scala
@@ -76,7 +76,7 @@ object Load
{
val eval = mkEval(data(config.globalPluginClasspath), base, defaultEvalOptions)
val imports = baseImports ++ importAllRoot(config.globalPluginNames)
- EvaluateConfigurations(eval, files, imports)
+ loader => EvaluateConfigurations(eval, files, imports)(loader).settings
}
def loadGlobal(state: State, base: File, global: File, config: LoadBuildConfiguration): LoadBuildConfiguration =
if(base != global && global.exists)
@@ -124,7 +124,7 @@ object Load
val loaded = resolveProjects(load(rootBase, s, config))
val projects = loaded.units
lazy val rootEval = lazyEval(loaded.units(loaded.root).unit)
- val settings = finalTransforms(buildConfigurations(loaded, getRootProject(projects), rootEval, config.injectSettings))
+ val settings = finalTransforms(buildConfigurations(loaded, getRootProject(projects), config.injectSettings))
val delegates = config.delegates(loaded)
val data = makeSettings(settings, delegates, config.scopeLocal)( Project.showLoadingKey( loaded ) )
val index = structureIndex(data, settings, loaded.extra(data))
@@ -187,33 +187,20 @@ object Load
}
def isProjectThis(s: Setting[_]) = s.key.scope.project match { case This | Select(ThisProject) => true; case _ => false }
- def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, rootEval: () => Eval, injectSettings: InjectSettings): Seq[Setting[_]] =
+ def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, injectSettings: InjectSettings): Seq[Setting[_]] =
{
((loadedBuild in GlobalScope :== loaded) +:
transformProjectOnly(loaded.root, rootProject, injectSettings.global)) ++
inScope(GlobalScope)( pluginGlobalSettings(loaded) ) ++
loaded.units.toSeq.flatMap { case (uri, build) =>
- val eval = if(uri == loaded.root) rootEval else lazyEval(build.unit)
val plugins = build.unit.plugins.plugins
val pluginBuildSettings = plugins.flatMap(_.buildSettings)
val pluginNotThis = plugins.flatMap(_.settings) filterNot isProjectThis
val projectSettings = build.defined flatMap { case (id, project) =>
- val loader = build.unit.definitions.loader
- lazy val defaultSbtFiles = configurationSources(project.base)
-
- import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence}
- def expand(auto: AddSettings): Seq[Setting[_]] = auto match {
- case User => injectSettings.projectLoaded(loader)
- case sf: SbtFiles => configurations( sf.files.map(f => IO.resolve(project.base, f)), eval, build.imports )(loader)
- case sf: DefaultSbtFiles => configurations( defaultSbtFiles.filter(sf.include), eval, build.imports )(loader)
- case f: Plugins => plugins.filter(f.include).flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings)
- case q: Sequence => q.sequence.flatMap(expand)
- }
-
val ref = ProjectRef(uri, id)
val defineConfig: Seq[Setting[_]] = for(c <- project.configurations) yield ( (configuration in (ref, ConfigKey(c.name))) :== c)
val builtin: Seq[Setting[_]] = (thisProject :== project) +: (thisProjectRef :== ref) +: defineConfig
- val settings = builtin ++ project.settings ++ expand(project.auto) ++ injectSettings.project
+ val settings = builtin ++ project.settings ++ injectSettings.project
// map This to thisScope, Select(p) to mapRef(uri, rootProject, p)
transformSettings(projectScope(ref), uri, rootProject, settings)
}
@@ -249,8 +236,8 @@ object Load
def mkEval(classpath: Seq[File], base: File, options: Seq[String]): Eval =
new Eval(options, classpath, s => new ConsoleReporter(s), Some(evalOutputDirectory(base)))
- def configurations(srcs: Seq[File], eval: () => Eval, imports: Seq[String]): ClassLoader => Seq[Setting[_]] =
- if(srcs.isEmpty) const(Nil) else EvaluateConfigurations(eval(), srcs, imports)
+ def configurations(srcs: Seq[File], eval: () => Eval, imports: Seq[String]): ClassLoader => LoadedSbtFile =
+ if(srcs.isEmpty) const(LoadedSbtFile.empty) else EvaluateConfigurations(eval(), srcs, imports)
def load(file: File, s: State, config: LoadBuildConfiguration): PartBuild =
load(file, builtinLoader(s, config.copy(pluginManagement = config.pluginManagement.shift, extraBuilds = Nil)), config.extraBuilds.toList )
@@ -387,8 +374,8 @@ object Load
// we don't have the complete build graph loaded, so we don't have the rootProject function yet.
// Therefore, we use resolveProjectBuild instead of resolveProjectRef. After all builds are loaded, we can fully resolve ProjectReferences.
val resolveBuild = (_: Project).resolveBuild(ref => Scope.resolveProjectBuild(unit.uri, ref))
- val resolve = resolveBuild compose resolveBase(unit.localBase)
- unit.definitions.builds.flatMap(_.projectDefinitions(unit.localBase) map resolve)
+ // although the default loader will resolve the project base directory, other loaders may not, so run resolveBase here as well
+ unit.definitions.projects.map(resolveBuild compose resolveBase(unit.localBase))
}
def getRootProject(map: Map[URI, BuildUnitBase]): URI => String =
uri => getBuild(map, uri).rootProjects.headOption getOrElse emptyBuild(uri)
@@ -411,43 +398,52 @@ object Load
{
val normBase = localBase.getCanonicalFile
val defDir = selectProjectDir(normBase, config.log)
- val pluginDir = pluginDirectory(defDir)
- val oldStyleExists = pluginDir.isDirectory
- val newStyleExists = configurationSources(defDir).nonEmpty || projectStandard(defDir).exists
- val (plugs, defs) =
- if(newStyleExists || !oldStyleExists)
- {
- if(oldStyleExists)
- config.log.warn("Detected both new and deprecated style of plugin configuration.\n Ignoring deprecated project/plugins/ directory (" + pluginDir + ").")
- loadUnitNew(defDir, s, config)
- }
- else
- loadUnitOld(defDir, pluginDir, s, config)
- new BuildUnit(uri, normBase, defs, plugs)
- }
- def loadUnitNew(defDir: File, s: State, config: LoadBuildConfiguration): (LoadedPlugins, LoadedDefinitions) =
- {
val plugs = plugins(defDir, s, config)
val defNames = analyzed(plugs.fullClasspath) flatMap findDefinitions
val defs = if(defNames.isEmpty) Build.default :: Nil else loadDefinitions(plugs.loader, defNames)
- val loadedDefs = new LoadedDefinitions(defDir, Nil, plugs.loader, defs, defNames)
- (plugs, loadedDefs)
- }
- def loadUnitOld(defDir: File, pluginDir: File, s: State, config: LoadBuildConfiguration): (LoadedPlugins, LoadedDefinitions) =
- {
- config.log.warn("Using project/plugins/ (" + pluginDir + ") for plugin configuration is deprecated.\n" +
- "Put .sbt plugin definitions directly in project/,\n .scala plugin definitions in project/project/,\n and remove the project/plugins/ directory.")
- val plugs = plugins(pluginDir, s, config)
- val defs = definitionSources(defDir)
- val target = buildOutputDirectory(defDir, config.compilers.scalac.scalaInstance)
- IO.createDirectory(target)
- val loadedDefs =
- if(defs.isEmpty)
- new LoadedDefinitions(defDir, target :: Nil, plugs.loader, Build.default :: Nil, Nil)
- else
- definitions(defDir, target, defs, plugs, config.definesClass, config.compilers, config.log)
- (plugs, loadedDefs)
+ val imports = BuildUtil.getImports(plugs.pluginNames, defNames)
+
+ lazy val eval = mkEval(plugs.classpath, defDir, Nil)
+ val initialProjects = defs.flatMap(_.projectDefinitions(normBase).map(resolveBase(normBase)))
+
+ val loadedProjects = loadTransitive(initialProjects, imports, plugs, () => eval, config.injectSettings, Nil)
+ val loadedDefs = new LoadedDefinitions(defDir, Nil, plugs.loader, defs, loadedProjects, defNames)
+ new BuildUnit(uri, normBase, loadedDefs, plugs)
+ }
+
+ private[this] def loadTransitive(newProjects: Seq[Project], imports: Seq[String], plugins: LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, acc: Seq[Project]): Seq[Project] =
+ {
+ val loaded = newProjects map { project =>
+ val loadedSbtFiles = loadSettings(project.auto, project.base, imports, plugins, eval, injectSettings)
+ val transformed = project.copy(settings = (project.settings: Seq[Setting[_]]) ++ loadedSbtFiles.settings)
+ (transformed, loadedSbtFiles.projects)
+ }
+ val (transformed, np) = loaded.unzip
+ val nextProjects = np.flatten
+ val loadedProjects = transformed ++ acc
+
+ if(nextProjects.isEmpty)
+ loadedProjects
+ else
+ loadTransitive(nextProjects, imports, plugins, eval, injectSettings, loadedProjects)
+ }
+
+ private[this] def loadSettings(auto: AddSettings, projectBase: File, buildImports: Seq[String], loadedPlugins: LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings): LoadedSbtFile =
+ {
+ lazy val defaultSbtFiles = configurationSources(projectBase)
+ def settings(ss: Seq[Setting[_]]) = new LoadedSbtFile(ss, Nil, Nil)
+ val loader = loadedPlugins.loader
+
+ import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence}
+ def expand(auto: AddSettings): LoadedSbtFile = auto match {
+ case User => settings(injectSettings.projectLoaded(loader))
+ case sf: SbtFiles => configurations( sf.files.map(f => IO.resolve(projectBase, f)), eval, buildImports )(loader)
+ case sf: DefaultSbtFiles => configurations( defaultSbtFiles.filter(sf.include), eval, buildImports )(loader)
+ case f: Plugins => settings(loadedPlugins.plugins.filter(f.include).flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings))
+ case q: Sequence => (LoadedSbtFile.empty /: q.sequence) { (b,add) => b.merge( expand(add) ) }
+ }
+ expand(auto)
}
def globalPluginClasspath(globalPlugin: Option[GlobalPlugin]): Seq[Attributed[File]] =
@@ -533,29 +529,11 @@ object Load
config.evalPluginDef(pluginDef, pluginState)
}
- def definitions(base: File, targetBase: File, srcs: Seq[File], plugins: LoadedPlugins, definesClass: DefinesClass, compilers: Compilers, log: Logger): LoadedDefinitions =
- {
- val (inputs, defAnalysis) = build(plugins.fullClasspath, srcs, targetBase, compilers, definesClass, log)
- val target = inputs.config.classesDirectory
- val definitionLoader = ClasspathUtilities.toLoader(target :: Nil, plugins.loader)
- val defNames = findDefinitions(defAnalysis)
- val defs = if(defNames.isEmpty) Build.default :: Nil else loadDefinitions(definitionLoader, defNames)
- new LoadedDefinitions(base, target :: Nil, definitionLoader, defs, defNames)
- }
-
def loadDefinitions(loader: ClassLoader, defs: Seq[String]): Seq[Build] =
defs map { definition => loadDefinition(loader, definition) }
def loadDefinition(loader: ClassLoader, definition: String): Build =
ModuleUtilities.getObject(definition, loader).asInstanceOf[Build]
- def build(classpath: Seq[Attributed[File]], sources: Seq[File], target: File, compilers: Compilers, definesClass: DefinesClass, log: Logger): (Inputs, inc.Analysis) =
- {
- // TODO: make used of classpath metadata for recompilation
- val inputs = Compiler.inputs(data(classpath), sources, target, Nil, Nil, definesClass, Compiler.DefaultMaxErrors, CompileOrder.Mixed)(compilers, log)
- val analysis = Compiler(inputs, log)
- (inputs, analysis)
- }
-
def loadPlugins(dir: File, data: PluginData, loader: ClassLoader): LoadedPlugins =
{
val (pluginNames, plugins) = if(data.classpath.isEmpty) (Nil, Nil) else {
View
17 main/src/main/scala/sbt/LoadedSbtFile.scala
@@ -0,0 +1,17 @@
+package sbt
+
+ import Def.Setting
+
+/** Represents the exported contents of a .sbt file. Currently, that includes the list of settings,
+* the values of Project vals, and the import statements for all defined vals/defs. */
+private[sbt] final class LoadedSbtFile(val settings: Seq[Setting[_]], val projects: Seq[Project], val importedDefs: Seq[String])
+{
+ def merge(o: LoadedSbtFile): LoadedSbtFile =
+ new LoadedSbtFile(settings ++ o.settings, projects ++ o.projects, importedDefs ++ o.importedDefs)
+}
+private[sbt] object LoadedSbtFile
+{
+ /** Represents an empty .sbt file: no Projects, imports, or settings.*/
+ def empty = new LoadedSbtFile(Nil, Nil, Nil)
+}
+
View
2  main/src/main/scala/sbt/Script.scala
@@ -25,7 +25,7 @@ object Script
import extracted._
val embeddedSettings = blocks(script).flatMap { block =>
- evaluate(eval(), script.getPath, block.lines, currentUnit.imports, block.offset+1)(currentLoader)
+ evaluate(eval(), script, block.lines, currentUnit.imports, block.offset+1)(currentLoader)
}
val scriptAsSource = sources in Compile := script :: Nil
val asScript = scalacOptions ++= Seq("-Xscript", script.getName.stripSuffix(".scala"))
View
6 sbt/src/sbt-test/project/Class.forName/test
@@ -2,13 +2,13 @@
> package
$ delete build.sbt
-$ copy-file target/definition-lib-forname-test-1.0.jar project/plugins/lib/test.jar
+$ copy-file target/definition-lib-forname-test-1.0.jar project/lib/test.jar
$ copy-file changes/build2.sbt build.sbt
# the copied project definition depends on the Test module in test.jar and will
-# fail to compile if sbt did not put the jars in project/plugins/lib/ on the compile classpath
+# fail to compile if sbt did not put the jars in project/lib/ on the compile classpath
> reload
# The project definition uses the class in test.jar and will fail here if sbt did not put the
-# jars in project/plugins/lib on the runtime classpath
+# jars in project/lib on the runtime classpath
> use-jar
View
0  ...rc/sbt-test/project/plugins/project/plugins/p.sbt → sbt/src/sbt-test/project/plugins/project/p.sbt
File renamed without changes
View
1  sbt/src/sbt-test/project/sbt-file-projects/a/A.scala
@@ -0,0 +1 @@
+object A
View
4 sbt/src/sbt-test/project/sbt-file-projects/a/a.sbt
@@ -0,0 +1,4 @@
+
+val aa = taskKey[Unit]("A task in the 'a' project")
+
+aa := println("Hello.")
View
4 sbt/src/sbt-test/project/sbt-file-projects/b/build.sbt
@@ -0,0 +1,4 @@
+
+val h = taskKey[Unit]("A task in project 'b'")
+
+h := println("Hello.")
View
9 sbt/src/sbt-test/project/sbt-file-projects/build.sbt
@@ -0,0 +1,9 @@
+val a = "a"
+val f = file("a")
+val g = taskKey[Unit]("A task in the root project")
+
+val p = Project(a, f).autoSettings(AddSettings.sbtFiles( file("a.sbt") ))
+
+val b = Project("b", file("b"))
+
+g := println("Hello.")
View
6 sbt/src/sbt-test/project/sbt-file-projects/changes/Basic.scala
@@ -0,0 +1,6 @@
+import sbt._
+import Keys._
+
+object B extends Build {
+ lazy val root = Project("root", file("."))
+}
View
7 sbt/src/sbt-test/project/sbt-file-projects/changes/Restricted.scala
@@ -0,0 +1,7 @@
+import sbt._
+import Keys._
+
+object B extends Build {
+ lazy val root = Project("root", file(".")).autoSettings(
+ AddSettings.sbtFiles( file("other.sbt") )) // ignore build.sbt
+}
View
1  sbt/src/sbt-test/project/sbt-file-projects/other.sbt
@@ -0,0 +1 @@
+val c = Project("c", file("c"))
View
29 sbt/src/sbt-test/project/sbt-file-projects/test
@@ -0,0 +1,29 @@
+# nothing in project/
+> g
+-> root/compile
+> a/compile
+> a/aa
+> b/compile
+> b/h
+> c/compile
+
+$ copy-file changes/Basic.scala project/Build.scala
+> reload
+> g
+> root/compile
+> a/compile
+> a/aa
+> b/compile
+> b/h
+> c/compile
+
+$ copy-file changes/Restricted.scala project/Build.scala
+> reload
+> root/compile
+-> g
+-> h
+-> a/compile
+-> a/aa
+-> b/compile
+-> b/h
+> c/compile
View
5 sbt/src/sbt-test/project/src-plugins/plugin/build.sbt
@@ -1,3 +1,2 @@
-libraryDependencies <<= (libraryDependencies, appConfiguration) { (deps, conf) =>
- deps :+ ("org.scala-sbt" % "sbt" % conf.provider.id.version)
-}
+libraryDependencies +=
+ "org.scala-sbt" % "sbt" % appConfiguration.value.provider.id.version
View
2  sbt/src/sbt-test/project/src-plugins/project/plugins/project/P.scala
@@ -2,5 +2,5 @@ import sbt._
object B extends Build
{
- lazy val root = Project("root", file(".")).dependsOn( file("../../plugin") )
+ lazy val root = Project("root", file(".")).dependsOn( file("../plugin") )
}
View
6 sbt/src/sbt-test/project/src-plugins/project/project/P.scala
@@ -0,0 +1,6 @@
+import sbt._
+
+object B extends Build
+{
+ lazy val root = Project("root", file(".")).dependsOn( file("../plugin") )
+}
Please sign in to comment.
Something went wrong with that request. Please try again.