enhance 'projects' to allow temporarily adding/removing builds to the session

This commit is contained in:
Mark Harrah 2012-04-06 20:28:31 -04:00
parent 0b1297d65f
commit 12d72facb1
6 changed files with 65 additions and 17 deletions

View File

@ -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 <URI>+
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 <URI>+
Removes extra builds from this session.
Builds explicitly listed in the build definition are not affected by this command.
"""
def sbtrc = ".sbtrc"

View File

@ -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)

View File

@ -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, "<build URI>").+
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)

View File

@ -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 {

View File

@ -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.

View File

@ -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