From 13fc1114deece6df5248b989ca9fe228da8084de Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 16 Jul 2014 20:58:33 -0400 Subject: [PATCH 1/3] 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. --- .../src/main/scala/sbt/compiler/Eval.scala | 18 ++++-- main/src/main/scala/sbt/BuildStructure.scala | 24 +++++-- main/src/main/scala/sbt/BuildUtil.scala | 2 +- main/src/main/scala/sbt/ConsoleProject.scala | 6 +- .../scala/sbt/EvaluateConfigurations.scala | 27 ++++---- main/src/main/scala/sbt/Load.scala | 16 +++-- main/src/main/scala/sbt/LoadedSbtFile.scala | 63 +++++++++++++++++-- main/src/main/scala/sbt/Main.scala | 14 ++++- sbt/src/sbt-test/actions/set/build.sbt | 23 +++++++ sbt/src/sbt-test/actions/set/test | 2 + 10 files changed, 160 insertions(+), 35 deletions(-) create mode 100644 sbt/src/sbt-test/actions/set/build.sbt create mode 100644 sbt/src/sbt-test/actions/set/test diff --git a/main/actions/src/main/scala/sbt/compiler/Eval.scala b/main/actions/src/main/scala/sbt/compiler/Eval.scala index b92207551..a940e5fd9 100644 --- a/main/actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main/actions/src/main/scala/sbt/compiler/Eval.scala @@ -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) diff --git a/main/src/main/scala/sbt/BuildStructure.scala b/main/src/main/scala/sbt/BuildStructure.scala index 221d227dd..9e82d2584 100644 --- a/main/src/main/scala/sbt/BuildStructure.scala +++ b/main/src/main/scala/sbt/BuildStructure.scala @@ -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)]) { diff --git a/main/src/main/scala/sbt/BuildUtil.scala b/main/src/main/scala/sbt/BuildUtil.scala index 8f94e1d57..58ecd14ca 100644 --- a/main/src/main/scala/sbt/BuildUtil.scala +++ b/main/src/main/scala/sbt/BuildUtil.scala @@ -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) diff --git a/main/src/main/scala/sbt/ConsoleProject.scala b/main/src/main/scala/sbt/ConsoleProject.scala index bdb844263..446169a33 100644 --- a/main/src/main/scala/sbt/ConsoleProject.scala +++ b/main/src/main/scala/sbt/ConsoleProject.scala @@ -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) { diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index dfc49c281..f586a76eb 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -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 { diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index a3dc684d0..922b51d76 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -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 diff --git a/main/src/main/scala/sbt/LoadedSbtFile.scala b/main/src/main/scala/sbt/LoadedSbtFile.scala index 238bcdcb9..ef8a5d694 100644 --- a/main/src/main/scala/sbt/LoadedSbtFile.scala +++ b/main/src/main/scala/sbt/LoadedSbtFile.scala @@ -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) } diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index cd21f80b2..67ee0d41c 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -234,7 +234,19 @@ object BuiltinCommands { case (s, (all, arg)) => val extracted = Project extract s import extracted._ - val settings = EvaluateConfigurations.evaluateSetting(session.currentEval(), "", 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(), + "", + 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) diff --git a/sbt/src/sbt-test/actions/set/build.sbt b/sbt/src/sbt-test/actions/set/build.sbt new file mode 100644 index 000000000..641163ffb --- /dev/null +++ b/sbt/src/sbt-test/actions/set/build.sbt @@ -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 + } +) \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/set/test b/sbt/src/sbt-test/actions/set/test new file mode 100644 index 000000000..8867956d9 --- /dev/null +++ b/sbt/src/sbt-test/actions/set/test @@ -0,0 +1,2 @@ +> helloWorldTest +> buildSbtTest \ No newline at end of file From 31c9de8efd176c646b36f2d98b09f3f99fbda6fe Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 17 Jul 2014 19:54:52 -0400 Subject: [PATCH 2/3] Fix the val extractor to only pull sbt types rather than all. --- main/actions/src/main/scala/sbt/compiler/Eval.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/main/actions/src/main/scala/sbt/compiler/Eval.scala b/main/actions/src/main/scala/sbt/compiler/Eval.scala index a940e5fd9..1267a0038 100644 --- a/main/actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main/actions/src/main/scala/sbt/compiler/Eval.scala @@ -217,6 +217,11 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se private[this] final class ValExtractor() extends Traverser { private[this] var vals = List[String]() def getVals(t: Tree): List[String] = { vals = Nil; traverse(t); vals } + def isAcceptableType(tpe: Type): Boolean = { + tpe.baseClasses.exists { sym => + sym.fullName startsWith "sbt." + } + } override def traverse(tree: Tree): Unit = tree match { // 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 @@ -225,7 +230,8 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se // 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) => + // For now, we just check that the type + case ValDef(_, n, actualTpe, _) if isTopLevelModule(tree.symbol.owner) && isAcceptableType(actualTpe.tpe) => vals ::= nme.localToGetter(n).encoded case _ => super.traverse(tree) } From 50696398a100ba878b25e337af22fc3b059790de Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 17 Jul 2014 23:32:29 -0400 Subject: [PATCH 3/3] Minor cleanups and fix Eval tests to work correctly. --- .../src/main/scala/sbt/compiler/Eval.scala | 16 ++++------------ .../main/scala/sbt/EvaluateConfigurations.scala | 4 +++- main/src/main/scala/sbt/Main.scala | 6 +++--- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/main/actions/src/main/scala/sbt/compiler/Eval.scala b/main/actions/src/main/scala/sbt/compiler/Eval.scala index 1267a0038..e533ac5a2 100644 --- a/main/actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main/actions/src/main/scala/sbt/compiler/Eval.scala @@ -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): EvalDefinitions = + def evalDefinitions(definitions: Seq[(String, scala.Range)], imports: EvalImports, srcName: String, valTypes: Seq[String]): EvalDefinitions = { require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.") val ev = new EvalType[Seq[String]] { @@ -101,7 +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) = { - atPhase(run.typerPhase.next) { (new ValExtractor()).getVals(unit.body) } + atPhase(run.typerPhase.next) { (new ValExtractor(valTypes.toSet)).getVals(unit.body) } } def read(file: File) = IO.readLines(file) def write(value: Seq[String], file: File) = IO.writeLines(file, value) @@ -214,23 +214,15 @@ 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() extends Traverser { + private[this] final class ValExtractor(tpes: Set[String]) extends Traverser { private[this] var vals = List[String]() def getVals(t: Tree): List[String] = { vals = Nil; traverse(t); vals } def isAcceptableType(tpe: Type): Boolean = { tpe.baseClasses.exists { sym => - sym.fullName startsWith "sbt." + tpes.contains(sym.fullName) } } override def traverse(tree: Tree): Unit = tree match { - // 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. - // For now, we just check that the type case ValDef(_, n, actualTpe, _) if isTopLevelModule(tree.symbol.owner) && isAcceptableType(actualTpe.tpe) => vals ::= nme.localToGetter(n).encoded case _ => super.traverse(tree) diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index f586a76eb..a035fbd50 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -235,10 +235,12 @@ object EvaluateConfigurations { val trimmed = line.trim DefinitionKeywords.exists(trimmed startsWith _) } + private[this] def extractedValTypes: Seq[String] = + Seq(classOf[Project], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]).map(_.getName) 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) } - eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name) + eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name, extractedValTypes) } } object Index { diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 67ee0d41c..d7d364472 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -235,9 +235,9 @@ object BuiltinCommands { val extracted = Project extract s import extracted._ 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"}") + // TODO - This is possibly inefficient (or stupid). We should try to only attach the + // classloader + imports NEEDED to compile the set command, rather than + // just ALL of them. val ims = (imports(extracted) ++ dslVals.imports.map(i => (i, -1))) val cl = dslVals.classloader(currentLoader) val settings = EvaluateConfigurations.evaluateSetting(