**Problem**
Intermittent java.nio.file.FileAlreadyExistsException when publishing
classes.sbtdir.zip during action-cache packaging under parallel tasks
(#9043). Copying from a temp directory straight into the final path races
on the fixed destination name.
**Solution**
Stage the built zip next to the destination with a unique temp file, then
replace the final path via Files.move with REPLACE_EXISTING and ATOMIC_MOVE,
falling back to a non-atomic move when needed. Add a concurrent
packageDirectory test.
Closes#9043
Generated-by: Cursor (AI-assisted)
**Problem**
In sbt 2.x, if we execute a run task from the shell,
and if the program fails, it ends up taking down the entire shell
because client-side run rethrows, which is not desirable
for the sbt shell.
**Solution**
1. Omit printing out success for ClientJobParams, which is the runinfo.
2. Print out success or error on the client-side for shell usage case.
In sbt 2.x, running batch commands through the thin client (sbtn) suppresses stack traces for all tasks because the server's shell command unconditionally sets state.interactive = true. This causes LogManager.defaultTraceLevel to return -1 (suppressed) even when the client explicitly signals non-interactive (batch) mode via Attach(interactive=false).
This fixes the shell command to check the originating NetworkChannel's interactive flag before setting state.interactive, so thin client batch commands correctly get Int.MaxValue trace level and display full stack traces.
**Problem**
PomGenerator (introduced in #8873) was missing <description> and <url>
elements that the old Ivy-based MakePom included. Setting `description`
and `homepage` in build.sbt had no effect on the generated pom.xml.
**Solution**
Add makeDescription and makeHomePage helpers to PomGenerator, matching
the behavior of MakePom. Add test assertions for both fields.
Fixes#9054
**Problem**
When a file referenced during cache serialization is deleted between task
execution and cache storage, sbt crashes with an uncaught NoSuchFileException.
The IOException catch added in #8699 only wraps store.put(), but
Converter.toJsonUnsafe(result) and mkInput() also call
hashedVirtualFileRefToStr which calls Files.readAttributes — and these are
outside the try-catch.
**Solution**
Move the try/catch IOException to wrap the entire post-action cache storage
section, so NoSuchFileException from result serialization or cache key
computation is caught and handled gracefully (skip caching, return result).
Fixes#9044
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.co>
The fallback ServerHandler silently dropped requests with unknown
methods, violating the JSON-RPC spec. Clients like Metals that send
unrecognized requests would never receive a response, potentially
causing timeouts or hangs.
Return a -32601 (Method not found) error response instead.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the download URL returns an HTTP error (e.g. 404), curl without
--fail exits 0 and saves the error page HTML as the jar file. This
leaves a corrupt jar that causes confusing errors on subsequent runs.
With --fail, curl returns a non-zero exit code on HTTP errors and
does not write the response body to the output file.
Fixes#6654
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
**problem**
.previous was implemented as an inline expansion to wrapInitTask(Previous.runtime(in)(<instance of JsonFormat[A1]))
For example, the .previous on taskKey[String] gets inlined like
this, and then sbt's macro moves `Previous.runtime(...)(using StringJsonFormat)` into task input.
lazy val fingerprints = taskKey[String]("...")
val previousFingerprints = fingerprints.previous
// inlined
val previousFingerprints: Option[String] = {
InputWrapper.wrapInitTask(
Previous.runtime(...)(using StringJsonFormat)
)
}
- 6c8ee6ea37/main-settings/src/main/scala/sbt/Def.scala (L410-L412)
- 6c8ee6ea37/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala (L468)
However, if the return type of task is a bit more complicated like
Seq[String], it doesn't work:
Scala 3 Compiler's inliner creates a synthetic proxy symbol if the
inlining tree is an application, whose arguments are unstable path.
For example,
lazy val fingerprints = taskKey[Seq[String]]("...")
val previousFingerprints = fingerprints.previous
// inline to
val previousFingerprints: Option[Seq[String]] = {
val x$2$proxy1 = immSeqFormat(StringJsonFormat)
InputWrapper.wrapInitTask(
Previous.runtime(...)(using x$2$proxy1)
)
}
cc7d6db700/compiler/src/dotty/tools/dotc/inlines/Inliner.scala (L324-L329)
However, sbt2's Cont macro captures only `Previous.runtime(...)(using x$2$proxy1)`, while it doesn't capture the proxy definition. Consequently, while sbt macro moves the `Previous.runtime(...)` application as a task input, the proxy definition is left in the task body.
mapN(
(
link / fingerprints,
Previous.runtime(...)(using x$2$proxy1) // here x$2$proxy1 can't be found
)
) {
...
val x$2$proxy1 = ...
}
Then we get:
-- Error: /.../build.sbt:14:59
14 | val previousFingerprints = (link / fingerprints).previous
| ^
|While expanding a macro, a reference to value x$2$proxy1 was used outside the scope where it was defined
**How this PR fixed**
This commit fixes the problem by defining a dedicated Scala3 macro for .previous that summon JsonFormat[A1] inside the macro before constructing the wrapped previous.runtime(...)(using ...) by inliner. The macro insert the found given value tree directly into the previous.runtime(...)(using $found).
This way, Cont macro always moves the Previous.runtime tree along with it's given argument, without leaking compiler-generated inline proxies across scopes.
**Problem**
Like File, Path normally captures the absolute path,
so likely not a good candidate for caching.
**Solution**
Ban it.
---------
Co-authored-by: Anatolii Kmetiuk <anatoliikmt@proton.me>
getFileProperty returned relative File objects when system properties
like sbt.global.base were set to relative paths (e.g. via -sbt-dir
../cache in .sbtopts). This caused an assertion failure in
IO.directoryURI: "Not absolute: ../cache/plugins".
Also fixes misleading scaladoc in SemanticSelector that claimed
`<=1.0` is equivalent to `<1.1.0` — they differ for pre-release
versions like 1.1.0-M1 (#4423).
Fixes#3729
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
**Problem**
When usePipelining := true in a multi-project build, sbt appends
-Ypickle-java and -Ypickle-write <path>/early.jar to scalacOptions
for fast parallel compilation. These flags were being leaked into the Scala
REPL, causing failure on console
**Solution**
Strip pipelining flags before forwarding to the REPL in Deafults.scala.
**Problem**
There seems to be multiple problems around metabuild reloading (reload plugins).
1. Originally reported issue 9006 states that the build can't come back
from reload plugins.
2. During the course of fixing, I discovered that when reload plugins is used
the project name is set to "project" instead of foo-build etc.
3. Also there was a bug in the rootPaths when reload plugins is used,
which results in class not defined error.
**Solution**
1. Fix the plugin context so reload plugin still behaves like a metabuild.
2. Fix the rootPaths.
**Problem**
When build.sbt contains no explicit project definition (e.g. just bare settings with no lazy val root = project.in(file("."))), sbt creates a synthetic root project. In Load.loadTransitive, this synthetic root was processed with expand = false, which prevented AutoPlugin.derivedProjects from being called.
Any plugin relying on derivedProjects to inject projects would fail with "Reference to undefined setting" errors.
The workaround was to add an explicit root project in build.sbt (val root = project.in(file("."))), which caused the Some(root) branch to execute with expand = true.
**Solution**
Removed the expand variable from loadTransitive and pass true directly to processProject. Previously, the Some(root) branch set expand = true while the None branch set expand = false. Since derivedProjects should always be invoked for the root project regardless of whether it was explicitly defined or auto-generated, both branches should behave the same way. Eliminating the variable makes this intent clear.
earlyOutput is a virtual file reference, so passing it directly via
.toString produces a virtual path that scalac cannot resolve. Use
fileConverter.value.toPath() to convert it to an actual filesystem path.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [2.x] feat: Add cacheVersion setting for global cache invalidation
**Problem**
There was no escape hatch to invalidate all task caches when needed.
**Solution**
Add `Global / cacheVersion` setting that incorporates into the cache key
hash. Changing it invalidates all caches. Defaults to reading system
property `sbt.cacheversion`, or else 0L. When 0L, the hash is identical
to the previous behavior (backward compatible).
Fixes#8992
* [2.x] refactor: Simplify BuildWideCacheConfiguration and add cacheVersion test
- Replace auxiliary constructors with default parameter values
- Add unit test verifying cacheVersion invalidates the cache
* [2.x] fix: Restore auxiliary constructors for binary compatibility
* [2.x] test: Improve cacheVersion scripted test and add release note
- Scripted test now verifies cache invalidation via a counter
that increments only when the task body actually executes
- Add release note documenting the cacheVersion setting
publishM2 never wrote maven-metadata-local.xml, which Maven uses to
distinguish local installs from remote artifacts. Without it, Maven
re-downloads remote SNAPSHOTs even when a local copy exists, making
publishM2 effectively broken for SNAPSHOT workflows.
Fixes#2053
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The first paragraph of `help ++` still said "binary compatible" from
before PR #6946 changed the behavior to use SemanticSelector pattern
matching. Update it to match the already-correct second paragraph.
Fixes#6988
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
**Problem**
When dependencyMode := Direct is set, the filtering was applied at
the managedClasspath level, which removed transitive dependencies
from all downstream classpaths including Test / dependencyClasspath.
This caused runtime test failures because transitive deps like
hamcrest-core (pulled in by junit) were missing.
**Solution**
Move the dependencyMode filtering from managedClasspath to a new
filteredDependencyClasspath task, and wire dependencyPicklePath
(the classpath used by the compiler) to use it. Runtime classpaths
like dependencyClasspath and fullClasspath remain unfiltered,
preserving all transitive dependencies for test execution.
Fixes#8989