Document helper functions in BuildStructure

Also define LoadedBuildUnit#projects & BuildUnit#thisRootProject, &
cleanup AttributeKey & BasicAttributeMap
This commit is contained in:
Dale Wijnand 2019-06-01 10:32:35 +01:00
parent b7fb0ca2a3
commit 4e89e8ace5
No known key found for this signature in database
GPG Key ID: 4F256E3D151DF5EF
8 changed files with 95 additions and 73 deletions

View File

@ -17,11 +17,11 @@ import sbt.util.OptJsonWriter
/**
* A key in an [[AttributeMap]] that constrains its associated value to be of type `T`.
* The key is uniquely defined by its [[label]] and type `T`, represented at runtime by [[manifest]].
* The key is uniquely defined by its `label` and type `T`, represented at runtime by `manifest`.
*/
sealed trait AttributeKey[T] {
/** The runtime evidence for `T` */
/** The runtime evidence for `T`. */
def manifest: Manifest[T]
/** The label is the identifier for the key and is camelCase by convention. */
@ -103,10 +103,10 @@ object AttributeKey {
rank0: Int
)(implicit mf: Manifest[T], ojw: OptJsonWriter[T]): AttributeKey[T] =
new SharedAttributeKey[T] {
require(name.headOption match {
case Some(c) => c.isLower
case None => false
}, s"A named attribute key must start with a lowercase letter: $name")
require(
name.headOption.exists(_.isLower),
s"A named attribute key must start with a lowercase letter: $name"
)
def manifest = mf
val label = Util.hyphenToCamel(name)
@ -220,18 +220,13 @@ private class BasicAttributeMap(private val backing: Map[AttributeKey[_], Any])
def keys: Iterable[AttributeKey[_]] = backing.keys
def ++(o: Iterable[AttributeEntry[_]]): AttributeMap = {
val newBacking = (backing /: o) {
case (b, AttributeEntry(key, value)) => b.updated(key, value)
}
new BasicAttributeMap(newBacking)
}
def ++(o: Iterable[AttributeEntry[_]]): AttributeMap =
new BasicAttributeMap(o.foldLeft(backing)((b, e) => b.updated(e.key, e.value)))
def ++(o: AttributeMap): AttributeMap =
o match {
case bam: BasicAttributeMap => new BasicAttributeMap(backing ++ bam.backing)
case _ => o ++ this
}
def ++(o: AttributeMap): AttributeMap = o match {
case bam: BasicAttributeMap => new BasicAttributeMap(backing ++ bam.backing)
case _ => o ++ this
}
def entries: Iterable[AttributeEntry[_]] =
backing.collect {

View File

@ -667,11 +667,10 @@ object BuiltinCommands {
def act: Command = Command.customHelp(Act.actParser, actHelp)
def actHelp: State => Help =
s =>
CommandStrings.showHelp ++ CommandStrings.printHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(
s
)
def actHelp: State => Help = { s =>
CommandStrings.showHelp ++ CommandStrings.printHelp ++ CommandStrings.multiTaskHelp ++
keysHelp(s)
}
def keysHelp(s: State): Help =
if (Project.isProjectLoaded(s))

View File

@ -28,35 +28,47 @@ final class BuildStructure(
val index: StructureIndex,
val streams: State => Streams,
val delegates: Scope => Seq[Scope],
val scopeLocal: ScopeLocal
val scopeLocal: ScopeLocal,
) {
val rootProject: URI => String = Load getRootProject units
def allProjects: Seq[ResolvedProject] = units.values.flatMap(_.defined.values).toSeq
def allProjects(build: URI): Seq[ResolvedProject] =
units.get(build).toList.flatMap(_.defined.values)
def allProjectRefs: Seq[ProjectRef] = units.toSeq flatMap {
case (build, unit) => refs(build, unit.defined.values.toSeq)
}
def allProjectRefs(build: URI): Seq[ProjectRef] = refs(build, allProjects(build))
def allProjectPairs: Seq[(ResolvedProject, ProjectRef)] =
for {
(build, unit) <- units.toSeq
p: ResolvedProject <- unit.defined.values.toSeq
} yield (p, ProjectRef(build, p.id))
val extra: BuildUtil[ResolvedProject] = BuildUtil(root, units, index.keyIndex, data)
private[this] def refs(build: URI, projects: Seq[ResolvedProject]): Seq[ProjectRef] =
projects.map { p =>
ProjectRef(build, p.id)
}
/** The root project for the specified build. Throws if no build or empty build. */
val rootProject: URI => String = extra.rootProjectID
/** All the projects in all builds. */
def allProjects: Seq[ResolvedProject] = eachBuild((_, p) => p)
/** References to all the projects in all the builds. */
def allProjectRefs: Seq[ProjectRef] = eachBuild((b, p) => ProjectRef(b, p.id))
/** Both the projects and the projects reference of all the projects in all the builds. */
def allProjectPairs: Seq[(ResolvedProject, ProjectRef)] =
eachBuild((b, p) => p -> ProjectRef(b, p.id))
/** All the projects in the specified build. */
def allProjects(build: URI): Seq[ResolvedProject] = eachProject(build, identity)
/** The references to all the projects in the specified build. */
def allProjectRefs(build: URI): Seq[ProjectRef] = eachProject(build, p => ProjectRef(build, p.id))
/** Foreach project in each build apply the specified function. */
private[this] def eachBuild[A](f: (URI, ResolvedProject) => A): Seq[A] =
units.iterator.flatMap { case (build, unit) => unit.projects.map(f(build, _)) }.toIndexedSeq
/** Foreach project in the specified build apply the specified function. */
private[this] def eachProject[A](build: URI, f: ResolvedProject => A): Seq[A] =
units.get(build).iterator.flatMap(_.projects).map(f).toIndexedSeq
}
// 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[_]]],
val triggers: Triggers[Task],
val keyIndex: KeyIndex,
val aggregateKeyIndex: KeyIndex
val aggregateKeyIndex: KeyIndex,
)
/**
@ -78,13 +90,11 @@ final class LoadedBuildUnit(
* The project to use as the default when one is not otherwise selected.
* [[LocalRootProject]] resolves to this from within the same build.
*/
val root = rootProjects match {
case Nil =>
throw new java.lang.AssertionError(
"assertion failed: No root projects defined for build unit " + unit
)
case Seq(root, _*) => root
}
val root = rootProjects.headOption.getOrElse(
throw new java.lang.AssertionError(
s"assertion failed: No root projects defined for build unit $unit"
)
)
/** The base directory of the build unit (not the build definition).*/
def localBase = unit.localBase
@ -104,6 +114,9 @@ final class LoadedBuildUnit(
/** The imports to use for .sbt files, `consoleProject` and other contexts that use code from the build definition. */
def imports = BuildUtil.getImports(unit)
def projects: Iterable[ResolvedProject] = defined.values
override def toString = unit.toString
}
@ -241,28 +254,37 @@ final class BuildUnit(
final class LoadedBuild(val root: URI, val units: Map[URI, LoadedBuildUnit]) {
BuildUtil.checkCycles(units)
def allProjectRefs: Seq[(ProjectRef, ResolvedProject)] =
for ((uri, unit) <- units.toSeq; (id, proj) <- unit.defined) yield ProjectRef(uri, id) -> proj
units.iterator.flatMap {
case (build, unit) => unit.projects.map(p => ProjectRef(build, p.id) -> p)
}.toIndexedSeq
def extra(data: Settings[Scope])(keyIndex: KeyIndex): BuildUtil[ResolvedProject] =
BuildUtil(root, units, keyIndex, data)
private[sbt] def autos = GroupedAutoPlugins(units)
}
final class PartBuild(val root: URI, val units: Map[URI, PartBuildUnit])
sealed trait BuildUnitBase { def rootProjects: Seq[String]; def buildSettings: Seq[Setting[_]] }
final class PartBuildUnit(
val unit: BuildUnit,
val defined: Map[String, Project],
val rootProjects: Seq[String],
val buildSettings: Seq[Setting[_]]
) extends BuildUnitBase {
def resolve(f: Project => ResolvedProject): LoadedBuildUnit =
new LoadedBuildUnit(unit, defined mapValues f toMap, rootProjects, buildSettings)
new LoadedBuildUnit(unit, defined.mapValues(f).toMap, rootProjects, buildSettings)
def resolveRefs(f: ProjectReference => ProjectRef): LoadedBuildUnit = resolve(_ resolve f)
}
object BuildStreams {
type Streams = std.Streams[ScopedKey[_]]
type Streams = sbt.std.Streams[ScopedKey[_]]
final val GlobalPath = "$global"
final val BuildUnitPath = "$build"
@ -300,11 +322,11 @@ object BuildStreams {
show: T => String
): String =
axis match {
case Zero => GlobalPath
case This =>
sys.error("Unresolved This reference for " + label + " in " + displayFull(scoped))
case Zero => GlobalPath
case This => sys.error(s"Unresolved This reference for $label in ${displayFull(scoped)}")
case Select(t) => show(t)
}
def nonProjectPath[T](scoped: ScopedKey[T]): Seq[String] = {
val scope = scoped.scope
pathComponent(scope.config, scoped, "config")(_.name) ::
@ -313,10 +335,13 @@ object BuildStreams {
scoped.key.label ::
Nil
}
def showAMap(a: AttributeMap): String =
a.entries.toSeq.sortBy(_.key.label).map {
case AttributeEntry(key, value) => key.label + "=" + value.toString
} mkString (" ")
a.entries.toStream
.sortBy(_.key.label)
.map { case AttributeEntry(key, value) => s"${key.label}=$value" }
.mkString(" ")
def projectPath(
units: Map[URI, LoadedBuildUnit],
root: URI,

View File

@ -21,8 +21,10 @@ final class BuildUtil[Proj](
val configurations: Proj => Seq[ConfigKey],
val aggregates: Relation[ProjectRef, ProjectRef]
) {
def rootProject(uri: URI): Proj =
project(uri, rootProjectID(uri))
def thisRootProject: Proj = rootProject(root)
def rootProject(uri: URI): Proj = project(uri, rootProjectID(uri))
def resolveRef(ref: Reference): ResolvedReference =
Scope.resolveReference(root, rootProjectID, ref)
@ -31,14 +33,14 @@ final class BuildUtil[Proj](
case ProjectRef(uri, id) => project(uri, id)
case BuildRef(uri) => rootProject(uri)
}
def projectRefFor(ref: ResolvedReference): ProjectRef = ref match {
case p: ProjectRef => p
case BuildRef(uri) => ProjectRef(uri, rootProjectID(uri))
}
def projectForAxis(ref: Option[ResolvedReference]): Proj = ref match {
case Some(ref) => projectFor(ref)
case None => rootProject(root)
}
def projectForAxis(ref: Option[ResolvedReference]): Proj = ref.fold(thisRootProject)(projectFor)
def exactProject(refOpt: Option[Reference]): Option[Proj] = refOpt map resolveRef flatMap {
case ProjectRef(uri, id) => Some(project(uri, id))
case _ => None
@ -46,7 +48,9 @@ final class BuildUtil[Proj](
val configurationsForAxis: Option[ResolvedReference] => Seq[String] =
refOpt => configurations(projectForAxis(refOpt)).map(_.name)
}
object BuildUtil {
def apply(
root: URI,
@ -57,14 +61,14 @@ object BuildUtil {
val getp = (build: URI, project: String) => Load.getProject(units, build, project)
val configs = (_: ResolvedProject).configurations.map(c => ConfigKey(c.name))
val aggregates = aggregationRelation(units)
new BuildUtil(keyIndex, data, root, Load getRootProject units, getp, configs, aggregates)
new BuildUtil(keyIndex, data, root, Load.getRootProject(units), getp, configs, aggregates)
}
def dependencies(units: Map[URI, LoadedBuildUnit]): BuildDependencies = {
import scala.collection.mutable
val agg = new mutable.HashMap[ProjectRef, Seq[ProjectRef]]
val cp = new mutable.HashMap[ProjectRef, Seq[ClasspathDep[ProjectRef]]]
for (lbu <- units.values; rp <- lbu.defined.values) {
for (lbu <- units.values; rp <- lbu.projects) {
val ref = ProjectRef(lbu.unit.uri, rp.id)
cp(ref) = rp.dependencies
agg(ref) = rp.aggregate
@ -79,7 +83,7 @@ object BuildUtil {
)(base: ResolvedProject => Seq[ProjectRef]): Seq[ResolvedProject] =
Dag.topologicalSort(proj)(p => base(p) map getRef)
// check for cycles
for ((_, lbu) <- units; proj <- lbu.defined.values) {
for ((_, lbu) <- units; proj <- lbu.projects) {
deps(proj)(_.dependencies.map(_.project))
deps(proj)(_.aggregate)
}
@ -110,7 +114,7 @@ object BuildUtil {
val depPairs =
for {
(uri, unit) <- units.toIterable // don't lose this toIterable, doing so breaks actions/cross-multiproject & actions/update-state-fail
project <- unit.defined.values
project <- unit.projects
ref = ProjectRef(uri, project.id)
agg <- project.aggregate
} yield (ref, agg)

View File

@ -23,7 +23,7 @@ private[sbt] final class GroupedAutoPlugins(
private[sbt] object GroupedAutoPlugins {
private[sbt] def apply(units: Map[URI, LoadedBuildUnit]): GroupedAutoPlugins = {
val byBuild: Map[URI, Seq[AutoPlugin]] =
units.mapValues(unit => unit.defined.values.flatMap(_.autoPlugins).toSeq.distinct).toMap
units.mapValues(unit => unit.projects.flatMap(_.autoPlugins).toSeq.distinct).toMap
val all: Seq[AutoPlugin] = byBuild.values.toSeq.flatten.distinct
new GroupedAutoPlugins(all, byBuild)
}

View File

@ -665,8 +665,7 @@ private[sbt] object Load {
def getProject(map: Map[URI, LoadedBuildUnit], uri: URI, id: String): ResolvedProject =
getBuild(map, uri).defined.getOrElse(id, noProject(uri, id))
def getBuild[T](map: Map[URI, T], uri: URI): T =
map.getOrElse(uri, noBuild(uri))
def getBuild[T](map: Map[URI, T], uri: URI): T = map.getOrElse(uri, noBuild(uri))
def emptyBuild(uri: URI) = sys.error(s"No root project defined for build unit '$uri'")
def noBuild(uri: URI) = sys.error(s"Build unit '$uri' not defined.")
@ -779,7 +778,7 @@ private[sbt] object Load {
case Right(id) => id
case Left(msg) => sys.error(autoIDError(f, msg))
}
def nthParentName(f: File, i: Int): String =
@tailrec def nthParentName(f: File, i: Int): String =
if (f eq null) BuildDef.defaultID(localBase)
else if (i <= 0) normalizeID(f)
else nthParentName(f.getParentFile, i - 1)

View File

@ -124,7 +124,7 @@ private[sbt] object PluginsDebug {
def helpBuild(uri: URI, build: LoadedBuildUnit): String = {
val pluginStrings = for (plugin <- availableAutoPlugins(build)) yield {
val activatedIn =
build.defined.values.toList.filter(_.autoPlugins.contains(plugin)).map(_.id)
build.projects.toList.filter(_.autoPlugins.contains(plugin)).map(_.id)
val actString =
if (activatedIn.nonEmpty) activatedIn.mkString(": enabled in ", ", ", "")
else "" // TODO: deal with large builds

View File

@ -46,7 +46,7 @@ object Resolve {
scope
else {
val (resolvedRef, proj) = scope.project match {
case Zero | This => (None, index.rootProject(index.root))
case Zero | This => (None, index.thisRootProject)
case Select(ref) =>
val r = index resolveRef ref
(Some(r), index.projectFor(r))