From 12d72facb11e6c6be7ff0c5a03fed9b6700982bd Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 6 Apr 2012 20:28:31 -0400 Subject: [PATCH] enhance 'projects' to allow temporarily adding/removing builds to the session --- main/CommandStrings.scala | 16 ++++++++++++++-- main/Load.scala | 18 ++++++++++-------- main/Main.scala | 33 +++++++++++++++++++++++++++++---- main/Project.scala | 10 +++++++++- main/command/State.scala | 2 +- util/complete/Parsers.scala | 3 ++- 6 files changed, 65 insertions(+), 17 deletions(-) diff --git a/main/CommandStrings.scala b/main/CommandStrings.scala index 5e0b19122..dbd87867e 100644 --- a/main/CommandStrings.scala +++ b/main/CommandStrings.scala @@ -161,8 +161,20 @@ ProjectCommand + Use n+1 dots to change to the nth parent. For example, 'project ....' is equivalent to three consecutive 'project ..' commands.""" - def projectsBrief = projectsDetailed - def projectsDetailed = "Displays the names of available projects." + def projectsBrief = "Displays the names of available projects or temporarily adds/removes extra builds to the session." + def projectsDetailed = +ProjectsCommand + """ + Displays the names of available builds and the projects defined in those builds. + +""" + ProjectsCommand + """ add + + Adds the builds at the provided URIs to this session. + These builds may be selected using the """ + ProjectCommand + """ command. + Alternatively, tasks from these builds may be run using the explicit syntax {URI}project/task + +""" + ProjectsCommand + """ remove + + Removes extra builds from this session. + Builds explicitly listed in the build definition are not affected by this command. +""" def sbtrc = ".sbtrc" diff --git a/main/Load.scala b/main/Load.scala index 58b8f377b..19649eb1e 100755 --- a/main/Load.scala +++ b/main/Load.scala @@ -27,14 +27,14 @@ object Load import Locate.DefinesClass // note that there is State passed in but not pulled out - def defaultLoad(state: State, baseDirectory: File, log: Logger, isPlugin: Boolean = false): (() => Eval, BuildStructure) = + def defaultLoad(state: State, baseDirectory: File, log: Logger, isPlugin: Boolean = false, topLevelExtras: List[URI] = Nil): (() => Eval, BuildStructure) = { val globalBase = getGlobalBase(state) val base = baseDirectory.getCanonicalFile val definesClass = FileValueCache(Locate.definesClass _) val rawConfig = defaultPreGlobal(state, base, definesClass.get, globalBase, log) val config0 = defaultWithGlobal(state, base, rawConfig, globalBase, log) - val config = if(isPlugin) enableSbtPlugin(config0) else config0 + val config = if(isPlugin) enableSbtPlugin(config0) else config0.copy(extraBuilds = topLevelExtras) val result = apply(base, state, config) definesClass.clear() result @@ -51,7 +51,8 @@ object Load val delegates = defaultDelegates val pluginMgmt = PluginManagement(loader) val inject = InjectSettings(injectGlobal(state), Nil, const(Nil)) - new LoadBuildConfiguration(stagingDirectory, classpath, loader, compilers, evalPluginDef, definesClass, delegates, EvaluateTask.injectStreams, pluginMgmt, inject, None, log) + new LoadBuildConfiguration(stagingDirectory, classpath, loader, compilers, evalPluginDef, definesClass, delegates, + EvaluateTask.injectStreams, pluginMgmt, inject, None, Nil, log) } def injectGlobal(state: State): Seq[Project.Setting[_]] = (appConfiguration in GlobalScope :== state.configuration) +: @@ -235,7 +236,7 @@ object Load if(srcs.isEmpty) const(Nil) else EvaluateConfigurations(eval(), srcs, imports) def load(file: File, s: State, config: LoadBuildConfiguration): PartBuild = - load(file, builtinLoader(s, config.copy(pluginManagement = config.pluginManagement.shift) )) + load(file, builtinLoader(s, config.copy(pluginManagement = config.pluginManagement.shift, extraBuilds = Nil)), config.extraBuilds.toList ) def builtinLoader(s: State, config: LoadBuildConfiguration): BuildLoader = { val fail = (uri: URI) => error("Invalid build URI (no handler available): " + uri) @@ -244,11 +245,11 @@ object Load val components = BuildLoader.components(resolver, build, full = BuildLoader.componentLoader) BuildLoader(components, fail, s, config) } - def load(file: File, loaders: BuildLoader): PartBuild = loadURI(IO.directoryURI(file), loaders) - def loadURI(uri: URI, loaders: BuildLoader): PartBuild = + def load(file: File, loaders: BuildLoader, extra: List[URI]): PartBuild = loadURI(IO.directoryURI(file), loaders, extra) + def loadURI(uri: URI, loaders: BuildLoader, extra: List[URI]): PartBuild = { IO.assertAbsolute(uri) - val (referenced, map, newLoaders) = loadAll(uri :: Nil, Map.empty, loaders, Map.empty) + val (referenced, map, newLoaders) = loadAll(uri :: extra, Map.empty, loaders, Map.empty) checkAll(referenced, map) val build = new PartBuild(uri, map) newLoaders transformAll build @@ -651,7 +652,8 @@ object Load final case class LoadBuildConfiguration(stagingDirectory: File, classpath: Seq[Attributed[File]], loader: ClassLoader, compilers: Compilers, evalPluginDef: (BuildStructure, State) => PluginData, definesClass: DefinesClass, delegates: LoadedBuild => Scope => Seq[Scope], scopeLocal: ScopeLocal, - pluginManagement: PluginManagement, injectSettings: InjectSettings, globalPlugin: Option[GlobalPlugin], log: Logger) + pluginManagement: PluginManagement, injectSettings: InjectSettings, globalPlugin: Option[GlobalPlugin], extraBuilds: Seq[URI], + log: Logger) { lazy val (globalPluginClasspath, globalPluginLoader) = pluginDefinitionLoader(this, Load.globalPluginClasspath(globalPlugin)) lazy val globalPluginNames = if(globalPluginClasspath.isEmpty) Nil else getPluginNames(globalPluginClasspath, globalPluginLoader) diff --git a/main/Main.scala b/main/Main.scala index 416977054..fb61984de 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -7,6 +7,7 @@ package sbt import compiler.EvalImports import Types.{const,idFun} import Aggregation.AnyKeys + import Project.LoadAction import scala.annotation.tailrec import Path._ @@ -317,13 +318,35 @@ object BuiltinCommands else Help.empty - def projects = Command.command(ProjectsCommand, projectsBrief, projectsDetailed ) { s => + def projects = Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed )(s => projectsParser(s).?) { + case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds) + case (s, None) => showProjects(s); s + } + def showProjects(s: State) + { val extracted = Project extract s import extracted._ import currentRef.{build => curi, project => cid} listBuild(curi, structure.units(curi), true, cid, s.log) for( (uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, s.log) - s + } + def transformExtraBuilds(s: State, f: List[URI] => List[URI]): State = + { + val original = Project.extraBuilds(s) + val extraUpdated = Project.updateExtraBuilds(s, f) + try doLoadProject(extraUpdated, LoadAction.Current) + catch { case e: Exception => + s.log.error("Project loading failed: reverting to previous state.") + Project.setExtraBuilds(s, original) + } + } + + def projectsParser(s: State): Parser[List[URI] => List[URI]] = + { + val addBase = token(Space ~> "add") ~> token(Space ~> basicUri, "").+ + val removeBase = token(Space ~> "remove") ~> token(Space ~> Uri(Project.extraBuilds(s).toSet) ).+ + addBase.map(toAdd => (xs: List[URI]) => (toAdd.toList ::: xs).removeDuplicates) | + removeBase.map(toRemove => (xs: List[URI]) => xs.filterNot(toRemove.toSet)) } def project = Command.make(ProjectCommand, projectBrief, projectDetailed)(ProjectNavigation.command) @@ -356,10 +379,12 @@ object BuiltinCommands def loadProjectCommands(arg: String) = (OnFailure + " " + LoadFailed) :: (LoadProjectImpl + " " + arg).trim :: ClearOnFailure :: FailureWall :: Nil def loadProject = Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(_ => matched(Project.loadActionParser)) { (s,arg) => loadProjectCommands(arg) ::: s } - def loadProjectImpl = Command(LoadProjectImpl)(_ => Project.loadActionParser) { (s0, action) => + def loadProjectImpl = Command(LoadProjectImpl)(_ => Project.loadActionParser)( doLoadProject ) + def doLoadProject(s0: State, action: LoadAction.Value): State = + { val (s, base) = Project.loadAction(SessionVar.clear(s0), action) IO.createDirectory(base) - val (eval, structure) = Load.defaultLoad(s, base, s.log, Project.inPluginProject(s)) + val (eval, structure) = Load.defaultLoad(s, base, s.log, Project.inPluginProject(s), Project.extraBuilds(s)) val session = Load.initialSession(structure, eval, s0) SessionSettings.checkSession(session, s) Project.setProject(session, structure, s) diff --git a/main/Project.scala b/main/Project.scala index 7b319d87d..d92f522ea 100755 --- a/main/Project.scala +++ b/main/Project.scala @@ -361,6 +361,14 @@ object Project extends Init[Scope] with ProjectExtra extracted.session.appendRaw(settings flatMap { x => rescope(x) } ) } + val ExtraBuilds = AttributeKey[List[URI]]("extra-builds", "Extra build URIs to load in addition to the ones defined by the project.") + def extraBuilds(s: State): List[URI] = getOrNil(s, ExtraBuilds) + def getOrNil[T](s: State, key: AttributeKey[List[T]]): List[T] = s get key getOrElse Nil + def setExtraBuilds(s: State, extra: List[URI]): State = s.put(ExtraBuilds, extra) + def addExtraBuilds(s: State, extra: List[URI]): State = setExtraBuilds(s, extra ::: extraBuilds(s)) + def removeExtraBuilds(s: State, remove: List[URI]): State = updateExtraBuilds(s, _.filterNot(remove.toSet)) + def updateExtraBuilds(s: State, f: List[URI] => List[URI]): State = setExtraBuilds(s, f(extraBuilds(s))) + object LoadAction extends Enumeration { val Return, Current, Plugins = Value } @@ -370,7 +378,7 @@ object Project extends Init[Scope] with ProjectExtra val loadActionParser = token(Space ~> ("plugins" ^^^ Plugins | "return" ^^^ Return)) ?? Current val ProjectReturn = AttributeKey[List[File]]("project-return", "Maintains a stack of builds visited using reload.") - def projectReturn(s: State): List[File] = s get ProjectReturn getOrElse Nil + def projectReturn(s: State): List[File] = getOrNil(s, ProjectReturn) def inPluginProject(s: State): Boolean = projectReturn(s).toList.length > 1 def setProjectReturn(s: State, pr: List[File]): State = s.copy(attributes = s.attributes.put( ProjectReturn, pr) ) def loadAction(s: State, action: LoadAction.Value) = action match { diff --git a/main/command/State.scala b/main/command/State.scala index b944aa929..d0abeb05c 100644 --- a/main/command/State.scala +++ b/main/command/State.scala @@ -9,7 +9,7 @@ package sbt /** Data structure representing all command execution information. -@param `configuration` provides access to the launcher environment, including the application configuration, Scala versions, jvm/filesystem wide locking, and the launcher itself +@param configuration provides access to the launcher environment, including the application configuration, Scala versions, jvm/filesystem wide locking, and the launcher itself @param definedCommands the list of command definitions that evaluate command strings. These may be modified to change the available commands. @param onFailure the command to execute when another command fails. `onFailure` is cleared before the failure handling command is executed. @param remainingCommands the sequence of commands to execute. This sequence may be modified to change the commands to be executed. Typically, the `::` and `:::` methods are used to prepend new commands to run. diff --git a/util/complete/Parsers.scala b/util/complete/Parsers.scala index ef34ea5fc..772ec20d5 100644 --- a/util/complete/Parsers.scala +++ b/util/complete/Parsers.scala @@ -96,7 +96,8 @@ trait Parsers def flag[T](p: Parser[T]): Parser[Boolean] = (p ^^^ true) ?? false def trimmed(p: Parser[String]) = p map { _.trim } - def Uri(ex: Set[URI]) = mapOrFail(URIClass)( uri => new URI(uri)) examples(ex.map(_.toString)) + lazy val basicUri = mapOrFail(URIClass)( uri => new URI(uri)) + def Uri(ex: Set[URI]) = basicUri examples(ex.map(_.toString)) } object Parsers extends Parsers object DefaultParsers extends Parsers with ParserMain