Skip to content

Commit

Permalink
Add warning for unknown configurations
Browse files Browse the repository at this point in the history
Fixes sbt#3432
  • Loading branch information
steinybot committed Jun 21, 2018
1 parent f2a7e1f commit c632802
Show file tree
Hide file tree
Showing 15 changed files with 536 additions and 93 deletions.
Expand Up @@ -209,7 +209,11 @@ object Parser extends ParserMain {
a.ifValid {
a.result match {
case Some(av) => success(f(av))
case None => new MapParser(a, f)
case None =>
a match {
case m: MapParser[_, A] => m.map(f)
case _ => new MapParser(a, f)
}
}
}

Expand Down Expand Up @@ -381,8 +385,8 @@ trait ParserMain {
}

/** Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.*/
implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] =
charClass(r contains _).examples(r.map(_.toString): _*)
implicit def range(r: collection.immutable.NumericRange[Char], label: String): Parser[Char] =
charClass(r contains _, label).examples(r.map(_.toString): _*)

/** Defines a Parser that parses a single character only if it is contained in `legal`.*/
def chars(legal: String): Parser[Char] = {
Expand All @@ -394,7 +398,7 @@ trait ParserMain {
* Defines a Parser that parses a single character only if the predicate `f` returns true for that character.
* If this parser fails, `label` is used as the failure message.
*/
def charClass(f: Char => Boolean, label: String = "<unspecified>"): Parser[Char] =
def charClass(f: Char => Boolean, label: String): Parser[Char] =
new CharacterClass(f, label)

/** Presents a single Char `ch` as a Parser that only parses that exact character. */
Expand Down Expand Up @@ -744,6 +748,7 @@ private final class MapParser[A, B](a: Parser[A], f: A => B) extends ValidParser
def completions(level: Int) = a.completions(level)
override def isTokenStart = a.isTokenStart
override def toString = "map(" + a + ")"
def map[C](g: B => C) = new MapParser[A, C](a, f.andThen(g))
}

private final class Filter[T](p: Parser[T], f: T => Boolean, seen: String, msg: String => String)
Expand Down
Expand Up @@ -163,7 +163,7 @@ trait Parsers {
}, "non-double-quote-backslash character")

/** Matches a single character that is valid somewhere in a URI. */
lazy val URIChar = charClass(alphanum) | chars("_-!.~'()*,;:$&+=?/[]@%#")
lazy val URIChar = charClass(alphanum, "alphanum") | chars("_-!.~'()*,;:$&+=?/[]@%#")

/** Returns true if `c` is an ASCII letter or digit. */
def alphanum(c: Char) =
Expand Down
4 changes: 2 additions & 2 deletions internal/util-complete/src/test/scala/ParserTest.scala
Expand Up @@ -121,8 +121,8 @@ object ParserTest extends Properties("Completing Parser") {
property("repeatDep accepts two tokens") = matches(repeat, colors.toSeq.take(2).mkString(" "))
}
object ParserExample {
val ws = charClass(_.isWhitespace).+
val notws = charClass(!_.isWhitespace).+
val ws = charClass(_.isWhitespace, "whitespace").+
val notws = charClass(!_.isWhitespace, "not whitespace").+

val name = token("test")
val options = (ws ~> token("quick" | "failed" | "new")).*
Expand Down
2 changes: 1 addition & 1 deletion main-command/src/main/scala/sbt/BasicCommands.scala
Expand Up @@ -114,7 +114,7 @@ object BasicCommands {
}

def multiParser(s: State): Parser[List[String]] = {
val nonSemi = token(charClass(_ != ';').+, hide = const(true))
val nonSemi = token(charClass(_ != ';', "not ';'").+, hide = const(true))
val semi = token(';' ~> OptSpace)
val part = semi flatMap (_ =>
matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace))
Expand Down
2 changes: 1 addition & 1 deletion main/src/main/scala/sbt/Extracted.scala
Expand Up @@ -144,7 +144,7 @@ final case class Extracted(structure: BuildStructure,
): State = {
val appendSettings =
Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings)
val newStructure = Load.reapply(sessionSettings ++ appendSettings, structure)
val newStructure = Load.reapply(sessionSettings ++ appendSettings, structure, state.log)
Project.setProject(session, newStructure, state)
}
}
2 changes: 1 addition & 1 deletion main/src/main/scala/sbt/Main.scala
Expand Up @@ -410,7 +410,7 @@ object BuiltinCommands {
val loggerInject = LogManager.settingsLogger(s)
val withLogger = newSession.appendRaw(loggerInject :: Nil)
val show = Project.showContextKey(newSession, structure)
val newStructure = Load.reapply(withLogger.mergeSettings, structure)(show)
val newStructure = Load.reapply(withLogger.mergeSettings, structure, s.log)(show)
Project.setProject(newSession, newStructure, s)
}

Expand Down
2 changes: 1 addition & 1 deletion main/src/main/scala/sbt/PluginCross.scala
Expand Up @@ -50,7 +50,7 @@ private[sbt] object PluginCross {
Seq(scalaVersion := scalaVersionSetting.value)
)
val cleared = session.mergeSettings.filterNot(crossExclude)
val newStructure = Load.reapply(cleared ++ add, structure)
val newStructure = Load.reapply(cleared ++ add, structure, state.log)
Project.setProject(session, newStructure, command :: state)
}
}
Expand Down
2 changes: 1 addition & 1 deletion main/src/main/scala/sbt/internal/IvyConsole.scala
Expand Up @@ -55,7 +55,7 @@ object IvyConsole {
rootProject,
depSettings)

val newStructure = Load.reapply(session.original ++ append, structure)
val newStructure = Load.reapply(session.original ++ append, structure, state.log)
val newState = state.copy(remainingCommands = Exec(Keys.consoleQuick.key.label, None) :: Nil)
Project.setProject(session, newStructure, newState)
}
Expand Down
102 changes: 78 additions & 24 deletions main/src/main/scala/sbt/internal/KeyIndex.scala
Expand Up @@ -35,11 +35,15 @@ object KeyIndex {
} yield {
val data = ids map { id =>
val configs = configurations.getOrElse(id, Seq())
Option(id) -> new ConfigIndex(Map.empty, configs.map(c => (c.name, c.id)).toMap)
val namedConfigs = configs.map { config =>
(config.name, ConfigData(Some(config.id), emptyAKeyIndex))
}.toMap
val inverse = namedConfigs.map((ConfigIndex.invert _).tupled)
Option(id) -> new ConfigIndex(namedConfigs, inverse, emptyAKeyIndex)
}
Option(uri) -> new ProjectIndex(data.toMap)
}
new KeyIndex0(new BuildIndex(data.toMap))
new KeyIndex0(new BuildIndex(data))
}

def combine(indices: Seq[KeyIndex]): KeyIndex = new KeyIndex {
Expand All @@ -55,6 +59,7 @@ object KeyIndex {
case Some(idx) => idx.fromConfigIdent(proj)(configIdent)
case _ => Scope.unguessConfigIdent(configIdent)
}
private[sbt] def guessedConfigIdents = concat(_.guessedConfigIdents)
def tasks(proj: Option[ResolvedReference], conf: Option[String]) = concat(_.tasks(proj, conf))
def tasks(proj: Option[ResolvedReference], conf: Option[String], key: String) =
concat(_.tasks(proj, conf, key))
Expand All @@ -68,7 +73,7 @@ object KeyIndex {
private[sbt] def getOr[A, B](m: Map[A, B], key: A, or: B): B = m.getOrElse(key, or)
private[sbt] def keySet[A, B](m: Map[Option[A], B]): Set[A] = m.keys.flatten.toSet
private[sbt] val emptyAKeyIndex = new AKeyIndex(Relation.empty)
private[sbt] val emptyConfigIndex = new ConfigIndex(Map.empty, Map.empty)
private[sbt] val emptyConfigIndex = new ConfigIndex(Map.empty, Map.empty, emptyAKeyIndex)
private[sbt] val emptyProjectIndex = new ProjectIndex(Map.empty)
private[sbt] val emptyBuildIndex = new BuildIndex(Map.empty)
}
Expand Down Expand Up @@ -97,6 +102,7 @@ trait KeyIndex {
task: Option[AttributeKey[_]]): Set[String]
private[sbt] def configIdents(project: Option[ResolvedReference]): Set[String]
private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String
private[sbt] def guessedConfigIdents: Set[(Option[ProjectReference], String, String)]
}
trait ExtendableKeyIndex extends KeyIndex {
def add(scoped: ScopedKey[_]): ExtendableKeyIndex
Expand All @@ -109,40 +115,67 @@ private[sbt] final class AKeyIndex(val data: Relation[Option[AttributeKey[_]], S
def keys(task: Option[AttributeKey[_]]): Set[String] = data.forward(task)
def allKeys: Set[String] = data._2s.toSet
def tasks: Set[AttributeKey[_]] = data._1s.flatten.toSet
def tasks(key: String): Set[AttributeKey[_]] = data.reverse(key).flatten.toSet
def tasks(key: String): Set[AttributeKey[_]] = data.reverse(key).flatten
}

private[sbt] case class IdentifiableConfig(name: String, ident: Option[String])

private[sbt] case class ConfigData(ident: Option[String], keys: AKeyIndex)

/*
* data contains the mapping between a configuration and keys.
* identData contains the mapping between a configuration and its identifier.
* data contains the mapping between a configuration name and its ident and keys.
* noConfigKeys contains the keys without a configuration.
*/
private[sbt] final class ConfigIndex(val data: Map[Option[String], AKeyIndex],
val identData: Map[String, String]) {
def add(config: Option[String],
private[sbt] final class ConfigIndex(val data: Map[String, ConfigData],
val inverse: Map[String, String],
val noConfigKeys: AKeyIndex) {
def add(config: Option[IdentifiableConfig],
task: Option[AttributeKey[_]],
key: AttributeKey[_]): ConfigIndex = {
new ConfigIndex(data updated (config, keyIndex(config).add(task, key)), this.identData)
config match {
case Some(c) => addKeyWithConfig(c, task, key)
case None => addKeyWithoutConfig(task, key)
}
}

def addKeyWithConfig(config: IdentifiableConfig,
task: Option[AttributeKey[_]],
key: AttributeKey[_]): ConfigIndex = {
val oldConfigData = data.getOrElse(config.name, ConfigData(None, emptyAKeyIndex))
val newConfigData = ConfigData(
ident = oldConfigData.ident.orElse(config.ident),
keys = oldConfigData.keys.add(task, key)
)
val newData = data.updated(config.name, newConfigData)
val newInverse = (inverse.updated _).tupled(ConfigIndex.invert(config.name, newConfigData))
new ConfigIndex(newData, newInverse, noConfigKeys)
}

def keyIndex(conf: Option[String]): AKeyIndex = getOr(data, conf, emptyAKeyIndex)
def configs: Set[String] = keySet(data)
def addKeyWithoutConfig(task: Option[AttributeKey[_]], key: AttributeKey[_]): ConfigIndex = {
new ConfigIndex(data, inverse, noConfigKeys.add(task, key))
}

private[sbt] val configIdentsInverse: Map[String, String] =
identData map { _.swap }
def keyIndex(conf: Option[String]): AKeyIndex = conf match {
case Some(c) => data.get(c).map(_.keys).getOrElse(emptyAKeyIndex)
case None => noConfigKeys
}

private[sbt] lazy val idents: Set[String] =
configs map { config =>
identData.getOrElse(config, Scope.guessConfigIdent(config))
}
def configs: Set[String] = data.keySet

// guess Configuration name from an identifier.
// There's a guessing involved because we could have scoped key that Project is not aware of.
private[sbt] def fromConfigIdent(ident: String): String =
configIdentsInverse.getOrElse(ident, Scope.unguessConfigIdent(ident))
inverse.getOrElse(ident, Scope.unguessConfigIdent(ident))
}
private[sbt] object ConfigIndex {
def invert(name: String, data: ConfigData): (String, String) = data match {
case ConfigData(Some(ident), _) => ident -> name
case ConfigData(None, _) => Scope.guessConfigIdent(name) -> name
}
}
private[sbt] final class ProjectIndex(val data: Map[Option[String], ConfigIndex]) {
def add(id: Option[String],
config: Option[String],
config: Option[IdentifiableConfig],
task: Option[AttributeKey[_]],
key: AttributeKey[_]): ProjectIndex =
new ProjectIndex(data updated (id, confIndex(id).add(config, task, key)))
Expand All @@ -152,7 +185,7 @@ private[sbt] final class ProjectIndex(val data: Map[Option[String], ConfigIndex]
private[sbt] final class BuildIndex(val data: Map[Option[URI], ProjectIndex]) {
def add(build: Option[URI],
project: Option[String],
config: Option[String],
config: Option[IdentifiableConfig],
task: Option[AttributeKey[_]],
key: AttributeKey[_]): BuildIndex =
new BuildIndex(data updated (build, projectIndex(build).add(project, config, task, key)))
Expand All @@ -169,11 +202,30 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn
def configs(project: Option[ResolvedReference]): Set[String] = confIndex(project).configs

private[sbt] def configIdents(project: Option[ResolvedReference]): Set[String] =
confIndex(project).idents
confIndex(project).configs

private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String =
confIndex(proj).fromConfigIdent(configIdent)

private[sbt] def guessedConfigIdents: Set[(Option[ProjectReference], String, String)] = {
val guesses = for {
(build, projIndex) <- data.data
(project, confIndex) <- projIndex.data
(config, data) <- confIndex.data
if data.ident.isEmpty && !Scope.configIdents.contains(config)
} yield (projRef(build, project), config, Scope.guessConfigIdent(config))
guesses.toSet
}

private def projRef(build: Option[URI], project: Option[String]): Option[ProjectReference] = {
(build, project) match {
case (Some(uri), Some(proj)) => Some(ProjectRef(uri, proj))
case (Some(uri), None) => Some(RootProject(uri))
case (None, Some(proj)) => Some(LocalProject(proj))
case (None, None) => None
}
}

def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[_]] =
keyIndex(proj, conf).tasks
def tasks(proj: Option[ResolvedReference],
Expand Down Expand Up @@ -221,6 +273,8 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn
id: Option[String],
config: ScopeAxis[ConfigKey],
task: ScopeAxis[AttributeKey[_]],
key: AttributeKey[_]): ExtendableKeyIndex =
new KeyIndex0(data.add(uri, id, config.toOption.map(_.name), task.toOption, key))
key: AttributeKey[_]): ExtendableKeyIndex = {
val keyConfig = config.toOption.map(c => IdentifiableConfig(c.name, None))
new KeyIndex0(data.add(uri, id, keyConfig, task.toOption, key))
}
}
27 changes: 23 additions & 4 deletions main/src/main/scala/sbt/internal/Load.scala
Expand Up @@ -269,7 +269,7 @@ private[sbt] object Load {
}
Project.checkTargets(data) foreach sys.error
val index = timed("Load.apply: structureIndex", log) {
structureIndex(data, settings, loaded.extra(data), projects)
structureIndex(data, settings, loaded.extra(data), projects, log)
}
val streams = timed("Load.apply: mkStreams", log) { mkStreams(projects, loaded.root, data) }
val bs = new BuildStructure(
Expand Down Expand Up @@ -322,7 +322,8 @@ private[sbt] object Load {
data: Settings[Scope],
settings: Seq[Setting[_]],
extra: KeyIndex => BuildUtil[_],
projects: Map[URI, LoadedBuildUnit]
projects: Map[URI, LoadedBuildUnit],
log: Logger
): StructureIndex = {
val keys = Index.allKeys(settings)
val attributeKeys = Index.attributeKeys(data) ++ keys.map(_.key)
Expand All @@ -331,6 +332,7 @@ private[sbt] object Load {
val configsMap: Map[String, Seq[Configuration]] =
projects.values.flatMap(bu => bu.defined map { case (k, v) => (k, v.configurations) }).toMap
val keyIndex = KeyIndex(scopedKeys.toVector, projectsMap, configsMap)
checkConfigurations(keyIndex, log)
val aggIndex = KeyIndex.aggregate(scopedKeys.toVector, extra(keyIndex), projectsMap, configsMap)
new StructureIndex(
Index.stringToKeyMap(attributeKeys),
Expand All @@ -341,15 +343,32 @@ private[sbt] object Load {
)
}

private def checkConfigurations(keyIndex: KeyIndex, log: Logger): Unit = {
keyIndex.guessedConfigIdents
.collect {
// Filter out any global configurations since we don't have a way of fixing them.
// Chances are this is only going to be the Test configuration which will have guessed correctly.
case (Some(projectRef), config, guess) =>
(Reference.display(projectRef), config, guess)
}
.foreach {
case (project, config, guess) =>
log.warn(
s"""The project $project references an unknown configuration "$config" and was guessed to be "$guess".""")
log.warn("This configuration should be explicitly added to the project.")
}
}

// Reevaluates settings after modifying them. Does not recompile or reload any build components.
def reapply(
newSettings: Seq[Setting[_]],
structure: BuildStructure
structure: BuildStructure,
log: Logger
)(implicit display: Show[ScopedKey[_]]): BuildStructure = {
val transformed = finalTransforms(newSettings)
val newData = Def.make(transformed)(structure.delegates, structure.scopeLocal, display)
def extra(index: KeyIndex) = BuildUtil(structure.root, structure.units, index, newData)
val newIndex = structureIndex(newData, transformed, extra, structure.units)
val newIndex = structureIndex(newData, transformed, extra, structure.units, log)
val newStreams = mkStreams(structure.units, structure.root, newData)
new BuildStructure(
units = structure.units,
Expand Down
2 changes: 1 addition & 1 deletion main/src/main/scala/sbt/internal/Script.scala
Expand Up @@ -60,7 +60,7 @@ object Script {
rootProject,
scriptSettings ++ embeddedSettings)

val newStructure = Load.reapply(session.original ++ append, structure)
val newStructure = Load.reapply(session.original ++ append, structure, state.log)
val arguments = state.remainingCommands.drop(1).map(e => s""""${e.commandLine}"""")
val newState = arguments.mkString("run ", " ", "") :: state.copy(remainingCommands = Nil)
Project.setProject(session, newStructure, newState)
Expand Down

0 comments on commit c632802

Please sign in to comment.