[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.
This commit is contained in:
Matt Dziuban 2026-04-18 03:29:45 -04:00 committed by Eugene Yokota
parent 2b0ef70a26
commit 02845c57f7
2 changed files with 43 additions and 1 deletions

View File

@ -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

View File

@ -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). */