mirror of https://github.com/sbt/sbt.git
improve commands, proper build/project base resolution
finish alias support better project printing in 'projects' completion support for 'help' resolve URIs in ProjectRef against base URI of defining build in keys and project relations resolve base directories and record build URI in BuildUnit preserve relative paths in File to URI conversion for later resolution
This commit is contained in:
parent
72261548ef
commit
2687836ca5
|
|
@ -181,8 +181,8 @@ object Load
|
|||
|
||||
def defaultLoad(state: State, log: Logger): BuildStructure =
|
||||
{
|
||||
val stagingDirectory = defaultStaging // TODO: properly configurable
|
||||
val base = state.configuration.baseDirectory
|
||||
val stagingDirectory = defaultStaging.getCanonicalFile // TODO: properly configurable
|
||||
val base = state.configuration.baseDirectory.getCanonicalFile
|
||||
val loader = getClass.getClassLoader
|
||||
val provider = state.configuration.provider
|
||||
val classpath = provider.mainClasspath ++ provider.scalaProvider.jars
|
||||
|
|
@ -266,21 +266,25 @@ object Load
|
|||
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(RetrieveUnit(config.stagingDirectory, uri), config) )
|
||||
def load(file: File, loader: URI => BuildUnit): LoadedBuild = loadURI(file.getAbsoluteFile.toURI, loader)
|
||||
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)
|
||||
def loadURI(uri: URI, loader: URI => BuildUnit): LoadedBuild =
|
||||
{
|
||||
IO.assertAbsolute(uri)
|
||||
val (referenced, map) = loadAll(uri :: Nil, Map.empty, loader, Map.empty)
|
||||
checkAll(referenced, map)
|
||||
new LoadedBuild(uri, map)
|
||||
}
|
||||
def loaded(unit: BuildUnit): (LoadedBuildUnit, List[ProjectRef]) =
|
||||
{
|
||||
val baseURI = unit.base.toURI.normalize
|
||||
def isRoot(p: Project) = p.base.toURI.normalize == baseURI
|
||||
// since everything should be resolved at this point, we can compare Files instead of converting to URIs
|
||||
def isRoot(p: Project) = p.base == unit.localBase
|
||||
|
||||
val defined = projects(unit)
|
||||
if(defined.isEmpty) error("No projects defined in build unit " + unit)
|
||||
val externals = referenced(defined).toList
|
||||
val rootProjects = defined.filter(isRoot).map(_.id)
|
||||
val projectsInRoot = defined.filter(isRoot).map(_.id)
|
||||
val rootProjects = if(projectsInRoot.isEmpty) defined.head.id :: Nil else projectsInRoot
|
||||
(new LoadedBuildUnit(unit, defined.map(d => (d.id, d)).toMap, rootProjects), externals)
|
||||
}
|
||||
|
||||
|
|
@ -293,10 +297,22 @@ object Load
|
|||
else
|
||||
{
|
||||
val (loadedBuild, refs) = loaded(externalLoader(b))
|
||||
checkBuildBase(loadedBuild.localBase)
|
||||
loadAll(refs.flatMap(_.uri) reverse_::: bs, references.updated(b, refs), externalLoader, builds.updated(b, loadedBuild))
|
||||
}
|
||||
case Nil => (references, builds)
|
||||
}
|
||||
def checkProjectBase(buildBase: File, projectBase: File)
|
||||
{
|
||||
checkDirectory(projectBase)
|
||||
assert(buildBase == projectBase || IO.relativize(buildBase, projectBase).isDefined, "Directory " + projectBase + " is not contained in build root " + buildBase)
|
||||
}
|
||||
def checkBuildBase(base: File) = checkDirectory(base)
|
||||
def checkDirectory(base: File)
|
||||
{
|
||||
assert(base.isDirectory, "Not an existing directory: " + base)
|
||||
assert(base.isAbsolute, "Not absolute: " + base)
|
||||
}
|
||||
def checkAll(referenced: Map[URI, List[ProjectRef]], builds: Map[URI, LoadedBuildUnit])
|
||||
{
|
||||
val rootProject = getRootProject(builds)
|
||||
|
|
@ -310,12 +326,27 @@ object Load
|
|||
}
|
||||
}
|
||||
|
||||
def resolveBase(against: File): Project => Project =
|
||||
def resolveBase(origin: URI, against: File): Project => Project =
|
||||
{
|
||||
val uri = against.getAbsoluteFile.toURI.normalize
|
||||
p => p.copy(base = new File(uri.resolve(p.base.toURI).normalize))
|
||||
assert(origin.isAbsolute, "Origin not absolute: " + origin)
|
||||
def resolveRefs(prs: Seq[ProjectRef]) = prs map resolveRef
|
||||
def resolveRef(pr: ProjectRef) = pr match { case ProjectRef(Some(uri), id) => ProjectRef(Some(resolveURI(uri)), id); case _ => pr }
|
||||
def resolveDeps(ds: Seq[Project.ClasspathDependency]) = ds map resolveDep
|
||||
def resolveDep(d: Project.ClasspathDependency) = d.copy(project = resolveRef(d.project))
|
||||
def resolveURI(u: URI): URI = IO.directoryURI(origin resolve u)
|
||||
def resolve(f: File) =
|
||||
{
|
||||
val fResolved = new File(IO.directoryURI(IO.resolve(against, f)))
|
||||
checkProjectBase(against, fResolved)
|
||||
fResolved
|
||||
}
|
||||
p => p.copy(base = resolve(p.base), aggregate = resolveRefs(p.aggregate), dependencies = resolveDeps(p.dependencies), inherits = resolveRefs(p.inherits))
|
||||
}
|
||||
def projects(unit: BuildUnit): Seq[Project] =
|
||||
{
|
||||
val resolve = resolveBase(unit.uri, unit.localBase)
|
||||
unit.definitions.builds.flatMap(_.projects map resolve)
|
||||
}
|
||||
def projects(unit: BuildUnit): Seq[Project] = unit.definitions.builds.flatMap(_.projects map resolveBase(unit.base))
|
||||
def getRootProject(map: Map[URI, LoadedBuildUnit]): URI => String =
|
||||
uri => getBuild(map, uri).rootProjects.headOption getOrElse emptyBuild(uri)
|
||||
def getConfiguration(map: Map[URI, LoadedBuildUnit], uri: URI, id: String, conf: ConfigKey): Configuration =
|
||||
|
|
@ -331,20 +362,21 @@ object Load
|
|||
def noProject(uri: URI, id: String) = error("No project '" + id + "' defined in '" + uri + "'.")
|
||||
def noConfiguration(uri: URI, id: String, conf: String) = error("No configuration '" + conf + "' defined in project '" + id + "' in '" + uri +"'")
|
||||
|
||||
def loadUnit(base: File, config: LoadBuildConfiguration): BuildUnit =
|
||||
def loadUnit(uri: URI, localBase: File, config: LoadBuildConfiguration): BuildUnit =
|
||||
{
|
||||
val defDir = selectProjectDir(base)
|
||||
val normBase = localBase.getCanonicalFile
|
||||
val defDir = selectProjectDir(normBase)
|
||||
val pluginDir = pluginDirectory(defDir)
|
||||
val plugs = plugins(pluginDir, config)
|
||||
|
||||
val defs = definitionSources(defDir)
|
||||
val loadedDefs =
|
||||
if(defs.isEmpty)
|
||||
new LoadedDefinitions(defDir, outputDirectory(defDir), plugs.loader, Build.default(base) :: Nil)
|
||||
new LoadedDefinitions(defDir, outputDirectory(defDir), plugs.loader, Build.default(normBase) :: Nil)
|
||||
else
|
||||
definitions(defDir, defs, plugs, config.compilers, config.log)
|
||||
|
||||
new BuildUnit(base, loadedDefs, plugs)
|
||||
new BuildUnit(uri, normBase, loadedDefs, plugs)
|
||||
}
|
||||
|
||||
def plugins(dir: File, config: LoadBuildConfiguration): LoadedPlugins = if(dir.exists) buildPlugins(dir, config) else noPlugins(config)
|
||||
|
|
@ -411,15 +443,19 @@ object Load
|
|||
object LoadedPlugins {
|
||||
def empty(loader: ClassLoader) = new LoadedPlugins(Nil, loader, Nil)
|
||||
}
|
||||
final class BuildUnit(val base: File, val definitions: LoadedDefinitions, val plugins: LoadedPlugins)
|
||||
final class BuildUnit(val uri: URI, val localBase: File, val definitions: LoadedDefinitions, val plugins: LoadedPlugins)
|
||||
{
|
||||
override def toString = if(uri.getScheme == "file") localBase.toString else (uri + " (locally: " + localBase +")")
|
||||
}
|
||||
|
||||
final class LoadedBuild(val root: URI, val units: Map[URI, LoadedBuildUnit])
|
||||
final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, Project], val rootProjects: Seq[String])
|
||||
{
|
||||
assert(!rootProjects.isEmpty, "No root projects defined for build unit '" + unit.base + "'")
|
||||
def base = unit.base
|
||||
assert(!rootProjects.isEmpty, "No root projects defined for build unit " + unit)
|
||||
def localBase = unit.localBase
|
||||
def classpath = unit.definitions.target +: unit.plugins.classpath
|
||||
def loader = unit.definitions.loader
|
||||
def loade = unit.definitions.loader
|
||||
override def toString = unit.toString
|
||||
}
|
||||
|
||||
// these are unresolved references
|
||||
|
|
@ -473,7 +509,7 @@ object BuildStreams
|
|||
def projectPath(units: Map[URI, LoadedBuildUnit], root: URI, scoped: ScopedKey[_]): (File, Seq[String]) =
|
||||
scoped.scope.project match
|
||||
{
|
||||
case Global => (units(root).base, GlobalPath :: Nil)
|
||||
case Global => (units(root).localBase, GlobalPath :: Nil)
|
||||
case Select(ProjectRef(Some(uri), Some(id))) => (units(uri).defined(id).base, Nil)
|
||||
case Select(pr) => error("Unresolved project reference (" + pr + ") in " + display(scoped))
|
||||
case This => error("Unresolved project reference (This) in " + display(scoped))
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ package sbt
|
|||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConversions._
|
||||
import Function.tupled
|
||||
import java.net.URI
|
||||
import Path._
|
||||
|
||||
import java.io.File
|
||||
|
|
@ -66,10 +67,18 @@ object Commands
|
|||
def detail(selected: Iterable[String])(h: Help): Option[String] =
|
||||
h.detail match { case (commands, value) => if( selected exists commands ) Some(value) else None }
|
||||
|
||||
// TODO: tab complete on command names
|
||||
def help = Command.args(HelpCommand, helpBrief, helpDetailed, "<command>") { (s, args) =>
|
||||
def help = Command(HelpCommand, helpBrief, helpDetailed)(helpParser)
|
||||
|
||||
def helpParser(s: State) =
|
||||
{
|
||||
val h = s.processors.flatMap(_.help)
|
||||
val helpCommands = h.flatMap(_.detail._1)
|
||||
val args = (token(Space) ~> token( OpOrID.examples(helpCommands : _*) )).*
|
||||
applyEffect(args)(runHelp(s, h))
|
||||
}
|
||||
|
||||
def runHelp(s: State, h: Seq[Help])(args: Seq[String]): State =
|
||||
{
|
||||
val message =
|
||||
if(args.isEmpty)
|
||||
h.map( _.brief match { case (a,b) => a + " : " + b } ).mkString("\n", "\n", "\n")
|
||||
|
|
@ -81,16 +90,20 @@ object Commands
|
|||
|
||||
def alias = Command(AliasCommand, AliasBrief, AliasDetailed) { s =>
|
||||
val name = token(OpOrID.examples( aliasNames(s) : _*) )
|
||||
val assign = token(Space ~ '=' ~ Space) ~> matched(Command.combine(s.processors)(s), partial = true)
|
||||
val base = (OptSpace ~> (name ~ assign.?).?)
|
||||
val assign = token(Space ~ '=' ~ OptSpace)
|
||||
val sfree = removeAliases(s)
|
||||
val to = matched(Command.combine(sfree.processors)(sfree), partial = true) | any.+.string
|
||||
val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?)
|
||||
applyEffect(base)(t => runAlias(s, t) )
|
||||
}
|
||||
def runAlias(s: State, args: Option[(String, Option[String])]): State =
|
||||
|
||||
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
|
||||
args match
|
||||
{
|
||||
case Some((name, Some(value))) => addAlias(s, name.trim, value.trim)
|
||||
case Some((x, None)) if !x.isEmpty=> printAlias(s, x.trim); s
|
||||
case None => printAliases(s); s
|
||||
case Some(x ~ None) if !x.isEmpty => printAlias(s, x.trim); s
|
||||
case Some(name ~ Some(None)) => removeAlias(s, name.trim)
|
||||
case Some(name ~ Some(Some(value))) => addAlias(s, name.trim, value.trim)
|
||||
}
|
||||
|
||||
def shell = Command.command(Shell, ShellBrief, ShellDetailed) { s =>
|
||||
|
|
@ -199,8 +212,12 @@ object Commands
|
|||
}
|
||||
}*/
|
||||
|
||||
def indent(withStar: Boolean) = if(withStar) "\t*" else "\t "
|
||||
def listProject(name: String, current: Boolean, log: Logger) = log.info( indent(current) + name )
|
||||
def listBuild(uri: URI, build: Load.LoadedBuildUnit, current: Boolean, currentID: String, log: Logger) =
|
||||
{
|
||||
log.info("In " + uri)
|
||||
def prefix(id: String) = if(currentID != id) " " else if(current) " * " else "(*)"
|
||||
for(id <- build.defined.keys) log.info("\t" + prefix(id) + id)
|
||||
}
|
||||
|
||||
def act = error("TODO")
|
||||
def projects = Command.command(ProjectsCommand, projectsBrief, projectsDetailed ) { s =>
|
||||
|
|
@ -208,11 +225,8 @@ object Commands
|
|||
val session = Project.session(s)
|
||||
val structure = Project.structure(s)
|
||||
val (curi, cid) = session.current
|
||||
for( (uri, build) <- structure.units)
|
||||
{
|
||||
log.info("In " + uri)
|
||||
for(id <- build.defined.keys) listProject(id, cid == id, log)
|
||||
}
|
||||
listBuild(curi, structure.units(curi), true, cid, log)
|
||||
for( (uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, log)
|
||||
s
|
||||
}
|
||||
def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State =
|
||||
|
|
@ -317,7 +331,6 @@ object Commands
|
|||
}
|
||||
|
||||
def addAlias(s: State, name: String, value: String): State =
|
||||
{
|
||||
if(Command validID name) {
|
||||
val removed = removeAlias(s, name)
|
||||
if(value.isEmpty) removed else removed.copy(processors = newAlias(name, value) +: removed.processors)
|
||||
|
|
@ -325,10 +338,12 @@ object Commands
|
|||
System.err.println("Invalid alias name '" + name + "'.")
|
||||
s.fail
|
||||
}
|
||||
}
|
||||
|
||||
def removeAliases(s: State): State = s.copy(processors = removeAliases(s.processors))
|
||||
def removeAliases(as: Seq[Command]): Seq[Command] = as.filter(c => ! (c.tags contains CommandAliasKey))
|
||||
def removeAlias(s: State, name: String): State = s.copy(processors = s.processors.filter(c => !isAliasNamed(name, c)) )
|
||||
def isAliasNamed(name: String, c: Command): Boolean = isNamed(name, getAlias(c))
|
||||
def isNamed(name: String, alias: Option[(String,String)]): Boolean = alias match { case None => false; case Some((alias,_)) => name != alias }
|
||||
def isNamed(name: String, alias: Option[(String,String)]): Boolean = alias match { case None => false; case Some((n,_)) => name == n }
|
||||
|
||||
def getAlias(c: Command): Option[(String,String)] = c.tags get CommandAliasKey
|
||||
def printAlias(s: State, name: String): Unit = printAliases(aliases(s,(n,v) => n == name) )
|
||||
|
|
@ -343,9 +358,9 @@ object Commands
|
|||
s.processors.flatMap(c => getAlias(c).filter(tupled(pred)))
|
||||
|
||||
def newAlias(name: String, value: String): Command =
|
||||
Command(name, (name, "<alias>"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
|
||||
Command(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
|
||||
def aliasBody(name: String, value: String)(state: State): Parser[() => State] =
|
||||
Parser(Command.combine(removeAlias(state,name).processors)(state))(value)
|
||||
|
||||
Parser(Command.combine(state.processors)(state))(value)
|
||||
|
||||
val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias")
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ object Project extends Init[Scope]
|
|||
}
|
||||
def transform(g: Scope => Scope, ss: Seq[Setting[_]]): Seq[Setting[_]] = {
|
||||
val f = mapScope(g)
|
||||
ss.map(_ mapKey f)
|
||||
ss.map(_ mapKey f mapReferenced f)
|
||||
}
|
||||
|
||||
val SessionKey = AttributeKey[SessionSettings]("session-settings")
|
||||
|
|
@ -95,18 +95,18 @@ trait ProjectConstructors
|
|||
implicit def configDependencyConstructor[T <% ProjectRef](p: T): Project.Constructor = new Project.Constructor(p)
|
||||
implicit def classpathDependency[T <% ProjectRef](p: T): Project.ClasspathDependency = new Project.ClasspathDependency(p, None)
|
||||
}
|
||||
|
||||
// the URI must be resolved and normalized before it is definitive
|
||||
final case class ProjectRef(uri: Option[URI], id: Option[String])
|
||||
object ProjectRef
|
||||
{
|
||||
def apply(base: URI, id: String): ProjectRef = ProjectRef(Some(base), Some(id))
|
||||
/** Reference to the project with 'id' in the current build unit.*/
|
||||
def apply(id: String): ProjectRef = ProjectRef(None, Some(id))
|
||||
def apply(base: File, id: String): ProjectRef = ProjectRef(Some(base.toURI), Some(id))
|
||||
def apply(base: File, id: String): ProjectRef = ProjectRef(Some(IO.toURI(base)), Some(id))
|
||||
/** Reference to the root project at 'base'.*/
|
||||
def apply(base: URI): ProjectRef = ProjectRef(Some(base), None)
|
||||
/** Reference to the root project at 'base'.*/
|
||||
def apply(base: File): ProjectRef = ProjectRef(Some(base.toURI), None)
|
||||
def apply(base: File): ProjectRef = ProjectRef(Some(IO.toURI(base)), None)
|
||||
/** Reference to the root project in the current build unit.*/
|
||||
def root = ProjectRef(None, None)
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ object Scope
|
|||
}
|
||||
def resolveRef(current: URI, rootProject: URI => String, ref: ProjectRef): (URI, String) =
|
||||
{
|
||||
val uri = ref.uri getOrElse current
|
||||
val unURI = ref.uri match { case Some(u) => current resolve u; case None => current }
|
||||
val uri = unURI.normalize
|
||||
(uri, ref.id getOrElse rootProject(uri))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,11 +99,11 @@ trait Init[Scope]
|
|||
def delegate(sMap: ScopedMap)(implicit delegates: Scope => Seq[Scope]): ScopedMap =
|
||||
{
|
||||
val md = memoDelegates(delegates)
|
||||
def refMap(refKey: ScopedKey[_]) = new (ScopedKey ~> ScopedKey) { def apply[T](k: ScopedKey[T]) = mapReferenced(sMap, k, md(k.scope), refKey) }
|
||||
def refMap(refKey: ScopedKey[_]) = new (ScopedKey ~> ScopedKey) { def apply[T](k: ScopedKey[T]) = delegateForKey(sMap, k, md(k.scope), refKey) }
|
||||
val f = new (SettingSeq ~> SettingSeq) { def apply[T](ks: Seq[Setting[T]]) = ks.map{ s => s mapReferenced refMap(s.key) } }
|
||||
sMap mapValues f
|
||||
}
|
||||
private[this] def mapReferenced[T](sMap: ScopedMap, k: ScopedKey[T], scopes: Seq[Scope], refKey: ScopedKey[_]): ScopedKey[T] =
|
||||
private[this] def delegateForKey[T](sMap: ScopedMap, k: ScopedKey[T], scopes: Seq[Scope], refKey: ScopedKey[_]): ScopedKey[T] =
|
||||
{
|
||||
val scache = PMap.empty[ScopedKey, ScopedKey]
|
||||
def resolve(search: Seq[Scope]): ScopedKey[T] =
|
||||
|
|
|
|||
|
|
@ -588,4 +588,37 @@ object IO
|
|||
Using.fileInputStream(file) { fin =>
|
||||
Using.gzipInputStream(fin) { ing =>
|
||||
Using.bufferedInputStream(ing)(f) }}
|
||||
|
||||
/** Converts an absolute File to a URI. The File is converted to a URI (toURI),
|
||||
* normalized (normalize), encoded (toASCIIString), and a forward slash ('/') is appended to the path component if
|
||||
* it does not already end with a slash.
|
||||
*/
|
||||
def directoryURI(dir: File): URI =
|
||||
{
|
||||
assertAbsolute(dir)
|
||||
directoryURI(dir.toURI.normalize)
|
||||
}
|
||||
|
||||
/** Converts an absolute File to a URI. The File is converted to a URI (toURI),
|
||||
* normalized (normalize), encoded (toASCIIString), and a forward slash ('/') is appended to the path component if
|
||||
* it does not already end with a slash.
|
||||
*/
|
||||
def directoryURI(uri: URI): URI =
|
||||
{
|
||||
assertAbsolute(uri)
|
||||
val str = uri.toASCIIString
|
||||
val dirStr = if(str.endsWith("/")) str else str + "/"
|
||||
(new URI(dirStr)).normalize
|
||||
}
|
||||
/** Converts the given File to a URI. If the File is relative, the URI is relative, unlike File.toURI*/
|
||||
def toURI(f: File): URI = if(f.isAbsolute) f.toURI else new URI(f.getPath)
|
||||
def resolve(base: File, f: File): File =
|
||||
{
|
||||
assertAbsolute(base)
|
||||
val fabs = if(f.isAbsolute) f else new File(directoryURI(new File(base, f.getPath)))
|
||||
assertAbsolute(fabs)
|
||||
fabs
|
||||
}
|
||||
def assertAbsolute(f: File) = assert(f.isAbsolute, "Not absolute: " + f)
|
||||
def assertAbsolute(uri: URI) = assert(uri.isAbsolute, "Not absolute: " + uri)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue