fix: Fix root project detection

**Problem**
Whether a subproject is root or not is currently detected by
comparing getCanonicalFile against the build base directory.
Problem is that often the root project uses "." as the directory,
and getting the canonical file works ok for the current build,
but it breaks when loading `ProjectRef`.
There might be other bugs related to the root project detection.

**Solution**
This factors out `isRootPath` function that's aware of the build base.

This PR adds the repro test prepared by xuwei-k.
This commit is contained in:
Eugene Yokota 2024-12-02 03:15:35 -05:00
parent bc69030e58
commit ac4ba55e9e
4 changed files with 20 additions and 11 deletions

View File

@ -596,7 +596,7 @@ private[sbt] object Load {
// since base directories are resolved at this point (after 'projects'),
// we can compare Files instead of converting to URIs
def isRoot(p: Project) = p.base.getCanonicalFile() == unit.localBase.getCanonicalFile()
def isRoot(p: Project) = isRootPath(p.base, unit.localBase)
val externals = referenced(defined).toList
val explicitRoots = unit.definitions.builds.flatMap(_.rootProject)
@ -688,6 +688,13 @@ private[sbt] object Load {
}
}
/**
* Returns true when value is the subproject base for root project.
* Note that it's often specified as file(".").
*/
def isRootPath(value: File, projectBase: File): Boolean =
projectBase.getCanonicalFile() == IO.resolve(projectBase, value).getCanonicalFile()
def resolveBase(against: File): Project => Project = {
def resolve(f: File) = {
val fResolved = new File(IO.directoryURI(IO.resolve(against, f)))
@ -821,11 +828,10 @@ private[sbt] object Load {
// TODO - As of sbt 0.13.6 we should always have a default root project from
// here on, so the autogenerated build aggregated can be removed from this code. ( I think)
// We may actually want to move it back here and have different flags in loadTransitive...
val hasRoot = loadedProjectsRaw.projects.exists(
_.base.getCanonicalFile() == normBase.getCanonicalFile()
) || defsScala.exists(
_.rootProject.isDefined
)
val hasRoot =
loadedProjectsRaw.projects.exists(p => isRootPath(p.base, normBase)) || defsScala.exists(
_.rootProject.isDefined
)
val (loadedProjects, defaultBuildIfNone, keepClassFiles) =
if (hasRoot)
(
@ -992,12 +998,11 @@ private[sbt] object Load {
// load all relevant configuration files (.sbt, as .scala already exists at this point)
def discover(base: File): DiscoveredProjects = {
val auto =
if (base.getCanonicalFile() == buildBase.getCanonicalFile()) AddSettings.allDefaults
if isRootPath(base, buildBase) then AddSettings.allDefaults
else AddSettings.defaultSbtFiles
val extraFiles =
if base.getCanonicalFile() == buildBase.getCanonicalFile() && isMetaBuildContext(context)
then extraSbtFiles
if isRootPath(base, buildBase) && isMetaBuildContext(context) then extraSbtFiles
else Nil
discoverProjects(auto, base, extraFiles, plugins, eval, memoSettings, converter)
}
@ -1311,8 +1316,7 @@ private[sbt] object Load {
val rawFiles = associatedFiles(auto)
val loadedFiles = loadFiles(rawFiles)
val rawProjects = loadedFiles.projects
val (root, nonRoot) =
rawProjects.partition(_.base.getCanonicalFile() == projectBase.getCanonicalFile())
val (root, nonRoot) = rawProjects.partition(p => isRootPath(p.base, projectBase))
// TODO - good error message if more than one root project
DiscoveredProjects(
root.headOption,

View File

@ -0,0 +1 @@
val a1 = (project in file("."))

View File

@ -0,0 +1,3 @@
// https://github.com/sbt/sbt/issues/7738
val root = (project in file("."))
.dependsOn(ProjectRef(file("a1"), "a1"))

View File

@ -0,0 +1 @@
> projects