get,set,eval commands

This commit is contained in:
Mark Harrah 2011-01-25 07:24:52 -05:00
parent 837bb80d40
commit f1af2c1cbc
6 changed files with 133 additions and 36 deletions

View File

@ -182,7 +182,7 @@ object Load
import BuildPaths._
import BuildStreams._
def defaultLoad(state: State, log: Logger): BuildStructure =
def defaultLoad(state: State, log: Logger): (() => Eval, BuildStructure) =
{
val stagingDirectory = defaultStaging.getCanonicalFile // TODO: properly configurable
val base = state.configuration.baseDirectory.getCanonicalFile
@ -218,56 +218,66 @@ object Load
// 6) Load all configurations using build definitions and plugins (their classpaths and loaded instances).
// 7) Combine settings from projects, plugins, and configurations
// 8) Evaluate settings
def apply(rootBase: File, config: LoadBuildConfiguration): BuildStructure =
def apply(rootBase: File, config: LoadBuildConfiguration): (() => Eval, BuildStructure) =
{
val loaded = load(rootBase, config)
val projects = loaded.units
val settings = buildConfigurations(loaded, getRootProject(projects), config.injectSettings)
val data = Project.make(settings)(config.delegates(loaded))
lazy val rootEval = lazyEval(loaded.units(loaded.root).unit)
val settings = buildConfigurations(loaded, getRootProject(projects), config.injectSettings, rootEval)
val delegates = config.delegates(loaded)
val data = Project.make(settings)(delegates)
val index = structureIndex(data)
val streams = mkStreams(projects, loaded.root, data)
new BuildStructure(projects, loaded.root, settings, data, index, streams)
(rootEval, new BuildStructure(projects, loaded.root, settings, data, index, streams, delegates))
}
def structureIndex(settings: Settings[Scope]): StructureIndex =
new StructureIndex(Index.stringToKeyMap(settings), Index.taskToKeyMap(settings))
// Reevaluates settings after modifying them. Does not recompile or reload any build components.
def reapply(modifySettings: Endo[Seq[Setting[_]]], structure: BuildStructure, delegates: Scope => Seq[Scope]): BuildStructure =
def reapply(newSettings: Seq[Setting[_]], structure: BuildStructure): BuildStructure =
{
val newSettings = modifySettings(structure.settings)
val newData = Project.make(newSettings)(delegates)
val newData = Project.make(newSettings)(structure.delegates)
val newIndex = structureIndex(newData)
val newStreams = mkStreams(structure.units, structure.root, newData)
new BuildStructure(units = structure.units, root = structure.root, settings = newSettings, data = newData, index = newIndex, streams = newStreams)
new BuildStructure(units = structure.units, root = structure.root, settings = newSettings, data = newData, index = newIndex, streams = newStreams, delegates = structure.delegates)
}
def isProjectThis(s: Setting[_]) = s.key.scope.project == This
def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, injectSettings: Seq[Setting[_]]): Seq[Setting[_]] =
def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, injectSettings: Seq[Setting[_]], rootEval: () => Eval): Seq[Setting[_]] =
loaded.units.toSeq flatMap { case (uri, build) =>
val eval = mkEval(build.unit.definitions, build.unit.plugins, Nil)
val eval = if(uri == loaded.root) rootEval else lazyEval(build.unit)
val pluginSettings = build.unit.plugins.plugins
val (pluginThisProject, pluginGlobal) = pluginSettings partition isProjectThis
val projectSettings = build.defined flatMap { case (id, project) =>
val srcs = configurationSources(project.base)
val settings = injectSettings ++ project.settings ++ pluginThisProject ++ configurations(srcs, eval)
val ext = ProjectRef(uri, id)
val thisScope = Scope(Select(ext), Global, Global, Global)
// map This to thisScope, Select(p) to mapRef(uri, rootProject, p)
Project.transform(Scope.resolveScope(thisScope, uri, rootProject), settings)
transformSettings(projectScope(uri, id), uri, rootProject, settings)
}
pluginGlobal ++ projectSettings
}
def transformSettings(thisScope: Scope, uri: URI, rootProject: URI => String, settings: Seq[Setting[_]]): Seq[Setting[_]] =
Project.transform(Scope.resolveScope(thisScope, uri, rootProject), settings)
def projectScope(uri: URI, id: String): Scope = projectScope(ProjectRef(uri, id))
def projectScope(project: ProjectRef): Scope = Scope(Select(project), Global, Global, Global)
def lazyEval(unit: BuildUnit): () => Eval =
{
lazy val eval = mkEval(unit)
() => eval
}
def mkEval(unit: BuildUnit): Eval = mkEval(unit.definitions, unit.plugins, Nil)
def mkEval(defs: LoadedDefinitions, plugs: LoadedPlugins, options: Seq[String]): Eval =
{
val classpathString = Path.makeString(defs.target +: plugs.classpath)
val optionsCp = "-cp" +: classpathString +: options // TODO: probably need to properly set up options with CompilerArguments
new Eval(optionsCp, s => new ConsoleReporter(s), defs.loader)
}
def configurations(srcs: Seq[File], eval: Eval): Seq[Setting[_]] =
if(srcs.isEmpty) Nil else EvaluateConfigurations(eval, srcs)
def configurations(srcs: Seq[File], eval: () => Eval): Seq[Setting[_]] =
if(srcs.isEmpty) Nil else EvaluateConfigurations(eval(), srcs)
def load(file: File, config: LoadBuildConfiguration): LoadedBuild = load(file, uri => loadUnit(uri, RetrieveUnit(config.stagingDirectory, uri), config) )
def load(file: File, loader: URI => BuildUnit): LoadedBuild = loadURI(IO.directoryURI(file), loader)
@ -386,7 +396,7 @@ object Load
def noPlugins(config: LoadBuildConfiguration): LoadedPlugins = new LoadedPlugins(config.classpath, config.loader, Nil)
def buildPlugins(dir: File, config: LoadBuildConfiguration): LoadedPlugins =
{
val pluginDef = apply(dir, config)
val (_,pluginDef) = apply(dir, config)
val (pluginClasspath, pluginAnalysis) = config.evalPluginDef(pluginDef)
val pluginLoader = ClasspathUtilities.toLoader(pluginClasspath, config.loader)
loadPlugins(pluginClasspath, pluginLoader, pluginAnalysis)
@ -431,8 +441,8 @@ object Load
discovery(Test.allDefs(analysis)).collect { case (definition, Discovered(_,_,_,true)) => definition.name }
}
def initialSession(structure: BuildStructure): SessionSettings =
new SessionSettings(structure.root, rootProjectMap(structure.units), structure.settings, Map.empty, Map.empty)
def initialSession(structure: BuildStructure, rootEval: () => Eval): SessionSettings =
new SessionSettings(structure.root, rootProjectMap(structure.units), structure.settings, Map.empty, Map.empty, rootEval)
def rootProjectMap(units: Map[URI, LoadedBuildUnit]): Map[URI, String] =
{
@ -466,7 +476,7 @@ object Load
def referenced(definition: Project): Seq[ProjectRef] = definition.inherits ++ definition.aggregate ++ definition.dependencies.map(_.project)
final class BuildStructure(val units: Map[URI, LoadedBuildUnit], val root: URI, val settings: Seq[Setting[_]], val data: Settings[Scope], val index: StructureIndex, val streams: Streams)
final class BuildStructure(val units: Map[URI, LoadedBuildUnit], val root: URI, val settings: Seq[Setting[_]], val data: Settings[Scope], val index: StructureIndex, val streams: Streams, val delegates: Scope => Seq[Scope])
final class LoadBuildConfiguration(val stagingDirectory: File, val classpath: Seq[File], val loader: ClassLoader, val compilers: Compilers, val evalPluginDef: BuildStructure => (Seq[File], Analysis), val delegates: LoadedBuild => Scope => Seq[Scope], val injectSettings: Seq[Setting[_]], val log: Logger)
// information that is not original, but can be reconstructed from the rest of BuildStructure
final class StructureIndex(val keyMap: Map[String, AttributeKey[_]], val taskToKey: Map[Task[_], ScopedKey[Task[_]]])

View File

@ -32,6 +32,7 @@ object Command
val HistoryPath = SettingKey[Option[File]]("history")
val Analysis = AttributeKey[inc.Analysis]("analysis")
val Watch = SettingKey[Watched]("continuous-watch")
val Sample = SettingKey[String]("sample-setting")
def command(name: String)(f: State => State): Command = command(name, Nil)(f)
def command(name: String, briefHelp: String, detail: String)(f: State => State): Command = command(name, Help(name, (name, briefHelp), detail) :: Nil)(f)

View File

@ -34,6 +34,37 @@ object CommandSupport
val Exit = "exit"
val Quit = "quit"
val EvalCommand = "eval"
val evalBrief = (EvalCommand + " <expression>", "Evaluates the given Scala expression and prints the result and type.")
val evalDetailed =
EvalCommand + """ <expression>
Evaluates the given Scala expression and prints the result and type.
"""
val GetCommand = "get"
val getBrief = (GetCommand + " <setting-key>", "Prints the value for the given key.")
val getDetailed =
GetCommand + """ <setting-key>
Displays the value bound to the key argument using its toString method.
<setting-key> is interpreted as a Scala expression of type sbt.ScopedSetting[_].
"""
val SetCommand = "set"
val setBrief = (SetCommand + " <setting-expression>", "Evaluates the given Setting and applies to the current project.")
val setDetailed =
SetCommand + """ <setting-expression>
Applies the given setting to the current project:
1) Constructs the expression provided as an argument by compiling and loading it.
2) Appends the new setting to the current project's settings.
3) Re-evaluates the build's settings.
This command does not rebuild the build definitions, plugins, or configurations.
It does not automatically persist the setting.
This is done by running 'save-settings'.
"""
/** The command name to terminate the program.*/
val TerminateAction: String = Exit

View File

@ -59,7 +59,7 @@ class xMain extends xsbti.AppMain
object Commands
{
def DefaultCommands: Seq[Command] = Seq(ignore, help, reload, read, history, continuous, exit, loadCommands, loadProject, compile, discover,
projects, project, setOnFailure, ifLast, multi, shell, alias, append, nop)
projects, project, setOnFailure, ifLast, multi, shell, set, get, eval, alias, append, nop)
def nop = Command.custom(s => success(() => s), Nil)
def ignore = Command.command(FailureWall)(identity)
@ -212,6 +212,35 @@ object Commands
}
}*/
def eval = Command.single(EvalCommand, evalBrief, evalDetailed) { (s, arg) =>
val log = logger(s)
val extracted = Project extract s
import extracted._
val (tpe, value) = session.currentEval().eval(arg, None)
log.info("ans: " + tpe + " = " + value.toString)
s
}
def set = Command.single(SetCommand, setBrief, setDetailed) { (s, arg) =>
val extracted = Project extract s
import extracted._
val setting = EvaluateConfigurations.evaluateSetting(session.currentEval(), "", Nil, arg)
val append = Load.transformSettings(Load.projectScope(curi, cid), curi, rootProject, setting :: Nil)
val newSession = session.appendSettings( append map (a => (a, arg)))
logger(s).info("Reapplying settings...")
val newStructure = Load.reapply(newSession.mergeSettings, structure)
setProject(newSession, newStructure, s)
}
def get = Command.single(GetCommand, getBrief, getDetailed) { (s, arg) =>
val extracted = Project extract s
import extracted._
val scoped = session.currentEval().eval(arg, Some("sbt.ScopedSetting[_]"))._2.asInstanceOf[ScopedSetting[_]]
val resolve = Scope.resolveScope(Load.projectScope(curi, cid), curi, rootProject)
(structure.data.get(resolve(scoped.scope), scoped.key)) match {
case None => logger(s).error("No entry for key."); s.fail
case Some(v) => logger(s).info(v.toString); s
}
}
def listBuild(uri: URI, build: Load.LoadedBuildUnit, current: Boolean, currentID: String, log: Logger) =
{
log.info("In " + uri)
@ -221,10 +250,9 @@ object Commands
def act = error("TODO")
def projects = Command.command(ProjectsCommand, projectsBrief, projectsDetailed ) { s =>
val extracted = Project extract s
import extracted._
val log = logger(s)
val session = Project.session(s)
val structure = Project.structure(s)
val (curi, cid) = session.current
listBuild(curi, structure.units(curi), true, cid, log)
for( (uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, log)
s
@ -260,8 +288,12 @@ object Commands
}
def loadProject = Command.command(LoadProject, LoadProjectBrief, LoadProjectDetailed) { s =>
val structure = Load.defaultLoad(s, logger(s))
val session = Load.initialSession(structure)
val (eval, structure) = Load.defaultLoad(s, logger(s))
val session = Load.initialSession(structure, eval)
setProject(session, structure, s)
}
def setProject(session: SessionSettings, structure: Load.BuildStructure, s: State): State =
{
val newAttrs = s.attributes.put(StructureKey, structure).put(SessionKey, session)
val newState = s.copy(attributes = newAttrs)
Project.updateCurrent(runExitHooks(newState))

View File

@ -6,14 +6,17 @@ package sbt
import java.io.File
import java.net.URI
import Project._
import Types.Endo
import Command.{HistoryPath,Watch}
import CommandSupport.logger
import compile.Eval
final case class Project(id: String, base: File, aggregate: Seq[ProjectRef] = Nil, dependencies: Seq[Project.ClasspathDependency] = Nil, inherits: Seq[ProjectRef] = Nil,
settings: Seq[Project.Setting[_]] = Project.defaultSettings, configurations: Seq[Configuration] = Configurations.default)
{
def uses = aggregate ++ dependencies.map(_.project)
}
final case class Extracted(structure: Load.BuildStructure, session: SessionSettings, curi: URI, cid: String, rootProject: URI => String)
object Project extends Init[Scope]
{
@ -27,13 +30,15 @@ object Project extends Init[Scope]
def getOrError[T](state: State, key: AttributeKey[T], msg: String): T = state get key getOrElse error(msg)
def structure(state: State): Load.BuildStructure = getOrError(state, StructureKey, "No build loaded.")
def session(state: State): SessionSettings = getOrError(state, SessionKey, "Session not initialized.")
def current(state: State): (URI, String) =
def extract(state: State): Extracted =
{
val s = session(state)
val uri = s.currentBuild
(uri, s.currentProject(uri))
val se = session(state)
val (curi, cid) = se.current
val st = structure(state)
Extracted(st, se, curi, cid, Load.getRootProject(st.units))
}
def current(state: State): (URI, String) = session(state).current
def currentRef(state: State): ProjectRef =
{
val (unit, it) = current(state)
@ -80,12 +85,25 @@ object Project extends Init[Scope]
import SessionSettings._
final class SessionSettings(val currentBuild: URI, val currentProject: Map[URI, String], val original: Seq[Setting[_]], val prepend: SessionMap, val append: SessionMap) {
final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, String], original: Seq[Setting[_]], prepend: SessionMap, append: SessionMap, currentEval: () => Eval)
{
assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.")
def setCurrent(build: URI, project: String): SessionSettings = new SessionSettings(build, currentProject.updated(build, project), original, prepend, append)
def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings = copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval)
def current: (URI, String) = (currentBuild, currentProject(currentBuild))
def appendSettings(s: Seq[SessionSetting]): SessionSettings = copy(append = modify(append, _ ++ s))
def prependSettings(s: Seq[SessionSetting]): SessionSettings = copy(prepend = modify(prepend, s ++ _))
def mergeSettings: Seq[Setting[_]] = merge(prepend) ++ original ++ merge(append)
private[this] def merge(map: SessionMap): Seq[Setting[_]] = map.values.toSeq.flatten[SessionSetting].map(_._1)
private[this] def modify(map: SessionMap, onSeq: Endo[Seq[SessionSetting]]): SessionMap =
{
val cur = current
map.updated(cur, onSeq(map.getOrElse( cur, Nil)))
}
}
object SessionSettings {
object SessionSettings
{
type SessionSetting = (Setting[_], String)
type SessionMap = Map[(URI, String), Seq[SessionSetting]]
}

View File

@ -29,7 +29,12 @@ final class ProjectNavigation(s: State)
val (uri, pid) = session.current
val projects = Load.getBuild(structure.units, uri).defined.keys
def setProject(uri: URI, id: String) = updateCurrent(s.put(SessionKey, session.setCurrent(uri, id)))
def setProject(nuri: URI, nid: String) =
{
val neval = if(uri == nuri) session.currentEval else mkEval()
updateCurrent(s.put(SessionKey, session.setCurrent(nuri, nid, neval)))
}
def mkEval() = Load.lazyEval(structure.units(uri).unit)
def getRoot(uri: URI) = Load.getRootProject(structure.units)(uri)
def apply(action: Navigate): State =