[2.x] Add a resolvedScalacOptions task that resolves cache placeholders (#9610)

Add a resolvedScalacOptions task that resolves cache placeholders in scalacOptions to absolute machine paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
azdrojowa123 2026-08-19 11:36:38 +02:00 committed by GitHub
parent 8753a98161
commit 3045cd0c46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 77 additions and 2 deletions

View File

@ -1147,6 +1147,9 @@ object Defaults extends BuildCommon with DefExtra {
consoleQuick / scalacOptions := Def.uncached {
Compiler.toConsoleScalacOptions(scalacOptions.value)
},
resolvedScalacOptions := Def.uncached {
Compiler.resolveVirtualizedScalacOptions(scalacOptions.value, rootPaths.value)
},
consoleQuick / forkOptions := Def.uncached((console / forkOptions).value),
discoveredMainClasses := compile
.map(discoverMainClasses)

View File

@ -217,6 +217,8 @@ object Keys {
val autoCompilerPlugins = settingKey[Boolean]("If true, enables automatically generating -Xplugin arguments to the compiler based on the classpath for the " + CompilerPlugin.name + " configuration.").withRank(AMinusSetting)
val maxErrors = settingKey[Int]("The maximum number of errors, such as compile errors, to list.").withRank(ASetting)
val scalacOptions = taskKey[Seq[String]]("Options for the Scala compiler.").withRank(BPlusTask)
@transient
val resolvedScalacOptions = taskKey[Seq[String]]("scalacOptions with cache placeholders (e.g. ${CSR_CACHE}) resolved to absolute machine paths.").withRank(BPlusTask)
val javacOptions = taskKey[Seq[String]]("Options for the Java compiler.").withRank(BPlusTask)
val incOptions = taskKey[IncOptions]("Options for the incremental compiler.").withRank(BTask)
val extraIncOptions = taskKey[Seq[(String, String)]]("Extra options for the incremental compiler").withRank(CTask)
@ -509,6 +511,7 @@ object Keys {
val bspBuildTargetRun = inputKey[Unit]("Corresponds to buildTarget/run request").withRank(DTask)
val bspBuildTargetCleanCache = inputKey[Unit]("Corresponds to buildTarget/cleanCache request").withRank(DTask)
val bspBuildTargetScalacOptions = inputKey[Unit]("").withRank(DTask)
@transient
val bspBuildTargetScalacOptionsItem = taskKey[ScalacOptionsItem]("").withRank(DTask)
val bspBuildTargetJavacOptions = inputKey[Unit]("Implementation of buildTarget/javacOptions").withRank(DTask)
val bspBuildTargetJavacOptionsItem = taskKey[JavacOptionsItem]("Item of buildTarget/javacOptions").withRank(DTask)

View File

@ -219,9 +219,11 @@ object BuildServerProtocol {
val items = bspBuildTargetScalacOptionsItem.result.all(filter).value
val appProvider = appConfiguration.value.provider()
val sbtJars = appProvider.mainClasspath()
val rootPaths = Keys.rootPaths.value
val buildItems = workspace.builds.map { build =>
val plugins: LoadedPlugins = build._2.unit.plugins
val scalacOptions = plugins.pluginData.scalacOptions
val scalacOptions =
Compiler.resolveVirtualizedScalacOptions(plugins.pluginData.scalacOptions, rootPaths)
val pluginClasspath = plugins.classpath
val converter = plugins.pluginData.converter
val classpath =
@ -330,7 +332,7 @@ object BuildServerProtocol {
bspBuildTargetRun := bspRunTask.evaluated,
bspBuildTargetScalacOptionsItem := {
val target = Keys.bspTargetIdentifier.value
val scalacOptions = Keys.scalacOptions.value.toVector
val scalacOptions = Keys.resolvedScalacOptions.value.toVector
val classDirectory = Keys.classDirectory.value
val classpath = classpathTask.value
ScalacOptionsItem(target, scalacOptions, classpath, classDirectory.toURI)

View File

@ -0,0 +1,26 @@
val check = taskKey[Unit]("Verify resolvedScalacOptions resolves cache placeholders (#9578).")
scalaVersion := "2.13.16"
addCompilerPlugin(("org.typelevel" % "kind-projector" % "0.13.3").cross(CrossVersion.full))
check := Def.uncached {
val raw = (Compile / scalacOptions).value
val resolved = (Compile / resolvedScalacOptions).value
assert(
raw.exists(_.contains("${CSR_CACHE}")),
s"expected a virtualized coursier-cache placeholder in raw scalacOptions, got: $raw"
)
assert(
resolved.forall(!_.contains("${")),
s"resolvedScalacOptions still contains a cache placeholder: $resolved"
)
val pluginArg = resolved
.find(_.startsWith("-Xplugin:"))
.getOrElse(sys.error(s"no -Xplugin entry in resolvedScalacOptions: $resolved"))
val jar = new java.io.File(pluginArg.stripPrefix("-Xplugin:"))
assert(jar.isAbsolute, s"resolved -Xplugin path is not absolute: $jar")
assert(jar.exists, s"resolved -Xplugin jar does not exist: $jar")
}

View File

@ -0,0 +1,2 @@
# resolvedScalacOptions must resolve ${CSR_CACHE} placeholders to absolute machine paths (#9578)
> check

View File

@ -38,6 +38,12 @@ lazy val util = project.settings(
Compile / classDirectory := baseDirectory.value / "classes"
)
lazy val scalacOptionsPlugin = project
.in(file("scalac-options-plugin"))
.settings(
addCompilerPlugin(("org.typelevel" % "kind-projector" % "0.13.3").cross(CrossVersion.full))
)
lazy val diagnostics = project
lazy val javaProj = project

View File

@ -432,6 +432,39 @@ class BuildServerTest extends AbstractServerTest {
)
}
test("buildTarget/scalacOptions resolves cache placeholders (#9578)") {
val buildTargets = Seq(buildTargetUri("scalacOptionsPlugin", "Compile"))
val id = scalacOptions(buildTargets)
val result =
svr.session.waitForResultInResponseMsg[ScalacOptionsResult](30.seconds, id).get
val options = result.items.head.options
assert(
options.forall(!_.contains("${")),
s"buildTarget/scalacOptions returned an unresolved cache placeholder: $options"
)
// The kind-projector compiler plugin must be reported as a concrete, absolute,
// existing jar path not a virtualized ${CSR_CACHE}/... reference.
val xplugin = options
.find(_.startsWith("-Xplugin:"))
.getOrElse(fail(s"expected a -Xplugin compiler-plugin option, got: $options"))
val pluginPath = xplugin.stripPrefix("-Xplugin:")
val pluginFile = new File(pluginPath)
assert(
pluginFile.isAbsolute,
s"resolved -Xplugin path is not absolute: $pluginPath"
)
assert(
pluginFile.getName.startsWith("kind-projector"),
s"expected the kind-projector plugin jar, got: $pluginPath"
)
assert(
pluginFile.exists,
s"resolved -Xplugin jar does not exist on disk: $pluginPath"
)
}
test("buildTarget/cleanCache") {
def classFile = svr.baseDirectory.toPath.resolve(
"target/out/jvm/scala-2.13.11/runandtest/classes/main/Main.class"