[2.x] fix: allow defining root project with extraProjects (#4976) (#8694)

When an AutoPlugin adds a project at the build root via extraProjects,
avoid creating a second root from the build definition so both do not
share the same target directory. Treat extraProjects root as 'root
already defined' and exclude the build-defined root from initialProjects
when both would be at the same base.
This commit is contained in:
bitloi 2026-02-05 19:07:30 -05:00 committed by GitHub
parent 12deebba2b
commit 52bc35e3a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 40 additions and 8 deletions

View File

@ -812,10 +812,17 @@ private[sbt] object Load {
mkReporter,
)
}
val projectsFromBuildDef = defsScala.flatMap(b => projectsFromBuild(b, normBase))
val rootFromExtra =
buildLevelExtraProjects.filter(p => isRootPath(p.base, normBase))
val initialProjects =
defsScala.flatMap(b => projectsFromBuild(b, normBase)) ++ buildLevelExtraProjects
val hasRootAlreadyDefined = defsScala.exists(_.rootProject.isDefined)
if rootFromExtra.nonEmpty then
projectsFromBuildDef.filterNot(p =>
isRootPath(p.base, normBase)
) ++ buildLevelExtraProjects
else projectsFromBuildDef ++ buildLevelExtraProjects
val hasRootAlreadyDefined =
defsScala.exists(_.rootProject.isDefined) || rootFromExtra.nonEmpty
val memoSettings = new mutable.HashMap[VirtualFile, LoadedSbtFile]
def loadProjects(ps: Seq[Project], createRoot: Boolean) =
@ -1086,11 +1093,9 @@ private[sbt] object Load {
val DiscoveredProjects(rootOpt, discovered, files, extraFiles, generated) = discover(
p.base
)
// TODO: We assume here the project defined in a build.sbt WINS because the original was a
// phony. However, we may want to 'merge' the two, or only do this if the original was a
// default generated project.
val root = rootOpt.getOrElse(p)
val root =
if p.projectOrigin == ProjectOrigin.ExtraProject && isRootPath(p.base, buildBase) then p
else rootOpt.getOrElse(p)
val (finalRoot, projectLevelExtra) = processProject(root, files, extraFiles, true)
val newProjects = rest ++ discovered ++ projectLevelExtra
val newAcc = acc :+ finalRoot

View File

@ -0,0 +1,2 @@
// Minimal build so the build definition contributes a root; the plugin's extraProjects
// root (foo at file(".")) should win and be the only root (#4976).

View File

@ -0,0 +1,19 @@
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
import sbt._, Keys._
/**
* Reproduces #4976 / Dale's use case: plugin defines the root project via extraProjects.
* Without the fix, loading fails with "Overlapping output directories" (build root + foo).
*/
object RootFromExtraPlugin extends AutoPlugin {
override def trigger = allRequirements
override def extraProjects: Seq[Project] =
Seq(Project("foo", file(".")).settings(name := "foo"))
}

View File

@ -0,0 +1,6 @@
# Root project is defined by plugin via extraProjects (#4976). Without the fix,
# loading fails with "Overlapping output directories" (build root + foo).
> compile
> show name