mirror of https://github.com/sbt/sbt.git
support defining Projects in .sbt files
vals of type Project are added to the Build
This commit is contained in:
parent
9a79af69c9
commit
d4fd136192
|
|
@ -1,4 +1,2 @@
|
|||
target/
|
||||
project/boot/
|
||||
.release.sbt
|
||||
__pycache__
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) =
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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] =
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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)
|
||||
}
|
||||
def loadUnitOld(defDir: File, pluginDir: File, s: State, config: LoadBuildConfiguration): (LoadedPlugins, LoadedDefinitions) =
|
||||
|
||||
private[this] def loadTransitive(newProjects: Seq[Project], imports: Seq[String], plugins: LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, acc: Seq[Project]): Seq[Project] =
|
||||
{
|
||||
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 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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
object A
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
val aa = taskKey[Unit]("A task in the 'a' project")
|
||||
|
||||
aa := println("Hello.")
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
val h = taskKey[Unit]("A task in project 'b'")
|
||||
|
||||
h := println("Hello.")
|
||||
|
|
@ -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.")
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import sbt._
|
||||
import Keys._
|
||||
|
||||
object B extends Build {
|
||||
lazy val root = Project("root", file("."))
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
val c = Project("c", file("c"))
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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") )
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import sbt._
|
||||
|
||||
object B extends Build
|
||||
{
|
||||
lazy val root = Project("root", file(".")).dependsOn( file("../plugin") )
|
||||
}
|
||||
Loading…
Reference in New Issue