From b8ab638b746fca586109397a02244306f7e19435 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 8 Aug 2014 17:28:25 -0400 Subject: [PATCH] So apparently scalariform was unable to parse these files. --- main/src/main/scala/sbt/Plugins.scala | 472 ++++++++------- main/src/main/scala/sbt/PluginsDebug.scala | 653 ++++++++++----------- 2 files changed, 556 insertions(+), 569 deletions(-) diff --git a/main/src/main/scala/sbt/Plugins.scala b/main/src/main/scala/sbt/Plugins.scala index 6bad8f617..40c394335 100644 --- a/main/src/main/scala/sbt/Plugins.scala +++ b/main/src/main/scala/sbt/Plugins.scala @@ -5,11 +5,11 @@ TODO: - error message when a task doesn't exist that it would be provided by plugin x, enabled by natures y,z, blocked by a, b */ - import logic.{Atom, Clause, Clauses, Formula, Literal, Logic, Negated} - import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException} - import Def.Setting - import Plugins._ - import annotation.tailrec +import logic.{Atom, Clause, Clauses, Formula, Literal, Logic, Negated} +import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException} +import Def.Setting +import Plugins._ +import annotation.tailrec /** An AutoPlugin defines a group of settings and the conditions where the settings are automatically added to a build (called "activation"). @@ -30,7 +30,7 @@ For example, the following will automatically add the settings in `projectSettin override def projectSettings = Seq(...) object autoImport { - lazy val obfuscate = taskKey[Seq[File]]("Obfuscates the source.") + lazy val obfuscate = taskKey[Seq[File]]("Obfuscates the source.") } } @@ -50,243 +50,237 @@ will activate `MyPlugin` defined above and have its settings automatically added then the `MyPlugin` settings (and anything that activates only when `MyPlugin` is activated) will not be added. */ -abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions -{ - /** Determines whether this AutoPlugin will be activated for this project when the `requires` clause is satisfied. - * - * When this method returns `allRequirements`, and `requires` method returns `Web && Javascript`, this plugin - * instance will be added automatically if the `Web` and `Javascript` plugins are enabled. - * - * When this method returns `noTrigger`, and `requires` method returns `Web && Javascript`, this plugin - * instance will be added only if the build user enables it, but it will automatically add both `Web` and `Javascript`. */ - def trigger: PluginTrigger = noTrigger +abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions { + /** Determines whether this AutoPlugin will be activated for this project when the `requires` clause is satisfied. + * + * When this method returns `allRequirements`, and `requires` method returns `Web && Javascript`, this plugin + * instance will be added automatically if the `Web` and `Javascript` plugins are enabled. + * + * When this method returns `noTrigger`, and `requires` method returns `Web && Javascript`, this plugin + * instance will be added only if the build user enables it, but it will automatically add both `Web` and `Javascript`. */ + def trigger: PluginTrigger = noTrigger - /** This AutoPlugin requires the plugins the [[Plugins]] matcher returned by this method. See [[trigger]]. - */ - def requires: Plugins = empty + /** This AutoPlugin requires the plugins the [[Plugins]] matcher returned by this method. See [[trigger]]. + */ + def requires: Plugins = empty - val label: String = getClass.getName.stripSuffix("$") + val label: String = getClass.getName.stripSuffix("$") - override def toString: String = label + override def toString: String = label - /** The [[Configuration]]s to add to each project that activates this AutoPlugin.*/ - def projectConfigurations: Seq[Configuration] = Nil + /** The [[Configuration]]s to add to each project that activates this AutoPlugin.*/ + def projectConfigurations: Seq[Configuration] = Nil - /** The [[Setting]]s to add in the scope of each project that activates this AutoPlugin. */ - def projectSettings: Seq[Setting[_]] = Nil + /** The [[Setting]]s to add in the scope of each project that activates this AutoPlugin. */ + def projectSettings: Seq[Setting[_]] = Nil - /** The [[Setting]]s to add to the build scope for each project that activates this AutoPlugin. - * The settings returned here are guaranteed to be added to a given build scope only once - * regardless of how many projects for that build activate this AutoPlugin. */ - def buildSettings: Seq[Setting[_]] = Nil + /** The [[Setting]]s to add to the build scope for each project that activates this AutoPlugin. + * The settings returned here are guaranteed to be added to a given build scope only once + * regardless of how many projects for that build activate this AutoPlugin. */ + def buildSettings: Seq[Setting[_]] = Nil - /** The [[Setting]]s to add to the global scope exactly once if any project activates this AutoPlugin. */ - def globalSettings: Seq[Setting[_]] = Nil + /** The [[Setting]]s to add to the global scope exactly once if any project activates this AutoPlugin. */ + def globalSettings: Seq[Setting[_]] = Nil - // TODO?: def commands: Seq[Command] + // TODO?: def commands: Seq[Command] - private[sbt] def unary_! : Exclude = Exclude(this) + private[sbt] def unary_! : Exclude = Exclude(this) - /** If this plugin does not have any requirements, it means it is actually a root plugin. */ - private[sbt] final def isRoot: Boolean = - requires match { - case Empty => true - case _ => false - } + /** If this plugin does not have any requirements, it means it is actually a root plugin. */ + private[sbt] final def isRoot: Boolean = + requires match { + case Empty => true + case _ => false + } - /** If this plugin does not have any requirements, it means it is actually a root plugin. */ - private[sbt] final def isAlwaysEnabled: Boolean = - isRoot && (trigger == AllRequirements) + /** If this plugin does not have any requirements, it means it is actually a root plugin. */ + private[sbt] final def isAlwaysEnabled: Boolean = + isRoot && (trigger == AllRequirements) } /** An error that occurs when auto-plugins aren't configured properly. * It translates the error from the underlying logic system to be targeted at end users. */ -final class AutoPluginException private(val message: String, val origin: Option[LogicException]) extends RuntimeException(message) -{ - /** Prepends `p` to the error message derived from `origin`. */ - def withPrefix(p: String) = new AutoPluginException(p + message, origin) +final class AutoPluginException private(val message: String, val origin: Option[LogicException]) extends RuntimeException(message) { + /** Prepends `p` to the error message derived from `origin`. */ + def withPrefix(p: String) = new AutoPluginException(p + message, origin) } -object AutoPluginException -{ - def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None) - def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Plugins.translateMessage(origin), Some(origin)) +object AutoPluginException { + def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None) + def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Plugins.translateMessage(origin), Some(origin)) } sealed trait PluginTrigger case object AllRequirements extends PluginTrigger -case object NoTrigger extends PluginTrigger +case object NoTrigger extends PluginTrigger /** An expression that matches `AutoPlugin`s. */ sealed trait Plugins { - def && (o: Basic): Plugins + def && (o: Basic): Plugins } -sealed trait PluginsFunctions -{ - /** [[Plugins]] instance that doesn't require any [[Plugins]]s. */ - def empty: Plugins = Plugins.Empty +sealed trait PluginsFunctions { + /** [[Plugins]] instance that doesn't require any [[Plugins]]s. */ + def empty: Plugins = Plugins.Empty - /** This plugin is activated when all required plugins are present. */ - def allRequirements: PluginTrigger = AllRequirements - /** This plugin is activated only when it is manually activated. */ - def noTrigger: PluginTrigger = NoTrigger + /** This plugin is activated when all required plugins are present. */ + def allRequirements: PluginTrigger = AllRequirements + /** This plugin is activated only when it is manually activated. */ + def noTrigger: PluginTrigger = NoTrigger } -object Plugins extends PluginsFunctions -{ - /** Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[AutoPlugin]]s. - * The [[AutoPlugin]]s are topologically sorted so that a required [[AutoPlugin]] comes before its requiring [[AutoPlugin]].*/ - def deducer(defined0: List[AutoPlugin]): (Plugins, Logger) => Seq[AutoPlugin] = - if(defined0.isEmpty) (_, _) => Nil - else - { - // TODO: defined should return all the plugins - val allReqs = (defined0 flatMap { asRequirements }).toSet - val diff = allReqs diff defined0.toSet - val defined = if (!diff.isEmpty) diff.toList ::: defined0 - else defined0 +object Plugins extends PluginsFunctions { + /** Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[AutoPlugin]]s. + * The [[AutoPlugin]]s are topologically sorted so that a required [[AutoPlugin]] comes before its requiring [[AutoPlugin]].*/ + def deducer(defined0: List[AutoPlugin]): (Plugins, Logger) => Seq[AutoPlugin] = + if(defined0.isEmpty) (_, _) => Nil + else { + // TODO: defined should return all the plugins + val allReqs = (defined0 flatMap { asRequirements }).toSet + val diff = allReqs diff defined0.toSet + val defined = if (!diff.isEmpty) diff.toList ::: defined0 + else defined0 - val byAtom = defined map { x => (Atom(x.label), x) } - val byAtomMap = byAtom.toMap - if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom) - // Ignore clauses for plugins that does not require anything else. - // Avoids the requirement for pure Nature strings *and* possible - // circular dependencies in the logic. - val allRequirementsClause = defined.filterNot(_.isRoot).flatMap(d => asRequirementsClauses(d)) - val allEnabledByClause = defined.filterNot(_.isRoot).flatMap(d => asEnabledByClauses(d)) + val byAtom = defined map { x => (Atom(x.label), x) } + val byAtomMap = byAtom.toMap + if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom) + // Ignore clauses for plugins that does not require anything else. + // Avoids the requirement for pure Nature strings *and* possible + // circular dependencies in the logic. + val allRequirementsClause = defined.filterNot(_.isRoot).flatMap(d => asRequirementsClauses(d)) + val allEnabledByClause = defined.filterNot(_.isRoot).flatMap(d => asEnabledByClauses(d)) // Note: Here is where the function begins. We're given a list of plugins now. - (requestedPlugins, log) => { + (requestedPlugins, log) => { def explicitlyDisabled(p: AutoPlugin): Boolean = hasExclude(requestedPlugins, p) - val alwaysEnabled: List[AutoPlugin] = defined.filter(_.isAlwaysEnabled).filterNot(explicitlyDisabled) + val alwaysEnabled: List[AutoPlugin] = defined.filter(_.isAlwaysEnabled).filterNot(explicitlyDisabled) System.err.println(s"Always Enabled Plugins = ${alwaysEnabled.mkString(", ")}") System.err.println(s"Requested = $requestedPlugins") - val knowlege0: Set[Atom] = ((flatten(requestedPlugins) ++ alwaysEnabled) collect { - case x: AutoPlugin => Atom(x.label) - }).toSet - val clauses = Clauses((allRequirementsClause ::: allEnabledByClause) filterNot { _.head subsetOf knowlege0 }) - log.debug(s"deducing auto plugins based on known facts ${knowlege0.toString} and clauses ${clauses.toString}") - Logic.reduce(clauses, (flattenConvert(requestedPlugins) ++ convertAll(alwaysEnabled)).toSet) match { - case Left(problem) => throw AutoPluginException(problem) - case Right(results) => - log.debug(s" :: deduced result: ${results}") - val selectedAtoms: List[Atom] = results.ordered - val selectedPlugins = selectedAtoms map { a => - byAtomMap.getOrElse(a, throw AutoPluginException(s"${a} was not found in atom map.")) - } - val forbidden: Set[AutoPlugin] = (selectedPlugins flatMap { Plugins.asExclusions }).toSet - val c = selectedPlugins.toSet & forbidden - if (!c.isEmpty) { - exlusionConflictError(requestedPlugins, selectedPlugins, c.toSeq sortBy {_.label}) - } - val retval = topologicalSort(selectedPlugins, log) - log.debug(s" :: sorted deduced result: ${retval.toString}") - retval - } - } - } - private[sbt] def topologicalSort(ns: List[AutoPlugin], log: Logger): List[AutoPlugin] = { - log.debug(s"sorting: ns: ${ns.toString}") - @tailrec def doSort(found0: List[AutoPlugin], notFound0: List[AutoPlugin], limit0: Int): List[AutoPlugin] = { - log.debug(s" :: sorting:: found: ${found0.toString} not found ${notFound0.toString}") - if (limit0 < 0) throw AutoPluginException(s"Failed to sort ${ns} topologically") - else if (notFound0.isEmpty) found0 - else { - val (found1, notFound1) = notFound0 partition { n => asRequirements(n).toSet subsetOf found0.toSet } - doSort(found0 ::: found1, notFound1, limit0 - 1) - } - } - val (roots, nonRoots) = ns partition (_.isRoot) - doSort(roots, nonRoots, ns.size * ns.size + 1) - } - private[sbt] def translateMessage(e: LogicException) = e match { - case ic: InitialContradictions => s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString(ic.literals.toSeq)}" - case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required plugins are satisifed. The directly selected plugins were: ${literalsString(io.literals.toSeq)}" - case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}" - } - private[this] def literalsString(lits: Seq[Literal]): String = - lits map { case Atom(l) => l; case Negated(Atom(l)) => l } mkString(", ") + val knowlege0: Set[Atom] = ((flatten(requestedPlugins) ++ alwaysEnabled) collect { + case x: AutoPlugin => Atom(x.label) + }).toSet + val clauses = Clauses((allRequirementsClause ::: allEnabledByClause) filterNot { _.head subsetOf knowlege0 }) + log.debug(s"deducing auto plugins based on known facts ${knowlege0.toString} and clauses ${clauses.toString}") + Logic.reduce(clauses, (flattenConvert(requestedPlugins) ++ convertAll(alwaysEnabled)).toSet) match { + case Left(problem) => throw AutoPluginException(problem) + case Right(results) => + log.debug(s" :: deduced result: ${results}") + val selectedAtoms: List[Atom] = results.ordered + val selectedPlugins = selectedAtoms map { a => + byAtomMap.getOrElse(a, throw AutoPluginException(s"${a} was not found in atom map.")) + } + val forbidden: Set[AutoPlugin] = (selectedPlugins flatMap { Plugins.asExclusions }).toSet + val c = selectedPlugins.toSet & forbidden + if (!c.isEmpty) { + exlusionConflictError(requestedPlugins, selectedPlugins, c.toSeq sortBy {_.label}) + } + val retval = topologicalSort(selectedPlugins, log) + log.debug(s" :: sorted deduced result: ${retval.toString}") + retval + } + } + } + private[sbt] def topologicalSort(ns: List[AutoPlugin], log: Logger): List[AutoPlugin] = { + log.debug(s"sorting: ns: ${ns.toString}") + @tailrec def doSort(found0: List[AutoPlugin], notFound0: List[AutoPlugin], limit0: Int): List[AutoPlugin] = { + log.debug(s" :: sorting:: found: ${found0.toString} not found ${notFound0.toString}") + if (limit0 < 0) throw AutoPluginException(s"Failed to sort ${ns} topologically") + else if (notFound0.isEmpty) found0 + else { + val (found1, notFound1) = notFound0 partition { n => asRequirements(n).toSet subsetOf found0.toSet } + doSort(found0 ::: found1, notFound1, limit0 - 1) + } + } + val (roots, nonRoots) = ns partition (_.isRoot) + doSort(roots, nonRoots, ns.size * ns.size + 1) + } + private[sbt] def translateMessage(e: LogicException) = e match { + case ic: InitialContradictions => s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString(ic.literals.toSeq)}" + case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required plugins are satisifed. The directly selected plugins were: ${literalsString(io.literals.toSeq)}" + case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}" + } + private[this] def literalsString(lits: Seq[Literal]): String = + lits map { case Atom(l) => l; case Negated(Atom(l)) => l } mkString(", ") - private[this] def duplicateProvidesError(byAtom: Seq[(Atom, AutoPlugin)]) { - val dupsByAtom = byAtom.groupBy(_._1).mapValues(_.map(_._2)) - val dupStrings = for( (atom, dups) <- dupsByAtom if dups.size > 1 ) yield - s"${atom.label} by ${dups.mkString(", ")}" - val (ns, nl) = if(dupStrings.size > 1) ("s", "\n\t") else ("", " ") - val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}" - throw AutoPluginException(message) - } - private[this] def exlusionConflictError(requested: Plugins, selected: Seq[AutoPlugin], conflicting: Seq[AutoPlugin]) { - def listConflicts(ns: Seq[AutoPlugin]) = (ns map { c => - val reasons = (if (flatten(requested) contains c) List("requested") - else Nil) ++ - (if (c.requires != empty && c.trigger == allRequirements) List(s"enabled by ${c.requires.toString}") - else Nil) ++ - { - val reqs = selected filter { x => asRequirements(x) contains c } - if (!reqs.isEmpty) List(s"""required by ${reqs.mkString(", ")}""") - else Nil - } ++ - { - val exs = selected filter { x => asExclusions(x) contains c } - if (!exs.isEmpty) List(s"""excluded by ${exs.mkString(", ")}""") - else Nil - } - s""" - conflict: ${c.label} is ${reasons.mkString("; ")}""" - }).mkString("\n") - throw AutoPluginException(s"""Contradiction in enabled plugins: + private[this] def duplicateProvidesError(byAtom: Seq[(Atom, AutoPlugin)]) { + val dupsByAtom = byAtom.groupBy(_._1).mapValues(_.map(_._2)) + val dupStrings = for( (atom, dups) <- dupsByAtom if dups.size > 1 ) yield + s"${atom.label} by ${dups.mkString(", ")}" + val (ns, nl) = if(dupStrings.size > 1) ("s", "\n\t") else ("", " ") + val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}" + throw AutoPluginException(message) + } + private[this] def exlusionConflictError(requested: Plugins, selected: Seq[AutoPlugin], conflicting: Seq[AutoPlugin]) { + def listConflicts(ns: Seq[AutoPlugin]) = (ns map { c => + val reasons = (if (flatten(requested) contains c) List("requested") + else Nil) ++ + (if (c.requires != empty && c.trigger == allRequirements) List(s"enabled by ${c.requires.toString}") + else Nil) ++ + { + val reqs = selected filter { x => asRequirements(x) contains c } + if (!reqs.isEmpty) List(s"""required by ${reqs.mkString(", ")}""") + else Nil + } ++ + { + val exs = selected filter { x => asExclusions(x) contains c } + if (!exs.isEmpty) List(s"""excluded by ${exs.mkString(", ")}""") + else Nil + } + s""" - conflict: ${c.label} is ${reasons.mkString("; ")}""" + }).mkString("\n") + throw AutoPluginException(s"""Contradiction in enabled plugins: - requested: ${requested.toString} - enabled: ${selected.mkString(", ")} ${listConflicts(conflicting)}""") - } + } - private[sbt] final object Empty extends Plugins { - def &&(o: Basic): Plugins = o - override def toString = "" - } + private[sbt] final object Empty extends Plugins { + def &&(o: Basic): Plugins = o + override def toString = "" + } - /** An included or excluded Nature/Plugin. TODO: better name than Basic. Also, can we dump - * this class. - */ - sealed abstract class Basic extends Plugins { - def &&(o: Basic): Plugins = And(this :: o :: Nil) - } - private[sbt] final case class Exclude(n: AutoPlugin) extends Basic { - override def toString = s"!$n" - } - private[sbt] final case class And(plugins: List[Basic]) extends Plugins { - def &&(o: Basic): Plugins = And(o :: plugins) - override def toString = plugins.mkString(" && ") - } - private[sbt] def and(a: Plugins, b: Plugins) = b match { - case Empty => a - case And(ns) => (a /: ns)(_ && _) - case b: Basic => a && b - } - private[sbt] def remove(a: Plugins, del: Set[Basic]): Plugins = a match { - case b: Basic => if(del(b)) Empty else b - case Empty => Empty - case And(ns) => - val removed = ns.filterNot(del) - if(removed.isEmpty) Empty else And(removed) - } + /** An included or excluded Nature/Plugin. TODO: better name than Basic. Also, can we dump + * this class. + */ + sealed abstract class Basic extends Plugins { + def &&(o: Basic): Plugins = And(this :: o :: Nil) + } + private[sbt] final case class Exclude(n: AutoPlugin) extends Basic { + override def toString = s"!$n" + } + private[sbt] final case class And(plugins: List[Basic]) extends Plugins { + def &&(o: Basic): Plugins = And(o :: plugins) + override def toString = plugins.mkString(" && ") + } + private[sbt] def and(a: Plugins, b: Plugins) = b match { + case Empty => a + case And(ns) => (a /: ns)(_ && _) + case b: Basic => a && b + } + private[sbt] def remove(a: Plugins, del: Set[Basic]): Plugins = a match { + case b: Basic => if(del(b)) Empty else b + case Empty => Empty + case And(ns) => + val removed = ns.filterNot(del) + if(removed.isEmpty) Empty else And(removed) + } - /** Defines enabled-by clauses for `ap`. */ - private[sbt] def asEnabledByClauses(ap: AutoPlugin): List[Clause] = - // `ap` is the head and the required plugins for `ap` is the body. - if (ap.trigger == AllRequirements) Clause( convert(ap.requires), Set(Atom(ap.label)) ) :: Nil - else Nil - /** Defines requirements clauses for `ap`. */ - private[sbt] def asRequirementsClauses(ap: AutoPlugin): List[Clause] = - // required plugin is the head and `ap` is the body. - asRequirements(ap) map { x => Clause( convert(ap), Set(Atom(x.label)) ) } - private[sbt] def asRequirements(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect { - case x: AutoPlugin => x - } - private[sbt] def asExclusions(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect { - case Exclude(x) => x - } + /** Defines enabled-by clauses for `ap`. */ + private[sbt] def asEnabledByClauses(ap: AutoPlugin): List[Clause] = + // `ap` is the head and the required plugins for `ap` is the body. + if (ap.trigger == AllRequirements) Clause( convert(ap.requires), Set(Atom(ap.label)) ) :: Nil + else Nil + /** Defines requirements clauses for `ap`. */ + private[sbt] def asRequirementsClauses(ap: AutoPlugin): List[Clause] = + // required plugin is the head and `ap` is the body. + asRequirements(ap) map { x => Clause( convert(ap), Set(Atom(x.label)) ) } + private[sbt] def asRequirements(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect { + case x: AutoPlugin => x + } + private[sbt] def asExclusions(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect { + case Exclude(x) => x + } // TODO - This doesn't handle nested AND boolean logic... private[sbt] def hasExclude(n: Plugins, p: AutoPlugin): Boolean = n match { case `p` => false @@ -306,46 +300,46 @@ ${listConflicts(conflicting)}""") case b: Basic => false case Empty => false } - private[this] def flattenConvert(n: Plugins): Seq[Literal] = n match { - case And(ns) => convertAll(ns) - case b: Basic => convertBasic(b) :: Nil - case Empty => Nil - } - private[sbt] def flatten(n: Plugins): Seq[Basic] = n match { - case And(ns) => ns - case b: Basic => b :: Nil - case Empty => Nil - } + private[this] def flattenConvert(n: Plugins): Seq[Literal] = n match { + case And(ns) => convertAll(ns) + case b: Basic => convertBasic(b) :: Nil + case Empty => Nil + } + private[sbt] def flatten(n: Plugins): Seq[Basic] = n match { + case And(ns) => ns + case b: Basic => b :: Nil + case Empty => Nil + } - private[this] def convert(n: Plugins): Formula = n match { - case And(ns) => convertAll(ns).reduce[Formula](_ && _) - case b: Basic => convertBasic(b) - case Empty => Formula.True - } - private[this] def convertBasic(b: Basic): Literal = b match { - case Exclude(n) => !convertBasic(n) - case a: AutoPlugin => Atom(a.label) - } - private[this] def convertAll(ns: Seq[Basic]): Seq[Literal] = ns map convertBasic + private[this] def convert(n: Plugins): Formula = n match { + case And(ns) => convertAll(ns).reduce[Formula](_ && _) + case b: Basic => convertBasic(b) + case Empty => Formula.True + } + private[this] def convertBasic(b: Basic): Literal = b match { + case Exclude(n) => !convertBasic(n) + case a: AutoPlugin => Atom(a.label) + } + private[this] def convertAll(ns: Seq[Basic]): Seq[Literal] = ns map convertBasic - /** True if the trigger clause `n` is satisifed by `model`. */ - def satisfied(n: Plugins, model: Set[AutoPlugin]): Boolean = - flatten(n) forall { - case Exclude(a) => !model(a) - case ap: AutoPlugin => model(ap) - } + /** True if the trigger clause `n` is satisifed by `model`. */ + def satisfied(n: Plugins, model: Set[AutoPlugin]): Boolean = + flatten(n) forall { + case Exclude(a) => !model(a) + case ap: AutoPlugin => model(ap) + } - private[sbt] def hasAutoImportGetter(ap: AutoPlugin, loader: ClassLoader): Boolean = { - import reflect.runtime.{universe => ru} - import util.control.Exception.catching - val m = ru.runtimeMirror(loader) - val im = m.reflect(ap) - val hasGetterOpt = catching(classOf[ScalaReflectionException]) opt { - im.symbol.asType.toType.declaration(ru.newTermName("autoImport")) match { - case ru.NoSymbol => false - case sym => sym.asTerm.isGetter || sym.asTerm.isModule - } - } - hasGetterOpt getOrElse false - } + private[sbt] def hasAutoImportGetter(ap: AutoPlugin, loader: ClassLoader): Boolean = { + import reflect.runtime.{universe => ru} + import util.control.Exception.catching + val m = ru.runtimeMirror(loader) + val im = m.reflect(ap) + val hasGetterOpt = catching(classOf[ScalaReflectionException]) opt { + im.symbol.asType.toType.declaration(ru.newTermName("autoImport")) match { + case ru.NoSymbol => false + case sym => sym.asTerm.isGetter || sym.asTerm.isModule + } + } + hasGetterOpt getOrElse false + } } diff --git a/main/src/main/scala/sbt/PluginsDebug.scala b/main/src/main/scala/sbt/PluginsDebug.scala index bace7b52b..d15a238a8 100644 --- a/main/src/main/scala/sbt/PluginsDebug.scala +++ b/main/src/main/scala/sbt/PluginsDebug.scala @@ -1,382 +1,375 @@ package sbt - import Def.Setting - import Plugins._ - import PluginsDebug._ - import java.net.URI + import Def.Setting + import Plugins._ + import PluginsDebug._ + import java.net.URI -private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey: Map[String, AttributeKey[_]], val provided: Relation[AutoPlugin, AttributeKey[_]]) -{ - /** The set of [[AutoPlugin]]s that might define a key named `keyName`. - * Because plugins can define keys in different scopes, this should only be used as a guideline. */ - def providers(keyName: String): Set[AutoPlugin] = nameToKey.get(keyName) match { - case None => Set.empty - case Some(key) => provided.reverse(key) - } - /** Describes alternative approaches for defining key [[keyName]] in [[context]].*/ - def toEnable(keyName: String, context: Context): List[PluginEnable] = - providers(keyName).toList.map(plugin => pluginEnable(context, plugin)) +private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey: Map[String, AttributeKey[_]], val provided: Relation[AutoPlugin, AttributeKey[_]]) { + /** The set of [[AutoPlugin]]s that might define a key named `keyName`. + * Because plugins can define keys in different scopes, this should only be used as a guideline. */ + def providers(keyName: String): Set[AutoPlugin] = nameToKey.get(keyName) match { + case None => Set.empty + case Some(key) => provided.reverse(key) + } + /** Describes alternative approaches for defining key [[keyName]] in [[context]].*/ + def toEnable(keyName: String, context: Context): List[PluginEnable] = + providers(keyName).toList.map(plugin => pluginEnable(context, plugin)) - /** Provides text to suggest how [[notFoundKey]] can be defined in [[context]]. */ - def debug(notFoundKey: String, context: Context): String = - { - val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) { - case pa: PluginActivated => Left(pa) - case pd: EnableDeactivated => Right(pd) - } - val activePrefix = if(activated.nonEmpty) s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n" else "" - activePrefix + debugDeactivated(notFoundKey, deactivated) - } - private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String = - { - val (impossible, possible) = Util.separate(deactivated) { - case pi: PluginImpossible => Left(pi) - case pr: PluginRequirements => Right(pr) - } - if(possible.nonEmpty) { - val explained = possible.map(explainPluginEnable) - val possibleString = - if(explained.size > 1) explained.zipWithIndex.map{case (s,i) => s"$i. $s"}.mkString("Multiple plugins are available that can provide $notFoundKey:\n", "\n", "") - else s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}" - def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ") - val imPostfix = if(impossible.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are impossible to add: $impossiblePlugins" - possibleString + imPostfix - } - else if(impossible.isEmpty) - s"No available plugin provides key $notFoundKey." - else { - val explanations = impossible.map(explainPluginEnable) - explanations.mkString(s"Plugins are available that could provide $notFoundKey, but they are impossible to add:\n\t", "\n\t", "") - } - } + /** Provides text to suggest how [[notFoundKey]] can be defined in [[context]]. */ + def debug(notFoundKey: String, context: Context): String = + { + val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) { + case pa: PluginActivated => Left(pa) + case pd: EnableDeactivated => Right(pd) + } + val activePrefix = if(activated.nonEmpty) s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n" else "" + activePrefix + debugDeactivated(notFoundKey, deactivated) + } + private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String = + { + val (impossible, possible) = Util.separate(deactivated) { + case pi: PluginImpossible => Left(pi) + case pr: PluginRequirements => Right(pr) + } + if(possible.nonEmpty) { + val explained = possible.map(explainPluginEnable) + val possibleString = + if(explained.size > 1) explained.zipWithIndex.map{case (s,i) => s"$i. $s"}.mkString("Multiple plugins are available that can provide $notFoundKey:\n", "\n", "") + else s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}" + def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ") + val imPostfix = if(impossible.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are impossible to add: $impossiblePlugins" + possibleString + imPostfix + } + else if(impossible.isEmpty) + s"No available plugin provides key $notFoundKey." + else { + val explanations = impossible.map(explainPluginEnable) + explanations.mkString(s"Plugins are available that could provide $notFoundKey, but they are impossible to add:\n\t", "\n\t", "") + } + } - /** Text that suggests how to activate [[plugin]] in [[context]] if possible and if it is not already activated.*/ - def help(plugin: AutoPlugin, context: Context): String = - if(context.enabled.contains(plugin)) - activatedHelp(plugin) - else - deactivatedHelp(plugin, context) - private def activatedHelp(plugin: AutoPlugin): String = - { - val prefix = s"${plugin.label} is activated." - val keys = provided.forward(plugin) - val keysString = if(keys.isEmpty) "" else s"\nIt may affect these keys: ${multi(keys.toList.map(_.label))}" - val configs = plugin.projectConfigurations - val confsString = if(configs.isEmpty) "" else s"\nIt defines these configurations: ${multi(configs.map(_.name))}" - prefix + keysString + confsString - } - private def deactivatedHelp(plugin: AutoPlugin, context: Context): String = - { - val prefix = s"${plugin.label} is NOT activated." - val keys = provided.forward(plugin) - val keysString = if(keys.isEmpty) "" else s"\nActivating it may affect these keys: ${multi(keys.toList.map(_.label))}" - val configs = plugin.projectConfigurations - val confsString = if(configs.isEmpty) "" else s"\nActivating it will define these configurations: ${multi(configs.map(_.name))}" - val toActivate = explainPluginEnable(pluginEnable(context, plugin)) - s"$prefix$keysString$confsString\n$toActivate" - } + /** Text that suggests how to activate [[plugin]] in [[context]] if possible and if it is not already activated.*/ + def help(plugin: AutoPlugin, context: Context): String = + if (context.enabled.contains(plugin)) activatedHelp(plugin) + else deactivatedHelp(plugin, context) + private def activatedHelp(plugin: AutoPlugin): String = + { + val prefix = s"${plugin.label} is activated." + val keys = provided.forward(plugin) + val keysString = if(keys.isEmpty) "" else s"\nIt may affect these keys: ${multi(keys.toList.map(_.label))}" + val configs = plugin.projectConfigurations + val confsString = if(configs.isEmpty) "" else s"\nIt defines these configurations: ${multi(configs.map(_.name))}" + prefix + keysString + confsString + } + private def deactivatedHelp(plugin: AutoPlugin, context: Context): String = + { + val prefix = s"${plugin.label} is NOT activated." + val keys = provided.forward(plugin) + val keysString = if(keys.isEmpty) "" else s"\nActivating it may affect these keys: ${multi(keys.toList.map(_.label))}" + val configs = plugin.projectConfigurations + val confsString = if(configs.isEmpty) "" else s"\nActivating it will define these configurations: ${multi(configs.map(_.name))}" + val toActivate = explainPluginEnable(pluginEnable(context, plugin)) + s"$prefix$keysString$confsString\n$toActivate" + } - private[this] def multi(strs: Seq[String]): String = strs.mkString(if(strs.size > 4) "\n\t" else ", ") + private[this] def multi(strs: Seq[String]): String = strs.mkString(if(strs.size > 4) "\n\t" else ", ") } -private[sbt] object PluginsDebug -{ - def helpAll(s: State): String = - if(Project.isProjectLoaded(s)) - { - val extracted = Project.extract(s) - import extracted._ - 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) - val actString = if(activatedIn.nonEmpty) activatedIn.mkString(": enabled in ", ", ", "") else "" // TODO: deal with large builds - s"\n\t${plugin.label}$actString" - } - s"In $uri${pluginStrings.mkString}" - } - val buildStrings = for((uri, build) <- structure.units) yield helpBuild(uri, build) - buildStrings.mkString("\n") - } - else - "No project is currently loaded." +private[sbt] object PluginsDebug { + def helpAll(s: State): String = + if(Project.isProjectLoaded(s)) + { + val extracted = Project.extract(s) + import extracted._ + 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) + val actString = if(activatedIn.nonEmpty) activatedIn.mkString(": enabled in ", ", ", "") else "" // TODO: deal with large builds + s"\n\t${plugin.label}$actString" + } + s"In $uri${pluginStrings.mkString}" + } + val buildStrings = for((uri, build) <- structure.units) yield helpBuild(uri, build) + buildStrings.mkString("\n") + } + else "No project is currently loaded." - def autoPluginMap(s: State): Map[String, AutoPlugin] = - { - val extracted = Project.extract(s) - import extracted._ - structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap - } - private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] = - build.unit.plugins.detected.autoPlugins map {_.value} + def autoPluginMap(s: State): Map[String, AutoPlugin] = + { + val extracted = Project.extract(s) + import extracted._ + structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap + } + private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] = + build.unit.plugins.detected.autoPlugins map {_.value} - def help(plugin: AutoPlugin, s: State): String = - { - val extracted = Project.extract(s) - import extracted._ - def definesPlugin(p: ResolvedProject): Boolean = p.autoPlugins.contains(plugin) - def projectForRef(ref: ProjectRef): ResolvedProject = get(Keys.thisProject in ref) - val perBuild: Map[URI, Set[AutoPlugin]] = structure.units.mapValues(unit => availableAutoPlugins(unit).toSet) - val pluginsThisBuild = perBuild.getOrElse(currentRef.build, Set.empty).toList - lazy val context = Context(currentProject.plugins, currentProject.autoPlugins, Plugins.deducer(pluginsThisBuild), pluginsThisBuild, s.log) - lazy val debug = PluginsDebug(context.available) - if(!pluginsThisBuild.contains(plugin)) { - val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1) - s"Plugin ${plugin.label} is only available in builds:\n\t${availableInBuilds.mkString("\n\t")}\nSwitch to a project in one of those builds using `project` and rerun this command for more information." - } else if(definesPlugin(currentProject)) - debug.activatedHelp(plugin) - else { - val thisAggregated = BuildUtil.dependencies(structure.units).aggregateTransitive.getOrElse(currentRef, Nil) - val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref))) - if(definedInAggregated.nonEmpty) { - val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified - s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t${projectNames.mkString("\n\t")}" - } else { - val base = debug.deactivatedHelp(plugin, context) - val aggNote = if(thisAggregated.nonEmpty) "Note: This project aggregates other projects and this" else "Note: This" - val common = " information is for this project only." - val helpOther = "To see how to activate this plugin for another project, change to the project using `project ` and rerun this command." - s"$base\n$aggNote$common\n$helpOther" - } - } - } + def help(plugin: AutoPlugin, s: State): String = + { + val extracted = Project.extract(s) + import extracted._ + def definesPlugin(p: ResolvedProject): Boolean = p.autoPlugins.contains(plugin) + def projectForRef(ref: ProjectRef): ResolvedProject = get(Keys.thisProject in ref) + val perBuild: Map[URI, Set[AutoPlugin]] = structure.units.mapValues(unit => availableAutoPlugins(unit).toSet) + val pluginsThisBuild = perBuild.getOrElse(currentRef.build, Set.empty).toList + lazy val context = Context(currentProject.plugins, currentProject.autoPlugins, Plugins.deducer(pluginsThisBuild), pluginsThisBuild, s.log) + lazy val debug = PluginsDebug(context.available) + if(!pluginsThisBuild.contains(plugin)) { + val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1) + s"Plugin ${plugin.label} is only available in builds:\n\t${availableInBuilds.mkString("\n\t")}\nSwitch to a project in one of those builds using `project` and rerun this command for more information." + } else if(definesPlugin(currentProject)) + debug.activatedHelp(plugin) + else { + val thisAggregated = BuildUtil.dependencies(structure.units).aggregateTransitive.getOrElse(currentRef, Nil) + val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref))) + if(definedInAggregated.nonEmpty) { + val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified + s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t${projectNames.mkString("\n\t")}" + } else { + val base = debug.deactivatedHelp(plugin, context) + val aggNote = if(thisAggregated.nonEmpty) "Note: This project aggregates other projects and this" else "Note: This" + val common = " information is for this project only." + val helpOther = "To see how to activate this plugin for another project, change to the project using `project ` and rerun this command." + s"$base\n$aggNote$common\n$helpOther" + } + } + } - /** Precomputes information for debugging plugins. */ - def apply(available: List[AutoPlugin]): PluginsDebug = - { - val keyR = definedKeys(available) - val nameToKey: Map[String, AttributeKey[_]] = keyR._2s.toList.map(key => (key.label, key)).toMap - new PluginsDebug(available, nameToKey, keyR) - } + /** Precomputes information for debugging plugins. */ + def apply(available: List[AutoPlugin]): PluginsDebug = + { + val keyR = definedKeys(available) + val nameToKey: Map[String, AttributeKey[_]] = keyR._2s.toList.map(key => (key.label, key)).toMap + new PluginsDebug(available, nameToKey, keyR) + } - /** The context for debugging a plugin (de)activation. - * @param initial The initially defined [[AutoPlugin]]s. - * @param enabled The resulting model. - * @param deducePlugin The function used to compute the model. - * @param available All [[AutoPlugin]]s available for consideration. */ - final case class Context(initial: Plugins, enabled: Seq[AutoPlugin], deducePlugin: (Plugins, Logger) => Seq[AutoPlugin], available: List[AutoPlugin], log: Logger) + /** The context for debugging a plugin (de)activation. + * @param initial The initially defined [[AutoPlugin]]s. + * @param enabled The resulting model. + * @param deducePlugin The function used to compute the model. + * @param available All [[AutoPlugin]]s available for consideration. */ + final case class Context(initial: Plugins, enabled: Seq[AutoPlugin], deducePlugin: (Plugins, Logger) => Seq[AutoPlugin], available: List[AutoPlugin], log: Logger) - /** Describes the steps to activate a plugin in some context. */ - sealed abstract class PluginEnable - /** Describes a [[plugin]] that is already activated in the [[context]].*/ - final case class PluginActivated(plugin: AutoPlugin, context: Context) extends PluginEnable - sealed abstract class EnableDeactivated extends PluginEnable - /** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */ - final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin]) extends EnableDeactivated + /** Describes the steps to activate a plugin in some context. */ + sealed abstract class PluginEnable + /** Describes a [[plugin]] that is already activated in the [[context]].*/ + final case class PluginActivated(plugin: AutoPlugin, context: Context) extends PluginEnable + sealed abstract class EnableDeactivated extends PluginEnable + /** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */ + final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin]) extends EnableDeactivated - /** Describes the requirements for activating [[plugin]] in [[context]]. - * @param context The base plugins, exclusions, and ultimately activated plugins - * @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped - * @param enablingPlugins [[AutoPlugin]]s that are not currently enabled, but need to be enabled for [[plugin]] to activate - * @param extraEnabledPlugins Plugins that will be enabled as a result of [[plugin]] activating, but are not required for [[plugin]] to activate - * @param willRemove Plugins that will be deactivated as a result of [[plugin]] activating - * @param deactivate Describes plugins that must be deactivated for [[plugin]] to activate. These require an explicit exclusion or dropping a transitive [[AutoPlugin]].*/ - final case class PluginRequirements(plugin: AutoPlugin, context: Context, blockingExcludes: Set[AutoPlugin], enablingPlugins: Set[AutoPlugin], extraEnabledPlugins: Set[AutoPlugin], willRemove: Set[AutoPlugin], deactivate: List[DeactivatePlugin]) extends EnableDeactivated + /** Describes the requirements for activating [[plugin]] in [[context]]. + * @param context The base plugins, exclusions, and ultimately activated plugins + * @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped + * @param enablingPlugins [[AutoPlugin]]s that are not currently enabled, but need to be enabled for [[plugin]] to activate + * @param extraEnabledPlugins Plugins that will be enabled as a result of [[plugin]] activating, but are not required for [[plugin]] to activate + * @param willRemove Plugins that will be deactivated as a result of [[plugin]] activating + * @param deactivate Describes plugins that must be deactivated for [[plugin]] to activate. These require an explicit exclusion or dropping a transitive [[AutoPlugin]].*/ + final case class PluginRequirements(plugin: AutoPlugin, context: Context, blockingExcludes: Set[AutoPlugin], enablingPlugins: Set[AutoPlugin], extraEnabledPlugins: Set[AutoPlugin], willRemove: Set[AutoPlugin], deactivate: List[DeactivatePlugin]) extends EnableDeactivated - /** Describes a [[plugin]] that must be removed in order to activate another plugin in some context. - * The [[plugin]] can always be directly, explicitly excluded. - * @param removeOneOf If non-empty, removing one of these [[AutoPlugin]]s will deactivate [[plugin]] without affecting the other plugin. If empty, a direct exclusion is required. - * @param newlySelected If false, this plugin was selected in the original context. */ - final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean) + /** Describes a [[plugin]] that must be removed in order to activate another plugin in some context. + * The [[plugin]] can always be directly, explicitly excluded. + * @param removeOneOf If non-empty, removing one of these [[AutoPlugin]]s will deactivate [[plugin]] without affecting the other plugin. If empty, a direct exclusion is required. + * @param newlySelected If false, this plugin was selected in the original context. */ + final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean) - /** Determines how to enable [[plugin]] in [[context]]. */ - def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable = - if(context.enabled.contains(plugin)) - PluginActivated(plugin, context) - else - enableDeactivated(context, plugin) + /** Determines how to enable [[plugin]] in [[context]]. */ + def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable = + if(context.enabled.contains(plugin)) + PluginActivated(plugin, context) + else + enableDeactivated(context, plugin) - private[this] def enableDeactivated(context: Context, plugin: AutoPlugin): PluginEnable = - { - // deconstruct the context - val initialModel = context.enabled.toSet - val initial = flatten(context.initial) - val initialPlugins = plugins(initial) - val initialExcludes = excludes(initial) + private[this] def enableDeactivated(context: Context, plugin: AutoPlugin): PluginEnable = { + // deconstruct the context + val initialModel = context.enabled.toSet + val initial = flatten(context.initial) + val initialPlugins = plugins(initial) + val initialExcludes = excludes(initial) - val minModel = minimalModel(plugin) + val minModel = minimalModel(plugin) - /* example 1 - A :- B, not C - C :- D, E - initial: B, D, E - propose: drop D or E + /* example 1 + A :- B, not C + C :- D, E + initial: B, D, E + propose: drop D or E - initial: B, not A - propose: drop 'not A' + initial: B, not A + propose: drop 'not A' - example 2 - A :- B, not C - C :- B - initial: - propose: B, exclude C - */ + example 2 + A :- B, not C + C :- B + initial: + propose: B, exclude C + */ - // `plugin` will only be activated when all of these plugins are activated - // Deactivating any one of these would deactivate `plugin`. - val minRequiredPlugins = plugins(minModel) + // `plugin` will only be activated when all of these plugins are activated + // Deactivating any one of these would deactivate `plugin`. + val minRequiredPlugins = plugins(minModel) - // The presence of any one of these plugins would deactivate `plugin` - val minAbsentPlugins = excludes(minModel).toSet + // The presence of any one of these plugins would deactivate `plugin` + val minAbsentPlugins = excludes(minModel).toSet - // Plugins that must be both activated and deactivated for `plugin` to activate. - // A non-empty list here cannot be satisfied and is an error. - val contradictions = minAbsentPlugins & minRequiredPlugins + // Plugins that must be both activated and deactivated for `plugin` to activate. + // A non-empty list here cannot be satisfied and is an error. + val contradictions = minAbsentPlugins & minRequiredPlugins - if(contradictions.nonEmpty) - PluginImpossible(plugin, context, contradictions) - else - { - // Plguins that the user has to add to the currently selected plugins in order to enable `plugin`. - val addToExistingPlugins = minRequiredPlugins -- initialPlugins + if(contradictions.nonEmpty) PluginImpossible(plugin, context, contradictions) + else { + // Plguins that the user has to add to the currently selected plugins in order to enable `plugin`. + val addToExistingPlugins = minRequiredPlugins -- initialPlugins - // Plugins that are currently excluded that need to be allowed. - val blockingExcludes = initialExcludes & minRequiredPlugins + // Plugins that are currently excluded that need to be allowed. + val blockingExcludes = initialExcludes & minRequiredPlugins - // The model that results when the minimal plugins are enabled and the minimal plugins are excluded. - // This can include more plugins than just `minRequiredPlugins` because the plguins required for `plugin` - // might activate other plugins as well. - val modelForMin = context.deducePlugin(and(includeAll(minRequiredPlugins), excludeAll(minAbsentPlugins)), context.log) + // The model that results when the minimal plugins are enabled and the minimal plugins are excluded. + // This can include more plugins than just `minRequiredPlugins` because the plguins required for `plugin` + // might activate other plugins as well. + val modelForMin = context.deducePlugin(and(includeAll(minRequiredPlugins), excludeAll(minAbsentPlugins)), context.log) - val incrementalInputs = and( includeAll(minRequiredPlugins ++ initialPlugins), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins)) - val incrementalModel = context.deducePlugin(incrementalInputs, context.log).toSet + val incrementalInputs = and( includeAll(minRequiredPlugins ++ initialPlugins), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins)) + val incrementalModel = context.deducePlugin(incrementalInputs, context.log).toSet - // Plugins that are newly enabled as a result of selecting the plugins needed for `plugin`, but aren't strictly required for `plugin`. - // These could be excluded and `plugin` and the user's current plugins would still be activated. - val extraPlugins = incrementalModel.toSet -- minRequiredPlugins -- initialModel + // Plugins that are newly enabled as a result of selecting the plugins needed for `plugin`, but aren't strictly required for `plugin`. + // These could be excluded and `plugin` and the user's current plugins would still be activated. + val extraPlugins = incrementalModel.toSet -- minRequiredPlugins -- initialModel - // Plugins that will no longer be enabled as a result of enabling `plugin`. - val willRemove = initialModel -- incrementalModel + // Plugins that will no longer be enabled as a result of enabling `plugin`. + val willRemove = initialModel -- incrementalModel - // Determine the plugins that must be independently deactivated. - // If both A and B must be deactivated, but A transitively depends on B, deactivating B will deactivate A. - // If A must be deactivated, but one if its (transitively) required plugins isn't present, it won't be activated. - // So, in either of these cases, A doesn't need to be considered further and won't be included in this set. - val minDeactivate = minAbsentPlugins.filter(p => Plugins.satisfied(p.requires, incrementalModel)) + // Determine the plugins that must be independently deactivated. + // If both A and B must be deactivated, but A transitively depends on B, deactivating B will deactivate A. + // If A must be deactivated, but one if its (transitively) required plugins isn't present, it won't be activated. + // So, in either of these cases, A doesn't need to be considered further and won't be included in this set. + val minDeactivate = minAbsentPlugins.filter(p => Plugins.satisfied(p.requires, incrementalModel)) - val deactivate = for(d <- minDeactivate.toList) yield { - // removing any one of these plugins will deactivate `d`. TODO: This is not an especially efficient implementation. - val removeToDeactivate = plugins(minimalModel(d)) -- minRequiredPlugins - val newlySelected = !initialModel(d) - // a. suggest removing a plugin in removeOneToDeactivate to deactivate d - // b. suggest excluding `d` to directly deactivate it in any case - // c. note whether d was already activated (in context.enabled) or is newly selected - DeactivatePlugin(d, removeToDeactivate, newlySelected) - } + val deactivate = for(d <- minDeactivate.toList) yield { + // removing any one of these plugins will deactivate `d`. TODO: This is not an especially efficient implementation. + val removeToDeactivate = plugins(minimalModel(d)) -- minRequiredPlugins + val newlySelected = !initialModel(d) + // a. suggest removing a plugin in removeOneToDeactivate to deactivate d + // b. suggest excluding `d` to directly deactivate it in any case + // c. note whether d was already activated (in context.enabled) or is newly selected + DeactivatePlugin(d, removeToDeactivate, newlySelected) + } - PluginRequirements(plugin, context, blockingExcludes, addToExistingPlugins, extraPlugins, willRemove, deactivate) - } - } + PluginRequirements(plugin, context, blockingExcludes, addToExistingPlugins, extraPlugins, willRemove, deactivate) + } + } - private[this] def includeAll[T <: Basic](basic: Set[T]): Plugins = And(basic.toList) - private[this] def excludeAll(plugins: Set[AutoPlugin]): Plugins = And(plugins map (p => Exclude(p)) toList) + private[this] def includeAll[T <: Basic](basic: Set[T]): Plugins = And(basic.toList) + private[this] def excludeAll(plugins: Set[AutoPlugin]): Plugins = And(plugins map (p => Exclude(p)) toList) - private[this] def excludes(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case Exclude(b) => b }.toSet - private[this] def plugins(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case n: AutoPlugin => n }.toSet + private[this] def excludes(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case Exclude(b) => b }.toSet + private[this] def plugins(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case n: AutoPlugin => n }.toSet - // If there is a model that includes `plugin`, it includes at least what is returned by this method. - // This is the list of plugins that must be included as well as list of plugins that must not be present. - // It might not be valid, such as if there are contradictions or if there are cycles that are unsatisfiable. - // The actual model might be larger, since other plugins might be enabled by the selected plugins. - private[this] def minimalModel(plugin: AutoPlugin): Seq[Basic] = Dag.topologicalSortUnchecked(plugin: Basic) { - case _: Exclude => Nil - case ap: AutoPlugin => Plugins.flatten(ap.requires) :+ plugin - } + // If there is a model that includes `plugin`, it includes at least what is returned by this method. + // This is the list of plugins that must be included as well as list of plugins that must not be present. + // It might not be valid, such as if there are contradictions or if there are cycles that are unsatisfiable. + // The actual model might be larger, since other plugins might be enabled by the selected plugins. + private[this] def minimalModel(plugin: AutoPlugin): Seq[Basic] = Dag.topologicalSortUnchecked(plugin: Basic) { + case _: Exclude => Nil + case ap: AutoPlugin => Plugins.flatten(ap.requires) :+ plugin + } - /** String representation of [[PluginEnable]], intended for end users. */ - def explainPluginEnable(ps: PluginEnable): String = - ps match { - case PluginRequirements(plugin, context, blockingExcludes, enablingPlugins, extraEnabledPlugins, toBeRemoved, deactivate) => - def indent(str: String) = if(str.isEmpty) "" else s"\t$str" - def note(str: String) = if(str.isEmpty) "" else s"Note: $str" - val parts = - indent(excludedError(false /* TODO */, blockingExcludes.toList)) :: - indent(required(enablingPlugins.toList)) :: - indent(needToDeactivate(deactivate)) :: - note(willAdd(plugin, extraEnabledPlugins.toList)) :: - note(willRemove(plugin, toBeRemoved.toList)) :: - Nil - parts.filterNot(_.isEmpty).mkString("\n") - case PluginImpossible(plugin, context, contradictions) => pluginImpossible(plugin, contradictions) - case PluginActivated(plugin, context) => s"Plugin ${plugin.label} already activated." - } + /** String representation of [[PluginEnable]], intended for end users. */ + def explainPluginEnable(ps: PluginEnable): String = + ps match { + case PluginRequirements(plugin, context, blockingExcludes, enablingPlugins, extraEnabledPlugins, toBeRemoved, deactivate) => + def indent(str: String) = if(str.isEmpty) "" else s"\t$str" + def note(str: String) = if(str.isEmpty) "" else s"Note: $str" + val parts = + indent(excludedError(false /* TODO */, blockingExcludes.toList)) :: + indent(required(enablingPlugins.toList)) :: + indent(needToDeactivate(deactivate)) :: + note(willAdd(plugin, extraEnabledPlugins.toList)) :: + note(willRemove(plugin, toBeRemoved.toList)) :: + Nil + parts.filterNot(_.isEmpty).mkString("\n") + case PluginImpossible(plugin, context, contradictions) => pluginImpossible(plugin, contradictions) + case PluginActivated(plugin, context) => s"Plugin ${plugin.label} already activated." + } - /** Provides a [[Relation]] between plugins and the keys they potentially define. - * Because plugins can define keys in different scopes and keys can be overridden, this is not definitive.*/ - def definedKeys(available: List[AutoPlugin]): Relation[AutoPlugin, AttributeKey[_]] = - { - def extractDefinedKeys(ss: Seq[Setting[_]]): Seq[AttributeKey[_]] = - ss.map(_.key.key) - def allSettings(p: AutoPlugin): Seq[Setting[_]] = p.projectSettings ++ p.buildSettings ++ p.globalSettings - val empty = Relation.empty[AutoPlugin, AttributeKey[_]] - (empty /: available)( (r,p) => r + (p, extractDefinedKeys(allSettings(p))) ) - } + /** Provides a [[Relation]] between plugins and the keys they potentially define. + * Because plugins can define keys in different scopes and keys can be overridden, this is not definitive.*/ + def definedKeys(available: List[AutoPlugin]): Relation[AutoPlugin, AttributeKey[_]] = + { + def extractDefinedKeys(ss: Seq[Setting[_]]): Seq[AttributeKey[_]] = + ss.map(_.key.key) + def allSettings(p: AutoPlugin): Seq[Setting[_]] = p.projectSettings ++ p.buildSettings ++ p.globalSettings + val empty = Relation.empty[AutoPlugin, AttributeKey[_]] + (empty /: available)( (r,p) => r + (p, extractDefinedKeys(allSettings(p))) ) + } - private[this] def excludedError(transitive: Boolean, dependencies: List[AutoPlugin]): String = - str(dependencies)(excludedPluginError(transitive), excludedPluginsError(transitive)) + private[this] def excludedError(transitive: Boolean, dependencies: List[AutoPlugin]): String = + str(dependencies)(excludedPluginError(transitive), excludedPluginsError(transitive)) - private[this] def excludedPluginError(transitive: Boolean)(dependency: AutoPlugin) = - s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded." - private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) = - s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}" - private[this] def transitiveString(transitive: Boolean) = - if(transitive) "(transitive) " else "" + private[this] def excludedPluginError(transitive: Boolean)(dependency: AutoPlugin) = + s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded." + private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) = + s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}" + private[this] def transitiveString(transitive: Boolean) = + if(transitive) "(transitive) " else "" - private[this] def required(plugins: List[AutoPlugin]): String = - str(plugins)(requiredPlugin, requiredPlugins) + private[this] def required(plugins: List[AutoPlugin]): String = + str(plugins)(requiredPlugin, requiredPlugins) - private[this] def requiredPlugin(plugin: AutoPlugin) = - s"Required plugin ${plugin.label} not present." - private[this] def requiredPlugins(plugins: List[AutoPlugin]) = - s"Required plugins not present:\n\t${plugins.map(_.label).mkString("\n\t")}" + private[this] def requiredPlugin(plugin: AutoPlugin) = + s"Required plugin ${plugin.label} not present." + private[this] def requiredPlugins(plugins: List[AutoPlugin]) = + s"Required plugins not present:\n\t${plugins.map(_.label).mkString("\n\t")}" - private[this] def str[A](list: List[A])(f: A => String, fs: List[A] => String): String = list match { - case Nil => "" - case single :: Nil => f(single) - case _ => fs(list) - } + private[this] def str[A](list: List[A])(f: A => String, fs: List[A] => String): String = + list match { + case Nil => "" + case single :: Nil => f(single) + case _ => fs(list) + } - private[this] def willAdd(base: AutoPlugin, plugins: List[AutoPlugin]): String = - str(plugins)(willAddPlugin(base), willAddPlugins(base)) + private[this] def willAdd(base: AutoPlugin, plugins: List[AutoPlugin]): String = + str(plugins)(willAddPlugin(base), willAddPlugins(base)) - private[this] def willAddPlugin(base: AutoPlugin)(plugin: AutoPlugin) = - s"Enabling ${base.label} will also enable ${plugin.label}" - private[this] def willAddPlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) = - s"Enabling ${base.label} will also enable:\n\t${labels(plugins).mkString("\n\t")}" + private[this] def willAddPlugin(base: AutoPlugin)(plugin: AutoPlugin) = + s"Enabling ${base.label} will also enable ${plugin.label}" + private[this] def willAddPlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) = + s"Enabling ${base.label} will also enable:\n\t${labels(plugins).mkString("\n\t")}" - private[this] def willRemove(base: AutoPlugin, plugins: List[AutoPlugin]): String = - str(plugins)(willRemovePlugin(base), willRemovePlugins(base)) + private[this] def willRemove(base: AutoPlugin, plugins: List[AutoPlugin]): String = + str(plugins)(willRemovePlugin(base), willRemovePlugins(base)) - private[this] def willRemovePlugin(base: AutoPlugin)(plugin: AutoPlugin) = - s"Enabling ${base.label} will disable ${plugin.label}" - private[this] def willRemovePlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) = - s"Enabling ${base.label} will disable:\n\t${labels(plugins).mkString("\n\t")}" + private[this] def willRemovePlugin(base: AutoPlugin)(plugin: AutoPlugin) = + s"Enabling ${base.label} will disable ${plugin.label}" + private[this] def willRemovePlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) = + s"Enabling ${base.label} will disable:\n\t${labels(plugins).mkString("\n\t")}" - private[this] def labels(plugins: List[AutoPlugin]): List[String] = - plugins.map(_.label) + private[this] def labels(plugins: List[AutoPlugin]): List[String] = + plugins.map(_.label) - private[this] def needToDeactivate(deactivate: List[DeactivatePlugin]): String = - str(deactivate)(deactivate1, deactivateN) - private[this] def deactivateN(plugins: List[DeactivatePlugin]): String = - plugins.map(deactivateString).mkString("These plugins need to be deactivated:\n\t", "\n\t", "") - private[this] def deactivate1(deactivate: DeactivatePlugin): String = - s"Need to deactivate ${deactivateString(deactivate)}" - private[this] def deactivateString(d: DeactivatePlugin): String = - { - val removePluginsString: String = - d.removeOneOf.toList match { - case Nil => "" - case x :: Nil => s" or no longer include $x" - case xs => s" or remove one of ${xs.mkString(", ")}" - } - s"${d.plugin.label}: directly exclude it${removePluginsString}" - } + private[this] def needToDeactivate(deactivate: List[DeactivatePlugin]): String = + str(deactivate)(deactivate1, deactivateN) + private[this] def deactivateN(plugins: List[DeactivatePlugin]): String = + plugins.map(deactivateString).mkString("These plugins need to be deactivated:\n\t", "\n\t", "") + private[this] def deactivate1(deactivate: DeactivatePlugin): String = + s"Need to deactivate ${deactivateString(deactivate)}" + private[this] def deactivateString(d: DeactivatePlugin): String = + { + val removePluginsString: String = + d.removeOneOf.toList match { + case Nil => "" + case x :: Nil => s" or no longer include $x" + case xs => s" or remove one of ${xs.mkString(", ")}" + } + s"${d.plugin.label}: directly exclude it${removePluginsString}" + } - private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String = - str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin)) + private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String = + str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin)) - private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String = - s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent. Please report the problem to the plugin's author." - private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String = - s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires these plugins to be both present and absent:\n\t${labels(contradictions).mkString("\n\t")}\nPlease report the problem to the plugin's author." + private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String = + s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent. Please report the problem to the plugin's author." + private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String = + s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires these plugins to be both present and absent:\n\t${labels(contradictions).mkString("\n\t")}\nPlease report the problem to the plugin's author." } \ No newline at end of file