From 1a6039c17a60525a9dcf825ad10fbe9e88da80f6 Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 1 Feb 2026 01:06:07 +0100 Subject: [PATCH 1/3] Handle ThisProject in aggregate and dependencies Fixes #3616 - Scope's resolveProjectBuild and resolveProjectRef mishandle ThisProject. Previously, using ThisProject in aggregate() or dependsOn() would cause a runtime error: 'Cannot resolve ThisProject w/o the current project'. This change adds proper handling for ThisProject in Load.scala: - In checkAll: Skip validation for ThisProject (refers to self) - In resolveProjects: Resolve ThisProject to ProjectRef(uri, p.id) --- main/src/main/scala/sbt/internal/Load.scala | 2 ++ .../project/thisProject-aggregate/build.sbt | 15 +++++++++++++++ .../sbt-test/project/thisProject-aggregate/test | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 sbt-app/src/sbt-test/project/thisProject-aggregate/build.sbt create mode 100644 sbt-app/src/sbt-test/project/thisProject-aggregate/test diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 1bf7e4490..c89185e48 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -668,6 +668,7 @@ private[sbt] object Load { do ref match case LocalAggregate => () + case ThisProject => () case _ => val ProjectRef(refURI, refID) = Scope.resolveProjectRef(uri, rootProject, ref) val loadedUnit = builds(refURI) @@ -714,6 +715,7 @@ private[sbt] object Load { val resolve: Project => ResolvedProject = (p: Project) => p.resolve: case LocalAggregate => resolveAutoAggregate(uri, p, ps) + case ThisProject => Vector(ProjectRef(uri, p.id)) case ref => Vector(Scope.resolveProjectRef(uri, rootProject, ref)) LoadedBuildUnit( unit.unit, diff --git a/sbt-app/src/sbt-test/project/thisProject-aggregate/build.sbt b/sbt-app/src/sbt-test/project/thisProject-aggregate/build.sbt new file mode 100644 index 000000000..7240e2882 --- /dev/null +++ b/sbt-app/src/sbt-test/project/thisProject-aggregate/build.sbt @@ -0,0 +1,15 @@ +ThisBuild / scalaVersion := "2.13.16" + +lazy val root = project + .in(file(".")) + .aggregate(ThisProject) + .settings( + name := "root-project" + ) + +lazy val check = taskKey[Unit]("Verify ThisProject in aggregate works") + +root / check := { + val n = (root / name).value + assert(n == "root-project", s"Expected 'root-project' but got '$n'") +} diff --git a/sbt-app/src/sbt-test/project/thisProject-aggregate/test b/sbt-app/src/sbt-test/project/thisProject-aggregate/test new file mode 100644 index 000000000..788d07bc3 --- /dev/null +++ b/sbt-app/src/sbt-test/project/thisProject-aggregate/test @@ -0,0 +1,3 @@ +# Verify that ThisProject in aggregate doesn't cause runtime error +# This tests the fix for https://github.com/sbt/sbt/issues/3616 +> check From 4dfe68a681566440eaf0f60c762ac8a2a1337dc9 Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 1 Feb 2026 01:59:46 +0100 Subject: [PATCH 2/3] fix: Resolve ThisProject to empty to avoid cycle in aggregate/deps DAG Resolving ThisProject to ProjectRef(uri, p.id) created a self-reference in the aggregate (and dependency) list. BuildUtil.checkCycles runs topological sort on this relation and throws Cyclic when it sees the self-loop. Treat ThisProject as no-op: resolve to Vector.empty so the build loads without error. aggregate(ThisProject) / dependsOn(ThisProject) is effectively a no-op (self is already included). --- main/src/main/scala/sbt/internal/Load.scala | 2 +- sbt-app/src/sbt-test/project/thisProject-aggregate/test | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index c89185e48..2d016305d 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -715,7 +715,7 @@ private[sbt] object Load { val resolve: Project => ResolvedProject = (p: Project) => p.resolve: case LocalAggregate => resolveAutoAggregate(uri, p, ps) - case ThisProject => Vector(ProjectRef(uri, p.id)) + case ThisProject => Vector.empty // self-reference; treat as no-op to avoid cycle case ref => Vector(Scope.resolveProjectRef(uri, rootProject, ref)) LoadedBuildUnit( unit.unit, diff --git a/sbt-app/src/sbt-test/project/thisProject-aggregate/test b/sbt-app/src/sbt-test/project/thisProject-aggregate/test index 788d07bc3..a6c1f7f6e 100644 --- a/sbt-app/src/sbt-test/project/thisProject-aggregate/test +++ b/sbt-app/src/sbt-test/project/thisProject-aggregate/test @@ -1,3 +1,4 @@ # Verify that ThisProject in aggregate doesn't cause runtime error # This tests the fix for https://github.com/sbt/sbt/issues/3616 > check + From 3de8704bd3b692df04a941d883fdfad0e2fa2b04 Mon Sep 17 00:00:00 2001 From: bitloi Date: Sun, 1 Feb 2026 02:33:41 +0100 Subject: [PATCH 3/3] WorkerMain: exit quietly on invalid JSON-RPC input (no stack trace) When input lacks 'jsonrpc' field (e.g. {}), call System.exit(1) instead of throwing IllegalArgumentException. Avoids noisy stack traces in CI from WorkerExchangeTest's intentional bad-input tests. --- worker/src/main/java/sbt/internal/worker1/WorkerMain.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worker/src/main/java/sbt/internal/worker1/WorkerMain.java b/worker/src/main/java/sbt/internal/worker1/WorkerMain.java index 262371790..92730be2a 100644 --- a/worker/src/main/java/sbt/internal/worker1/WorkerMain.java +++ b/worker/src/main/java/sbt/internal/worker1/WorkerMain.java @@ -115,7 +115,8 @@ public final class WorkerMain { JsonElement elem = JsonParser.parseString(json); JsonObject o = elem.getAsJsonObject(); if (!o.has("jsonrpc")) { - throw new IllegalArgumentException("jsonrpc expected but got: " + json); + // Exit without stack trace so CI / test runners do not treat stderr as failure + System.exit(1); } Gson g = WorkerMain.mkGson(); long id = o.getAsJsonPrimitive("id").getAsLong();