diff --git a/main/src/main/scala/sbt/internal/Act.scala b/main/src/main/scala/sbt/internal/Act.scala index a3662ac44..21d1e6c9c 100644 --- a/main/src/main/scala/sbt/internal/Act.scala +++ b/main/src/main/scala/sbt/internal/Act.scala @@ -63,7 +63,7 @@ object Act { keyMap: Map[String, AttributeKey[?]], data: Def.Settings ): Parser[ScopedKey[Any]] = - scopedKeySelected(index, current, defaultConfigs, keyMap, data, askProject = true) + scopedKeySelected(index, current, defaultConfigs, keyMap, data, askProject = true, None) .map(_.key.asInstanceOf[ScopedKey[Any]]) // the index should be an aggregated index for proper tab completion @@ -80,6 +80,7 @@ object Act { structure.index.keyMap, structure.data, askProject = true, + structure = Some(structure), ) ) yield Aggregation.aggregate( @@ -102,6 +103,7 @@ object Act { structure.index.keyMap, structure.data, askProject = optQuery.isEmpty, + structure = Some(structure), ) yield Aggregation .aggregate(selected.key, selected.mask, structure.extra) @@ -117,10 +119,11 @@ object Act { keyMap: Map[String, AttributeKey[?]], data: Def.Settings, askProject: Boolean, + structure: Option[BuildStructure], ): Parser[ParsedKey] = scopedKeyFull(index, current, defaultConfigs, keyMap, askProject = askProject).flatMap { choices => - select(choices, data)(using showRelativeKey2(current)) + select(choices, data, structure)(using showRelativeKey2(current)) } def scopedKeyFull( @@ -197,16 +200,39 @@ object Act { key ) - def select(allKeys: Seq[Parser[ParsedKey]], data: Def.Settings)(using - show: Show[ScopedKey[?]] - ): Parser[ParsedKey] = + def select( + allKeys: Seq[Parser[ParsedKey]], + data: Def.Settings + )(using show: Show[ScopedKey[?]]): Parser[ParsedKey] = + select(allKeys, data, None) + + def select( + allKeys: Seq[Parser[ParsedKey]], + data: Def.Settings, + structure: Option[BuildStructure] + )(using show: Show[ScopedKey[?]]): Parser[ParsedKey] = seq(allKeys) flatMap { ss => val default: Parser[ParsedKey] = ss.headOption match case None => noValidKeys case Some(x) => success(x) - selectFromValid(ss filter isValid(data), default) + val validFilter = structure.fold(isValid(data))(isValidForAggregate(data, _)) + selectFromValid(ss filter validFilter, default) } + private def isValidForAggregate( + data: Def.Settings, + structure: BuildStructure + )(parsed: ParsedKey): Boolean = + if data.contains(parsed.key) then true + else + val aggregated = + Aggregation.aggregate( + parsed.key.asInstanceOf[ScopedKey[Any]], + parsed.mask, + structure.extra + ) + aggregated.nonEmpty && aggregated.exists(data.contains) + def selectFromValid(ss: Seq[ParsedKey], default: Parser[ParsedKey])(using show: Show[ScopedKey[?]] ): Parser[ParsedKey] = @@ -230,7 +256,7 @@ object Act { if (zeros.nonEmpty) zeros else selects } - def noValidKeys = failure("No such key.") + def noValidKeys: Parser[ParsedKey] = failure("No such key.") def showAmbiguous(keys: Seq[ScopedKey[?]])(using show: Show[ScopedKey[?]]): String = keys.take(3).map(x => show.show(x)).mkString("", ", ", if (keys.size > 3) ", ..." else "") diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 2d016305d..83623cf3b 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -867,7 +867,7 @@ private[sbt] object Load { defaultProjects.generatedConfigClassFiles ++ loadedProjectsRaw.generatedConfigClassFiles ) } - val loadedProjects = processAutoAggregate(loadedProjects0, uri) + val loadedProjects = processAutoAggregate(loadedProjects0, uri, normBase, !hasRoot) timed("Load.loadUnit: cleanEvalClasses", log) { cleanEvalClasses(defDir, keepClassFiles) } @@ -889,9 +889,29 @@ private[sbt] object Load { new BuildUnit(uri, normBase, loadedDefs, plugs, converter) } - private def processAutoAggregate(inProjects: Seq[Project], uri: URI): Seq[Project] = - inProjects.map: proj => - proj + private def processAutoAggregate( + inProjects: Seq[Project], + uri: URI, + buildBase: File, + hasAutoRoot: Boolean + ): Seq[Project] = + if !hasAutoRoot then inProjects + else + val allProjectIds = inProjects.map(_.id).toSet + inProjects.map: proj => + val isAutoRoot = isRootPath(proj.base, buildBase) && proj.aggregate.nonEmpty + if isAutoRoot then + val currentAggregateIds = proj.aggregate + .flatMap: + case ref: ProjectRef => Some(ref.project) + case _ => None + .toSet + val missingIds = allProjectIds - proj.id -- currentAggregateIds + if missingIds.nonEmpty then + val missingRefs = missingIds.toSeq.map(id => ProjectRef(uri, id)) + proj.aggregate((proj.aggregate ++ missingRefs)*) + else proj + else proj private def autoID( localBase: File, diff --git a/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/build.sbt b/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/build.sbt new file mode 100644 index 000000000..415a43679 --- /dev/null +++ b/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/build.sbt @@ -0,0 +1,16 @@ +/* + * sbt + * Copyright 2023, Scala center + * Copyright 2011 - 2022, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +val check = taskKey[Unit]("Repro for #4947: task at root when extraProjects creates auto root") + +val a = project +val p = project + .settings( + name := "p", + check := () + ) diff --git a/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/project/ExtraPlugin.scala b/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/project/ExtraPlugin.scala new file mode 100644 index 000000000..77ff44de2 --- /dev/null +++ b/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/project/ExtraPlugin.scala @@ -0,0 +1,15 @@ +/* + * 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._ + +object ExtraPlugin extends AutoPlugin { + override def trigger = allRequirements + override def extraProjects: Seq[Project] = + Seq(Project("z", file("z")).settings(name := "z")) +} diff --git a/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/test b/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/test new file mode 100644 index 000000000..15675b169 --- /dev/null +++ b/sbt-app/src/sbt-test/project/extra-projects-key-aggregate/test @@ -0,0 +1 @@ +> check