From ac4ba55e9e90a34b60e5fc677df8a8b947115026 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 2 Dec 2024 03:15:35 -0500 Subject: [PATCH] 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. --- main/src/main/scala/sbt/internal/Load.scala | 26 +++++++++++-------- .../sbt-test/project/projectref/a1/build.sbt | 1 + .../src/sbt-test/project/projectref/build.sbt | 3 +++ sbt-app/src/sbt-test/project/projectref/test | 1 + 4 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 sbt-app/src/sbt-test/project/projectref/a1/build.sbt create mode 100644 sbt-app/src/sbt-test/project/projectref/build.sbt create mode 100644 sbt-app/src/sbt-test/project/projectref/test diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 02077b92f..25d91b642 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -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, diff --git a/sbt-app/src/sbt-test/project/projectref/a1/build.sbt b/sbt-app/src/sbt-test/project/projectref/a1/build.sbt new file mode 100644 index 000000000..86b12fc9d --- /dev/null +++ b/sbt-app/src/sbt-test/project/projectref/a1/build.sbt @@ -0,0 +1 @@ +val a1 = (project in file(".")) diff --git a/sbt-app/src/sbt-test/project/projectref/build.sbt b/sbt-app/src/sbt-test/project/projectref/build.sbt new file mode 100644 index 000000000..569c777f2 --- /dev/null +++ b/sbt-app/src/sbt-test/project/projectref/build.sbt @@ -0,0 +1,3 @@ +// https://github.com/sbt/sbt/issues/7738 +val root = (project in file(".")) + .dependsOn(ProjectRef(file("a1"), "a1")) diff --git a/sbt-app/src/sbt-test/project/projectref/test b/sbt-app/src/sbt-test/project/projectref/test new file mode 100644 index 000000000..44f00a9cc --- /dev/null +++ b/sbt-app/src/sbt-test/project/projectref/test @@ -0,0 +1 @@ +> projects