From ac0f3f5c6f3b80e7f359c1827bc1ae6efce79bc1 Mon Sep 17 00:00:00 2001 From: volcano303 <75143900+volcano303@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:50:28 -0600 Subject: [PATCH] [2.x] fix: Cache AutoPlugin projectSettings/projectConfigurations (#9077) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cache AutoPlugin.projectSettings and projectConfigurations via AutoPluginCache so they are evaluated once per plugin instance during build loading, not once per subproject (N×M → M) - Make GroupedAutoPlugins.globalSettings a lazy val and pre-compute buildSettingsMap to avoid repeated aggregation --- .../sbt/internal/GroupedAutoPlugins.scala | 6 +++-- main/src/main/scala/sbt/internal/Load.scala | 27 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/internal/GroupedAutoPlugins.scala b/main/src/main/scala/sbt/internal/GroupedAutoPlugins.scala index 13b2c988f..423e1b6df 100644 --- a/main/src/main/scala/sbt/internal/GroupedAutoPlugins.scala +++ b/main/src/main/scala/sbt/internal/GroupedAutoPlugins.scala @@ -16,9 +16,11 @@ private[sbt] final class GroupedAutoPlugins( val all: Seq[AutoPlugin], val byBuild: Map[URI, Seq[AutoPlugin]] ) { - def globalSettings: Seq[Setting[?]] = all.flatMap(_.globalSettings) + lazy val globalSettings: Seq[Setting[?]] = all.flatMap(_.globalSettings) + private lazy val buildSettingsMap: Map[URI, Seq[Setting[?]]] = + byBuild.view.mapValues(_.flatMap(_.buildSettings)).toMap def buildSettings(uri: URI): Seq[Setting[?]] = - byBuild.getOrElse(uri, Nil).flatMap(_.buildSettings) + buildSettingsMap.getOrElse(uri, Nil) } private[sbt] object GroupedAutoPlugins { diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index cafd1389e..583a9fbaf 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -40,6 +40,23 @@ import scala.annotation.tailrec import scala.collection.mutable import sbt.internal.util.Util +/** + * Caches `AutoPlugin.projectSettings` and `projectConfigurations` so they are + * evaluated only once per plugin instance during build loading, regardless of how + * many subprojects activate the plugin. See https://github.com/sbt/sbt/issues/5166 + */ +private[sbt] final class AutoPluginCache { + private val settingsCache: mutable.HashMap[AutoPlugin, Seq[Def.Setting[?]]] = + mutable.HashMap.empty + private val configCache: mutable.HashMap[AutoPlugin, Seq[Configuration]] = mutable.HashMap.empty + + def projectSettings(plugin: AutoPlugin): Seq[Def.Setting[?]] = + settingsCache.getOrElseUpdate(plugin, plugin.projectSettings) + + def projectConfigurations(plugin: AutoPlugin): Seq[Configuration] = + configCache.getOrElseUpdate(plugin, plugin.projectConfigurations) +} + private[sbt] object Load { // note that there is State passed in but not pulled out def defaultLoad( @@ -832,6 +849,7 @@ private[sbt] object Load { defsScala.exists(_.rootProject.isDefined) || rootFromExtra.nonEmpty val memoSettings = new mutable.HashMap[VirtualFile, LoadedSbtFile] + val pluginCache = new AutoPluginCache def loadProjects(ps: Seq[Project], createRoot: Boolean) = loadTransitive( ps, @@ -849,6 +867,7 @@ private[sbt] object Load { Nil, s.get(BasicKeys.extraMetaSbtFiles).getOrElse(Nil), converter = config.converter, + pluginCache = pluginCache, ) val loadedProjectsRaw = timed("Load.loadUnit: loadedProjectsRaw", log) { loadProjects(initialProjects, !hasRootAlreadyDefined) @@ -1010,6 +1029,7 @@ private[sbt] object Load { generatedConfigClassFiles: Seq[Path], extraSbtFiles: Seq[VirtualFile], converter: MappedFileConverter, + pluginCache: AutoPluginCache, ): LoadedProjects = // alias for parameter forwarding def loadTransitive1( @@ -1034,6 +1054,7 @@ private[sbt] object Load { generated, Nil, converter, + pluginCache, ) // alias for parameter forwarding @@ -1085,6 +1106,7 @@ private[sbt] object Load { extraSbtFiles = extraFiles, converter = converter, log = log, + pluginCache = pluginCache, ) val projectLevelExtra = if (expand) { @@ -1207,12 +1229,13 @@ private[sbt] object Load { machineWideUserSettings: InjectSettings, memoSettings: mutable.Map[VirtualFile, LoadedSbtFile], extraSbtFiles: Seq[VirtualFile], + pluginCache: AutoPluginCache, converter: MappedFileConverter, log: Logger ): Project = timed(s"Load.resolveProjectSettings(${p.id})", log) { import AddSettings.* - val autoConfigs = projectPlugins.flatMap(_.projectConfigurations) + val autoConfigs = projectPlugins.flatMap(pluginCache.projectConfigurations) val auto = AddSettings.allDefaults // 3. Use AddSettings instance to order all Setting[_]s appropriately // Settings are ordered as: @@ -1221,7 +1244,7 @@ private[sbt] object Load { // Filter the AutoPlugin settings we included based on which ones are // intended in the AddSettings.AutoPlugins filter. def autoPluginSettings(f: AutoPlugins) = - projectPlugins.withFilter(f.include).flatMap(_.projectSettings) + projectPlugins.withFilter(f.include).flatMap(pluginCache.projectSettings) // Expand the AddSettings instance into a real Seq[Setting[_]] we'll use on the project def expandPluginSettings(auto: AddSettings): Seq[Setting[?]] = auto match