[2.x] fix: Fixes plugin toggle precedence between disablePlugins and enablePlugins (#8794)

Treat explicit plugin toggles as last-call-wins for the same plugin.
This avoids contradictory include/exclude states when disablePlugins(X) is followed by
enablePlugins(X) (and vice versa), aligning behavior with normal override expectations.

Apply the same semantics to ProjectMatrix and add regression coverage:
- unit tests in main/src/test/scala/ProjectSpec.scala
- scripted test in sbt-app/src/sbt-test/project/i1926-disable-enable-plugin
This commit is contained in:
it-education-md 2026-02-23 00:11:57 -05:00 committed by GitHub
parent 62d7b9a29f
commit cb498de41d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 73 additions and 4 deletions

View File

@ -343,6 +343,16 @@ ${listConflicts(conflicting)}""")
case And(ns) => ns.foldLeft(a)(_ && _)
case b: Basic => a && b
}
private[sbt] def overrideWith(current: Plugins, update: Plugins): Plugins = {
val opposite: Set[Basic] = flatten(update).map {
case Exclude(p) => p: Basic
case p: AutoPlugin =>
Exclude(p): Basic
}.toSet
and(remove(current, opposite), update)
}
private[sbt] def remove(a: Plugins, del: Set[Basic]): Plugins = a match {
case b: Basic => if (del(b)) Empty else b
case Empty => Empty

View File

@ -158,11 +158,18 @@ sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeP
* A [[AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to enable on a project.
*/
def enablePlugins(ns: Plugins*): Project =
setPlugins(ns.foldLeft(plugins)(Plugins.and))
setPlugins(ns.foldLeft(plugins)(Plugins.overrideWith))
/** Disable the given plugins on this project. */
def disablePlugins(ps: AutoPlugin*): Project =
setPlugins(Plugins.and(plugins, Plugins.And(ps.map(p => Plugins.Exclude(p)).toList)))
if ps.isEmpty then this
else
setPlugins(
Plugins.overrideWith(
plugins,
Plugins.And(ps.map(p => Plugins.Exclude(p)).toList)
)
)
private[sbt] def setPlugins(ns: Plugins): Project = copy(plugins = ns)

View File

@ -455,10 +455,17 @@ object ProjectMatrix {
copy(settings = (settings: Seq[Def.Setting[?]]) ++ Def.settings(ss*))
override def enablePlugins(ns: Plugins*): ProjectMatrix =
setPlugins(ns.foldLeft(plugins)(Plugins.and))
setPlugins(ns.foldLeft(plugins)(Plugins.overrideWith))
override def disablePlugins(ps: AutoPlugin*): ProjectMatrix =
setPlugins(Plugins.and(plugins, Plugins.And(ps.map(p => Plugins.Exclude(p)).toList)))
if ps.isEmpty then this
else
setPlugins(
Plugins.overrideWith(
plugins,
Plugins.And(ps.map(p => Plugins.Exclude(p)).toList)
)
)
override def configure(ts: (Project => Project)*): ProjectMatrix =
copy(transforms = transforms ++ ts)

View File

@ -8,10 +8,30 @@
package sbt
import java.io.File
object ProjectSpec extends verify.BasicTestSuite {
object TestPlugin extends AutoPlugin {
override def requires: Plugins = empty
}
private val base = new File(".")
test("Project should normalize projectIDs if they are empty") {
assert(Project.normalizeProjectID(emptyFilename) == Right("root"))
}
test("disablePlugins then enablePlugins should keep plugin enabled") {
val p = Project("test", base).disablePlugins(TestPlugin).enablePlugins(TestPlugin)
assert(Plugins.hasInclude(p.plugins, TestPlugin))
assert(!Plugins.hasExclude(p.plugins, TestPlugin))
}
test("enablePlugins then disablePlugins should keep plugin disabled") {
val p = Project("test", base).enablePlugins(TestPlugin).disablePlugins(TestPlugin)
assert(!Plugins.hasInclude(p.plugins, TestPlugin))
assert(Plugins.hasExclude(p.plugins, TestPlugin))
}
def emptyFilename = ""
}

View File

@ -0,0 +1,4 @@
def myProject(id: String): Project =
Project(id, file(id)).disablePlugins(Issue1926Plugin)
lazy val b = myProject("b").enablePlugins(Issue1926Plugin)

View File

@ -0,0 +1,4 @@
def myProject(id: String): Project =
Project(id, file(id)).disablePlugins(Issue1926Plugin)
lazy val a = myProject("a")

View File

@ -0,0 +1,13 @@
import sbt._
object Issue1926Plugin extends AutoPlugin {
object autoImport {
val issue1926Marker = settingKey[String]("Marker setting for issue #1926 test")
}
import autoImport._
override def requires: Plugins = plugins.JvmPlugin
override def trigger = noTrigger
override def projectSettings = Seq(issue1926Marker := "enabled")
}

View File

@ -0,0 +1,4 @@
> projects
$ copy-file broken.sbt.disabled broken.sbt
> reload
> show b/issue1926Marker