From 02845c57f75e572133625bcba05f753898e43e9b Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Sat, 18 Apr 2026 03:29:45 -0400 Subject: [PATCH] [2.x] fix: Use rootPaths to replace virtual paths in console and doc scalac options. (#9110) Rather than using the FileConverter to replace virtual paths, this uses the rootPaths directly. It only replaces a virtual path in a scalac option if the given segment of the option begins with the root path key. --- .../main/scala/sbt/internal/Compiler.scala | 24 ++++++++++++++++++- .../internal/CompilerConsoleOptsTest.scala | 20 ++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/Compiler.scala b/main/src/main/scala/sbt/internal/Compiler.scala index a3bf5f0cf..35d54940d 100644 --- a/main/src/main/scala/sbt/internal/Compiler.scala +++ b/main/src/main/scala/sbt/internal/Compiler.scala @@ -344,6 +344,7 @@ object Compiler: Def.task { val s = Keys.streams.value val conv = Keys.fileConverter.value + val rootPaths = Keys.rootPaths.value val cside = (task / Keys.clientSide).value val depsJars = (task / Keys.externalDependencyClasspath).value.toVector .map(_.data) @@ -362,12 +363,14 @@ object Compiler: workingDir, conv, ) + val consoleScalacOptions = + resolveVirtualizedScalacOptions((task / Keys.scalacOptions).value, rootPaths) val param = ConsoleInfo( ArrayList(toolJars.asJava), ArrayList(bridgeJars.toVector.map(vf => conv.toPath(vf).toUri()).asJava), ArrayList(), ArrayList(Attributed.data(cp).toVector.map(vf => conv.toPath(vf).toUri()).asJava), - ArrayList((task / Keys.scalacOptions).value.asJava), + ArrayList(consoleScalacOptions.asJava), (task / Keys.initialCommands).value, (task / Keys.cleanupCommands).value, ) @@ -446,4 +449,23 @@ object Compiler: case head +: rest => head +: toConsoleScalacOptions(rest) case _ => Seq.empty + /** + * Converts mapped virtual file ids in compiler plugin options back to machine paths. + * + * Compiler plugin options are often encoded using `FileConverter.toVirtualFile` to keep + * paths portable in persisted settings (for example `-Xplugin:${CSR_CACHE}/...`). Before we + * launch tools that expect concrete filesystem paths (forked console, scaladoc), these ids + * must be resolved using the `rootPaths`. + */ + private[sbt] def resolveVirtualizedScalacOptions( + options: Seq[String], + rootPaths: Map[String, Path] + ): Seq[String] = + def convertValue(value: String): String = + rootPaths.find((key, _) => value.startsWith(s"$${$key}/")) match + case Some((key, p)) => p.resolve(value.stripPrefix(s"$${$key}/")).toString() + case None => value + + options.map(_.split(":").map(_.split(",").map(convertValue).mkString(",")).mkString(":")) + end Compiler diff --git a/main/src/test/scala/sbt/internal/CompilerConsoleOptsTest.scala b/main/src/test/scala/sbt/internal/CompilerConsoleOptsTest.scala index 310aad604..1812e0417 100644 --- a/main/src/test/scala/sbt/internal/CompilerConsoleOptsTest.scala +++ b/main/src/test/scala/sbt/internal/CompilerConsoleOptsTest.scala @@ -2,6 +2,7 @@ package sbt.internal import hedgehog.* import hedgehog.runner.* +import java.nio.file.Files /** * Tests for [[Compiler.toConsoleScalacOptions]] — pipelining flags must be stripped before @@ -102,6 +103,10 @@ object CompilerConsoleOptsTest extends Properties: Seq("-encoding", "utf8") ) ), + example( + "virtualized compiler plugin paths are resolved for tool invocation", + checkResolvedVirtualizedOptions + ), // ── property-based cases ────────────────────────────────────────────────── @@ -129,6 +134,21 @@ object CompilerConsoleOptsTest extends Properties: .log(s"expected: $expected") .log(s"got: $got") + private def checkResolvedVirtualizedOptions: Result = + val cacheRoot = Files.createTempDirectory("compiler-console-opts") + val rootPaths = Map("CSR_CACHE" -> cacheRoot) + val converter = _root_.sbt.internal.inc.MappedFileConverter(rootPaths, allowMachinePath = false) + val pluginJar = cacheRoot.resolve("plugins/acyclic.jar") + val pluginRef = converter.toVirtualFile(pluginJar).toString + val input = Seq(s"-Xplugin:$pluginRef", "-P:acyclic:force") + val expected = Seq(s"-Xplugin:${pluginJar.toString}", "-P:acyclic:force") + val got = Compiler.resolveVirtualizedScalacOptions(input, rootPaths) + Result + .assert(got == expected) + .log(s"input: $input") + .log(s"expected: $expected") + .log(s"got: $got") + private val pipeliningFlags = List("-Ypickle-java", "-Ypickle-write") /** Generate an arbitrary scalac-option token (flag or path-like argument). */