mirror of https://github.com/sbt/sbt.git
Export build.sbt values inside sbt.
* Expose the values PAST the Eval/sbt.compiler package. * Find projects using the name API rather than finding htem and dropping all values immediately. * Adds a test to make sure the .sbt values are discovered and set-able * Expose .sbt values in Set command and inside BuildUnit methods. * Ensure `consoleProject` can see build.sbt values. * Add notes for where we can look in the build if we want to expose .sbt values between files.
This commit is contained in:
parent
4f3da04515
commit
13fc1114de
|
|
@ -88,7 +88,7 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
|
|||
val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl))
|
||||
new EvalResult(i.extra, value, i.generated, i.enclosingModule)
|
||||
}
|
||||
def evalDefinitions(definitions: Seq[(String, scala.Range)], imports: EvalImports, srcName: String, valTypes: Seq[String]): EvalDefinitions =
|
||||
def evalDefinitions(definitions: Seq[(String, scala.Range)], imports: EvalImports, srcName: String): EvalDefinitions =
|
||||
{
|
||||
require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.")
|
||||
val ev = new EvalType[Seq[String]] {
|
||||
|
|
@ -101,8 +101,7 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
|
|||
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) }
|
||||
atPhase(run.typerPhase.next) { (new ValExtractor()).getVals(unit.body) }
|
||||
}
|
||||
def read(file: File) = IO.readLines(file)
|
||||
def write(value: Seq[String], file: File) = IO.writeLines(file, value)
|
||||
|
|
@ -215,11 +214,18 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
|
|||
}
|
||||
}
|
||||
/** 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] final class ValExtractor() 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 isTopLevelModule(tree.symbol.owner) && types.exists(_ <:< actualTpe.tpe) =>
|
||||
// TODO - We really need to clean this up so that we can filter by type and
|
||||
// track which vals are projects vs. other vals. It's important so that we avoid
|
||||
// instantiating values more than absolutely necessary on different classloaders.
|
||||
// However, it's not terrible right now if we do, as most likely the values
|
||||
// are used to instantiate each other i.e. a valuing in a build.sbt file is most likely
|
||||
// used in something which is contained in a `Project` vaue, therefore it will be
|
||||
// instantiated anyway.
|
||||
case ValDef(_, n, actualTpe, _) if isTopLevelModule(tree.symbol.owner) =>
|
||||
vals ::= nme.localToGetter(n).encoded
|
||||
case _ => super.traverse(tree)
|
||||
}
|
||||
|
|
@ -387,7 +393,7 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
|
|||
private[this] def index(a: Array[Int], i: Int): Int = if (i < 0 || i >= a.length) 0 else a(i)
|
||||
}
|
||||
}
|
||||
private object Eval {
|
||||
private[sbt] object Eval {
|
||||
def optBytes[T](o: Option[T])(f: T => Array[Byte]): Array[Byte] = seqBytes(o.toSeq)(f)
|
||||
def stringSeqBytes(s: Seq[String]): Array[Byte] = seqBytes(s)(bytes)
|
||||
def seqBytes[T](s: Seq[T])(f: T => Array[Byte]): Array[Byte] = bytes(s map f)
|
||||
|
|
|
|||
|
|
@ -50,15 +50,15 @@ final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, Resolv
|
|||
|
||||
/**
|
||||
* The classpath to use when compiling against this build unit's publicly visible code.
|
||||
* It includes build definition and plugin classes, but not classes for .sbt file statements and expressions.
|
||||
* It includes build definition and plugin classes and classes for .sbt file statements and expressions.
|
||||
*/
|
||||
def classpath: Seq[File] = unit.definitions.target ++ unit.plugins.classpath
|
||||
def classpath: Seq[File] = unit.definitions.target ++ unit.plugins.classpath ++ unit.definitions.dslDefinitions.classpath
|
||||
|
||||
/**
|
||||
* The class loader to use for this build unit's publicly visible code.
|
||||
* It includes build definition and plugin classes, but not classes for .sbt file statements and expressions.
|
||||
* It includes build definition and plugin classes and classes for .sbt file statements and expressions.
|
||||
*/
|
||||
def loader = unit.definitions.loader
|
||||
def loader = unit.definitions.dslDefinitions.classloader(unit.definitions.loader)
|
||||
|
||||
/** The imports to use for .sbt files, `consoleProject` and other contexts that use code from the build definition. */
|
||||
def imports = BuildUtil.getImports(unit)
|
||||
|
|
@ -80,7 +80,21 @@ final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, Resolv
|
|||
* and their `settings` and `configurations` updated as appropriate.
|
||||
* @param buildNames No longer used and will be deprecated once feasible.
|
||||
*/
|
||||
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 LoadedDefinitions(
|
||||
val base: File,
|
||||
val target: Seq[File],
|
||||
val loader: ClassLoader,
|
||||
val builds: Seq[Build],
|
||||
val projects: Seq[Project],
|
||||
val buildNames: Seq[String],
|
||||
val dslDefinitions: DefinedSbtValues) {
|
||||
def this(base: File,
|
||||
target: Seq[File],
|
||||
loader: ClassLoader,
|
||||
builds: Seq[Build],
|
||||
projects: Seq[Project],
|
||||
buildNames: Seq[String]) = this(base, target, loader, builds, projects, buildNames, DefinedSbtValues.empty)
|
||||
}
|
||||
|
||||
/** Auto-detected top-level modules (as in `object X`) of type `T` paired with their source names. */
|
||||
final class DetectedModules[T](val modules: Seq[(String, T)]) {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ object BuildUtil {
|
|||
|
||||
def baseImports: Seq[String] = "import sbt._, Keys._, dsl._" :: Nil
|
||||
|
||||
def getImports(unit: BuildUnit): Seq[String] = unit.plugins.detected.imports
|
||||
def getImports(unit: BuildUnit): Seq[String] = unit.plugins.detected.imports ++ unit.definitions.dslDefinitions.imports
|
||||
|
||||
@deprecated("Use getImports(Seq[String]).", "0.13.2")
|
||||
def getImports(pluginNames: Seq[String], buildNames: Seq[String]): Seq[String] = getImports(pluginNames ++ buildNames)
|
||||
|
|
|
|||
|
|
@ -9,14 +9,16 @@ object ConsoleProject {
|
|||
def apply(state: State, extra: String, cleanupCommands: String = "", options: Seq[String] = Nil)(implicit log: Logger) {
|
||||
val extracted = Project extract state
|
||||
val cpImports = new Imports(extracted, state)
|
||||
|
||||
val bindings = ("currentState" -> state) :: ("extracted" -> extracted) :: ("cpHelpers" -> cpImports) :: Nil
|
||||
val unit = extracted.currentUnit
|
||||
val compiler = Compiler.compilers(ClasspathOptions.repl)(state.configuration, log).scalac
|
||||
val imports = BuildUtil.getImports(unit.unit) ++ BuildUtil.importAll(bindings.map(_._1))
|
||||
val importString = imports.mkString("", ";\n", ";\n\n")
|
||||
val initCommands = importString + extra
|
||||
(new Console(compiler))(unit.classpath, options, initCommands, cleanupCommands)(Some(unit.loader), bindings)
|
||||
// TODO - Hook up dsl classpath correctly...
|
||||
(new Console(compiler))(
|
||||
unit.classpath, options, initCommands, cleanupCommands
|
||||
)(Some(unit.loader), bindings)
|
||||
}
|
||||
/** Conveniences for consoleProject that shouldn't normally be used for builds. */
|
||||
final class Imports private[sbt] (extracted: Extracted, state: State) {
|
||||
|
|
|
|||
|
|
@ -99,12 +99,13 @@ object EvaluateConfigurations {
|
|||
// detection for which project project manipulations should be applied.
|
||||
val name = file.getPath
|
||||
val parsed = parseConfiguration(lines, imports, offset)
|
||||
val (importDefs, projects) = if (parsed.definitions.isEmpty) (Nil, (l: ClassLoader) => Nil) else {
|
||||
val definitions = evaluateDefinitions(eval, name, parsed.imports, parsed.definitions)
|
||||
val imp = BuildUtil.importAllRoot(definitions.enclosingModule :: Nil)
|
||||
val projs = (loader: ClassLoader) => definitions.values(loader).map(p => resolveBase(file.getParentFile, p.asInstanceOf[Project]))
|
||||
(imp, projs)
|
||||
}
|
||||
val (importDefs, definitions) =
|
||||
if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty) else {
|
||||
val definitions = evaluateDefinitions(eval, name, parsed.imports, parsed.definitions)
|
||||
val imp = BuildUtil.importAllRoot(definitions.enclosingModule :: Nil)
|
||||
val projs = (loader: ClassLoader) => definitions.values(loader).map(p => resolveBase(file.getParentFile, p.asInstanceOf[Project]))
|
||||
(imp, DefinedSbtValues(definitions))
|
||||
}
|
||||
val allImports = importDefs.map(s => (s, -1)) ++ parsed.imports
|
||||
val dslEntries = parsed.settings map {
|
||||
case (dslExpression, range) =>
|
||||
|
|
@ -112,6 +113,10 @@ object EvaluateConfigurations {
|
|||
}
|
||||
eval.unlinkDeferred()
|
||||
loader => {
|
||||
val projects =
|
||||
definitions.values(loader).collect {
|
||||
case p: Project => resolveBase(file.getParentFile, p)
|
||||
}
|
||||
val (settingsRaw, manipulationsRaw) =
|
||||
dslEntries map (_ apply loader) partition {
|
||||
case internals.ProjectSettings(_) => true
|
||||
|
|
@ -124,9 +129,8 @@ object EvaluateConfigurations {
|
|||
val manipulations = manipulationsRaw map {
|
||||
case internals.ProjectManipulation(f) => f
|
||||
}
|
||||
val ps = projects(loader)
|
||||
// TODO -get project manipulations.
|
||||
new LoadedSbtFile(settings, ps, importDefs, manipulations)
|
||||
new LoadedSbtFile(settings, projects, importDefs, manipulations, definitions)
|
||||
}
|
||||
}
|
||||
/** move a project to be relative to this file after we've evaluated it. */
|
||||
|
|
@ -184,7 +188,7 @@ object EvaluateConfigurations {
|
|||
* @return A method that given an sbt classloader, can return the actual Seq[Setting[_]] defined by
|
||||
* the expression.
|
||||
*/
|
||||
@deprecated("Build DSL now includes non-Setting[_] type settings.", "0.13.6")
|
||||
@deprecated("Build DSL now includes non-Setting[_] type settings.", "0.13.6") // Note: This method is used by the SET command, so we may want to evaluate that sucker a bit.
|
||||
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): ClassLoader => Seq[Setting[_]] =
|
||||
{
|
||||
evaluateDslEntry(eval, name, imports, expression, range) andThen {
|
||||
|
|
@ -231,11 +235,10 @@ 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)]) =
|
||||
private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String, Int)], definitions: Seq[(String, LineRange)]): compiler.EvalDefinitions =
|
||||
{
|
||||
val convertedRanges = definitions.map { case (s, r) => (s, r.start to r.end) }
|
||||
val findTypes = (classOf[Project] :: /*classOf[Build] :: */ Nil).map(_.getName)
|
||||
eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name, findTypes)
|
||||
eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name)
|
||||
}
|
||||
}
|
||||
object Index {
|
||||
|
|
|
|||
|
|
@ -426,9 +426,13 @@ object Load {
|
|||
val defaultProjects = loadProjects(projectsFromBuild(b, normBase), false)
|
||||
(defaultProjects ++ loadedProjectsRaw, b)
|
||||
}
|
||||
|
||||
val defs = if (defsScala.isEmpty) defaultBuildIfNone :: Nil else defsScala
|
||||
val loadedDefs = new sbt.LoadedDefinitions(defDir, Nil, plugs.loader, defs, loadedProjects, plugs.detected.builds.names)
|
||||
// HERE we pull out the defined vals from memoSettings and unify them all so
|
||||
// we can use them later.
|
||||
val valDefinitions = memoSettings.values.foldLeft(DefinedSbtValues.empty) { (prev, sbtFile) =>
|
||||
prev.zip(sbtFile.definitions)
|
||||
}
|
||||
val loadedDefs = new sbt.LoadedDefinitions(defDir, Nil, plugs.loader, defs, loadedProjects, plugs.detected.builds.names, valDefinitions)
|
||||
new sbt.BuildUnit(uri, normBase, loadedDefs, plugs)
|
||||
}
|
||||
|
||||
|
|
@ -479,6 +483,9 @@ object Load {
|
|||
* @param context The plugin management context for autogenerated IDs.
|
||||
*
|
||||
* @return The completely resolved/updated sequence of projects defined, with all settings expanded.
|
||||
*
|
||||
* TODO - We want to attach the known (at this time) vals/lazy vals defined in each project's
|
||||
* build.sbt to that project so we can later use this for the `set` command.
|
||||
*/
|
||||
private[this] def loadTransitive(
|
||||
newProjects: Seq[Project],
|
||||
|
|
@ -518,12 +525,11 @@ object Load {
|
|||
// Continue loading if we find any more.
|
||||
newProjects match {
|
||||
case Seq(next, rest @ _*) =>
|
||||
log.debug(s"[Loading] Loading project ${next.id} @ ${next.base}")
|
||||
log.debug(s"[Loading] Loading project ${next.id} @ ${next.base}")
|
||||
val (finished, discovered) = discoverAndLoad(next)
|
||||
loadTransitive(rest ++ discovered, buildBase, plugins, eval, injectSettings, acc :+ finished, memoSettings, log, false, buildUri, context)
|
||||
case Nil if makeOrDiscoverRoot =>
|
||||
log.debug(s"[Loading] Scanning directory ${buildBase}")
|
||||
// TODO - Here we want to fully discover everything and make a default build...
|
||||
discover(AddSettings.defaultSbtFiles, buildBase) match {
|
||||
case DiscoveredProjects(Some(root), discovered, files) =>
|
||||
log.debug(s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}")
|
||||
|
|
@ -651,6 +657,8 @@ object Load {
|
|||
// Classloader of the build
|
||||
val loader = loadedPlugins.loader
|
||||
// How to load an individual file for use later.
|
||||
// TODO - We should import vals defined in other sbt files here, if we wish to
|
||||
// share. For now, build.sbt files have their own unique namespace.
|
||||
def loadSettingsFile(src: File): LoadedSbtFile =
|
||||
EvaluateConfigurations.evaluateSbtFile(eval(), src, IO.readLines(src), loadedPlugins.detected.imports, 0)(loader)
|
||||
// How to merge SbtFiles we read into one thing
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package sbt
|
||||
|
||||
import Def.Setting
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Represents the exported contents of a .sbt file. Currently, that includes the list of settings,
|
||||
|
|
@ -10,15 +11,69 @@ private[sbt] final class LoadedSbtFile(
|
|||
val settings: Seq[Setting[_]],
|
||||
val projects: Seq[Project],
|
||||
val importedDefs: Seq[String],
|
||||
val manipulations: Seq[Project => Project]) {
|
||||
val manipulations: Seq[Project => Project],
|
||||
// TODO - we may want to expose a simpler interface on top of here for the set command,
|
||||
// rather than what we have now...
|
||||
val definitions: DefinedSbtValues) {
|
||||
@deprecated("LoadedSbtFiles are no longer directly merged.", "0.13.6")
|
||||
def merge(o: LoadedSbtFile): LoadedSbtFile =
|
||||
new LoadedSbtFile(settings ++ o.settings, projects ++ o.projects, importedDefs ++ o.importedDefs, manipulations)
|
||||
new LoadedSbtFile(
|
||||
settings ++ o.settings,
|
||||
projects ++ o.projects,
|
||||
importedDefs ++ o.importedDefs,
|
||||
manipulations,
|
||||
definitions zip o.definitions)
|
||||
|
||||
def clearProjects = new LoadedSbtFile(settings, Nil, importedDefs, manipulations)
|
||||
def clearProjects = new LoadedSbtFile(settings, Nil, importedDefs, manipulations, definitions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the `val`/`lazy val` definitions defined within a build.sbt file
|
||||
* which we can reference in other settings.
|
||||
*/
|
||||
private[sbt] final class DefinedSbtValues(val sbtFiles: Seq[compiler.EvalDefinitions]) {
|
||||
|
||||
def values(parent: ClassLoader): Seq[Any] =
|
||||
sbtFiles flatMap (_ values parent)
|
||||
|
||||
def classloader(parent: ClassLoader): ClassLoader =
|
||||
sbtFiles.foldLeft(parent) { (cl, e) => e.loader(cl) }
|
||||
|
||||
def imports: Seq[String] = {
|
||||
// TODO - Sanity check duplicates and such, so users get a nice warning rather
|
||||
// than explosion.
|
||||
for {
|
||||
file <- sbtFiles
|
||||
m = file.enclosingModule
|
||||
v <- file.valNames
|
||||
} yield s"import ${m}.${v}"
|
||||
}
|
||||
def generated: Seq[File] =
|
||||
sbtFiles flatMap (_.generated)
|
||||
|
||||
// Returns a classpath for the generated .sbt files.
|
||||
def classpath: Seq[File] =
|
||||
generated.map(_.getParentFile).distinct
|
||||
|
||||
/**
|
||||
* Joins the defines of this build.sbt with another.
|
||||
* TODO - we may want to figure out scoping rules, as this could lead to
|
||||
* ambiguities.
|
||||
*/
|
||||
def zip(other: DefinedSbtValues): DefinedSbtValues =
|
||||
new DefinedSbtValues(sbtFiles ++ other.sbtFiles)
|
||||
}
|
||||
private[sbt] object DefinedSbtValues {
|
||||
/** Construct a DefinedSbtValues object directly from the underlying representation. */
|
||||
def apply(eval: compiler.EvalDefinitions): DefinedSbtValues =
|
||||
new DefinedSbtValues(Seq(eval))
|
||||
/** Construct an empty value object. */
|
||||
def empty = new DefinedSbtValues(Nil)
|
||||
|
||||
}
|
||||
|
||||
private[sbt] object LoadedSbtFile {
|
||||
/** Represents an empty .sbt file: no Projects, imports, or settings.*/
|
||||
def empty = new LoadedSbtFile(Nil, Nil, Nil, Nil)
|
||||
def empty = new LoadedSbtFile(Nil, Nil, Nil, Nil, DefinedSbtValues.empty)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -234,7 +234,19 @@ object BuiltinCommands {
|
|||
case (s, (all, arg)) =>
|
||||
val extracted = Project extract s
|
||||
import extracted._
|
||||
val settings = EvaluateConfigurations.evaluateSetting(session.currentEval(), "<set>", imports(extracted), arg, LineRange(0, 0))(currentLoader)
|
||||
val dslVals = extracted.currentUnit.unit.definitions.dslDefinitions
|
||||
// TODO - This is horribly inefficient. We should try to only attach the
|
||||
// classloader + imports NEEDED to compile the set command.
|
||||
System.err.println(s"DSL imports: ${dslVals.imports mkString "\n"}")
|
||||
val ims = (imports(extracted) ++ dslVals.imports.map(i => (i, -1)))
|
||||
val cl = dslVals.classloader(currentLoader)
|
||||
val settings = EvaluateConfigurations.evaluateSetting(
|
||||
session.currentEval(),
|
||||
"<set>",
|
||||
ims,
|
||||
arg,
|
||||
LineRange(0, 0)
|
||||
)(cl)
|
||||
val setResult = if (all) SettingCompletions.setAll(extracted, settings) else SettingCompletions.setThis(s, extracted, settings, arg)
|
||||
s.log.info(setResult.quietSummary)
|
||||
s.log.debug(setResult.verboseSummary)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
TaskKey[Unit]("checkName", "") := {
|
||||
assert(name.value == "hello-world", "Name is not hello-worled, failed to set!")
|
||||
}
|
||||
|
||||
val notExistingThing = settingKey[Int]("Something new")
|
||||
|
||||
TaskKey[Unit]("checkBuildSbtDefined", "") := {
|
||||
assert(notExistingThing.?.value == Some(5), "Failed to set a settingKey defined in build.sbt")
|
||||
}
|
||||
|
||||
commands ++= Seq(
|
||||
Command.command("helloWorldTest") { state: State =>
|
||||
"""set name := "hello-world"""" ::
|
||||
"checkName" ::
|
||||
state
|
||||
},
|
||||
Command.command("buildSbtTest") { state: State =>
|
||||
"""set notExistingThing := 5""" ::
|
||||
"checkBuildSbtDefined" ::
|
||||
state
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
> helloWorldTest
|
||||
> buildSbtTest
|
||||
Loading…
Reference in New Issue