[2.x] fix: include compileOrder and pipelining in compile cache key (#9749)

compileOrder and usePipelining were missing from CompileInputs2, so two builds differing only in those settings shared one cache entry and replayed each other's results. Adds both to the key, with a unit test on the hash and a scripted test that fails without the change.
This commit is contained in:
Stas Shevchenko
2026-09-12 02:20:06 -04:00
committed by GitHub
parent 9532b51190
commit ea673bf06f
7 changed files with 108 additions and 2 deletions
+4
View File
@@ -1005,6 +1005,8 @@ object Defaults extends BuildCommon with DefExtra:
c.toVirtualFile(inputs.setup.cacheFile.toPath),
extraIncOptions.value.toVector,
scalaVersion.value,
compileOrder.value.name,
incOptions.value.pipelining,
)
}
)
@@ -2570,6 +2572,8 @@ object Defaults extends BuildCommon with DefExtra:
c.toVirtualFile(inputs.setup.cacheFile.toPath),
incrementalOptions,
scalaVersion.value,
compileOrder.value.name,
incOptions.value.pipelining,
)
},
bspCompileTask :=
@@ -14,6 +14,8 @@ case class CompileInputs2(
cachePath: VirtualFileRef,
incrementalOptions: Vector[(String, String)],
scalaVersion: String,
compileOrder: String,
pipelining: Boolean,
)
object CompileInputs2:
@@ -23,7 +25,7 @@ object CompileInputs2:
CompileInputs2,
Vector[HashedVirtualFileRef] :*: Vector[HashedVirtualFileRef] :*: Vector[String] :*:
Vector[String] :*: VirtualFileRef :*: VirtualFileRef :*: Vector[(String, String)] :*:
String :*: LNil
String :*: String :*: Boolean :*: LNil
] =
LList.iso(
{ (v: CompileInputs2) =>
@@ -35,12 +37,14 @@ object CompileInputs2:
("cachePath", v.cachePath) :*:
("incrementalOptions", v.incrementalOptions) :*:
("scalaVersion", v.scalaVersion) :*:
("compileOrder", v.compileOrder) :*:
("pipelining", v.pipelining) :*:
LNil
},
{
(in: Vector[HashedVirtualFileRef] :*: Vector[HashedVirtualFileRef] :*: Vector[String] :*:
Vector[String] :*: VirtualFileRef :*: VirtualFileRef :*: Vector[(String, String)] :*:
String :*: LNil) =>
String :*: String :*: Boolean :*: LNil) =>
CompileInputs2(
in.head,
in.tail.head,
@@ -50,6 +54,8 @@ object CompileInputs2:
in.tail.tail.tail.tail.tail.head,
in.tail.tail.tail.tail.tail.tail.head,
in.tail.tail.tail.tail.tail.tail.tail.head,
in.tail.tail.tail.tail.tail.tail.tail.tail.head,
in.tail.tail.tail.tail.tail.tail.tail.tail.tail.head,
)
}
)
@@ -0,0 +1,53 @@
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal
import hedgehog.*
import hedgehog.runner.*
import _root_.sbt.util.CacheImplicits.given
import _root_.sbt.util.DigestHasher
import xsbti.{ HashedVirtualFileRef, VirtualFileRef }
/**
* The compile action cache is keyed on [[CompileInputs2]], so every setting that changes the
* compile outcome has to be part of it (#9748).
*/
object CompileInputs2HashTest extends Properties:
override def tests: List[Test] = List(
example("compileOrder changes the hash", compileOrderChangesHash),
example("pipelining changes the hash", pipeliningChangesHash),
example("identical inputs hash the same", identicalInputsHashTheSame),
)
private def base = CompileInputs2(
classpath = Vector(HashedVirtualFileRef.of("${BASE}/lib/dep.jar", "sha256-abc", 1L)),
sources = Vector(HashedVirtualFileRef.of("${BASE}/src/main/scala/A.scala", "sha256-def", 2L)),
scalacOptions = Vector("-deprecation"),
javacOptions = Vector.empty,
outputPath = VirtualFileRef.of("${BASE}/target/classes"),
cachePath = VirtualFileRef.of("${BASE}/target/inc_compile.zip"),
incrementalOptions = Vector.empty,
scalaVersion = "2.13.18",
compileOrder = "Mixed",
pipelining = false,
)
private def compileOrderChangesHash: Result =
val mixed = DigestHasher.hashUnsafe(base)
val javaThenScala = DigestHasher.hashUnsafe(base.copy(compileOrder = "JavaThenScala"))
Result.assert(mixed != javaThenScala)
private def pipeliningChangesHash: Result =
val off = DigestHasher.hashUnsafe(base)
val on = DigestHasher.hashUnsafe(base.copy(pipelining = true))
Result.assert(off != on)
private def identicalInputsHashTheSame: Result =
DigestHasher.hashUnsafe(base) ==== DigestHasher.hashUnsafe(base)
end CompileInputs2HashTest
+21
View File
@@ -0,0 +1,21 @@
Global / localCacheDirectory := baseDirectory.value / "diskcache"
val checkClasses = taskKey[Unit]("asserts the Java class was compiled")
// A distinct project id keeps this fixture's output paths from colliding with same-named
// sibling fixtures in scripted's shared batch directory.
lazy val compileOrderKey = project
.in(file("."))
.settings(
scalaVersion := "3.9.0",
compileOrder := (
if ((baseDirectory.value / "mixed.marker").exists) CompileOrder.Mixed
else CompileOrder.JavaThenScala
),
)
checkClasses := Def.uncached {
val dir = (compileOrderKey / Compile / classDirectory).value
val cls = dir / "example" / "J.class"
assert(cls.exists, s"$cls does not exist")
}
@@ -0,0 +1,7 @@
package example;
public class J {
public static String go() {
return new S().hello();
}
}
@@ -0,0 +1,4 @@
package example
class S:
def hello: String = "hi"
+11
View File
@@ -0,0 +1,11 @@
# Regression for #9748: compileOrder has to take part in the compile cache key. Under
# JavaThenScala javac runs before the Scala class exists and the build fails. Flipping to
# Mixed has to recompile, not replay that failure from an entry filed under the same key.
-> compile
$ touch mixed.marker
> reload
> compile
> checkClasses