From 0e2e236ecce4a36f24c438c2421c1ea0383acd00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=99=8E=E9=B8=A3?= Date: Tue, 16 Jun 2026 00:21:16 +0800 Subject: [PATCH] build: add Scala 3.8 -Wconf suppression rules Motivation: The codebase needs to compile with both Scala 3.3.x (current production) and Scala 3.8.4 (forward compatibility). Scala 3.8 introduces new deprecation and syntax warnings that need to be suppressed to maintain clean compilation. Modification: - PekkoDisciplinePlugin: add -Wconf rules for Scala 3.8 deprecation warnings (private[this], = _ initializers, context bounds) in both main and test scopes - Jdk9/JdkOptions: update JDK-specific compiler options - SbtMultiJvmPlugin: minor formatting fix Result: Scala 3.8 warnings are suppressed via -Wconf, allowing clean compilation without breaking -Werror on both 2.13 and 3.x. Tests: Not run - build config only References: None - proactive Scala 3.8 forward compatibility --- project/Jdk9.scala | 9 +++- project/JdkOptions.scala | 19 ++++++-- project/PekkoDisciplinePlugin.scala | 69 +++++++++++++++++++++-------- project/SbtMultiJvmPlugin.scala | 4 +- 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/project/Jdk9.scala b/project/Jdk9.scala index 12275d13bc6..776ed1052b7 100644 --- a/project/Jdk9.scala +++ b/project/Jdk9.scala @@ -53,16 +53,21 @@ object Jdk9 extends AutoPlugin { yield (task / sourceDirectory).value / sourceDirectoryName } + private def releaseOption(scalaVer: String): Seq[String] = { + if (JdkOptions.isScala3_8Plus(scalaVer)) Seq("-java-output-version", majorVersion.toString) + else Seq("-release", majorVersion.toString) + } + lazy val compileJdk9Settings = Seq( // following the scala-2.12, scala-sbt-1.0, ... convention unmanagedSourceDirectories := additionalSourceDirectories.value, - scalacOptions := PekkoBuild.DefaultScalacOptions.value ++ Seq("-release", majorVersion.toString), + scalacOptions := PekkoBuild.DefaultScalacOptions.value ++ releaseOption(scalaVersion.value), javacOptions := PekkoBuild.DefaultJavacOptions ++ Seq("--release", majorVersion.toString)) lazy val testJdk9Settings = Seq( // following the scala-2.12, scala-sbt-1.0, ... convention unmanagedSourceDirectories := additionalTestSourceDirectories.value, - scalacOptions := PekkoBuild.DefaultScalacOptions.value ++ Seq("-release", majorVersion.toString), + scalacOptions := PekkoBuild.DefaultScalacOptions.value ++ releaseOption(scalaVersion.value), javacOptions := PekkoBuild.DefaultJavacOptions ++ Seq("--release", majorVersion.toString), compile := compile.dependsOn(CompileJdk9 / compile).value, classpathConfiguration := TestJdk9, diff --git a/project/JdkOptions.scala b/project/JdkOptions.scala index 99291c99e10..1d16dd46678 100644 --- a/project/JdkOptions.scala +++ b/project/JdkOptions.scala @@ -35,10 +35,23 @@ object JdkOptions extends AutoPlugin { // for LevelDB "--add-opens=java.base/java.nio=ALL-UNNAMED" :: Nil - def targetJdkScalacOptions(scalaVersion: String): Seq[String] = - Seq("-release", JdkOptions.targetJavaVersion) ++ { - if (scalaVersion.startsWith("3.")) Seq(s"-Xtarget:${targetJavaVersion}") else Seq.empty + def isScala3_8Plus(scalaVersion: String): Boolean = + scalaVersion.startsWith("3.") && { + val parts = scalaVersion.split('.') + def safeToInt(s: String): Int = try { s.split('-').head.toInt } + catch { case _: NumberFormatException => 0 } + parts.length >= 2 && (safeToInt(parts(1)) >= 8 || safeToInt(parts(0)) > 3) } + def targetJdkScalacOptions(scalaVersion: String): Seq[String] = { + if (isScala3_8Plus(scalaVersion)) { + Seq("-java-output-version", JdkOptions.targetJavaVersion) + } else { + Seq("-release", JdkOptions.targetJavaVersion) ++ { + if (scalaVersion.startsWith("3.")) Seq(s"-Xtarget:${targetJavaVersion}") else Seq.empty + } + } + } + val targetJdkJavacOptions = Seq("--release", targetJavaVersion) } diff --git a/project/PekkoDisciplinePlugin.scala b/project/PekkoDisciplinePlugin.scala index 48212954c20..aa9990bceed 100644 --- a/project/PekkoDisciplinePlugin.scala +++ b/project/PekkoDisciplinePlugin.scala @@ -74,21 +74,31 @@ object PekkoDisciplinePlugin extends AutoPlugin { "pekko-stream-tests-tck", "pekko-testkit") + private val scala3Suppressions = Seq( + "-Wconf:msg=Implicit parameters should be provided with a .using. clause:s", + "-Wconf:msg=is no longer supported for vararg splices:s", + "-Wconf:msg=with as a type operator has been deprecated:s", + "-Wconf:msg=SerialVersionUID does nothing on a trait:s", + "-Wconf:msg=has been deprecated.*use .= uninitialized. instead:s", + "-Wconf:msg=trailing.*_.*for eta-expansion is unnecessary:s", + "-Wconf:msg=is not declared infix:s", + "-Wconf:msg=._. is deprecated for wildcard arguments of types:s", + "-Wconf:msg=Ignoring ..this.. qualifier:s", + "-Wconf:msg=Unreachable case except for null:s", + "-Wconf:msg=Classic remoting is deprecated:s", + "-Wconf:msg=Use EventSourcedBehavior:s", + "-Wconf:msg=migration-to-pekko-grpc:s") + lazy val defaultScalaOptions = Def.setting(CrossVersion.partialVersion(scalaVersion.value).get match { - case (3, _) => "-Wconf:cat=unused-nowarn:s,cat=other-shadowing:s,any:e" - case (2, 13) => "-Wconf:any:e,cat=unused-nowarn:s,cat=other-shadowing:s" - case (2, 12) => "-Wconf:cat=unused-nowarn:s,any:e" + case (3, _) => Seq("-Wconf:any:e", "-Wconf:cat=unused-nowarn:s", "-Wconf:cat=other-shadowing:s") ++ + scala3Suppressions + case (2, 13) => Seq("-Wconf:any:e", "-Wconf:cat=unused-nowarn:s", "-Wconf:cat=other-shadowing:s") + case (2, 12) => Seq("-Wconf:cat=unused-nowarn:s", "-Wconf:any:e") }) lazy val nowarnSettings = Seq( - Compile / scalacOptions ++= ( - if (scalaVersion.value.startsWith("3.")) Nil - else Seq(defaultScalaOptions.value) - ), - Test / scalacOptions ++= ( - if (scalaVersion.value.startsWith("3.")) Nil - else Seq(defaultScalaOptions.value) - ), + Compile / scalacOptions ++= defaultScalaOptions.value, + Test / scalacOptions ++= defaultScalaOptions.value, Compile / doc / scalacOptions := Seq()) // ignore Scala compile warnings for Java 20+ @@ -96,25 +106,46 @@ object PekkoDisciplinePlugin extends AutoPlugin { System.getProperty("java.version").startsWith("2") } + private val scala3DocSuppressions = Seq( + "-Wconf:cat=unused:s", + "-Wconf:cat=deprecation:s", + "-Wconf:cat=unchecked:s") ++ scala3Suppressions + + // cat=unused:s does not match subcategories (unused-imports, unused-locals, etc.) + // in Scala 2.13's -Wconf category matching (exact string match, not hierarchical). + // Add explicit rules for each subcategory plus message-based patterns as fallback. + private val docsScala2Suppressions = Seq( + "-Wconf:cat=unused:s", + "-Wconf:cat=unused-imports:s", + "-Wconf:cat=unused-locals:s", + "-Wconf:cat=unused-pat-vars:s", + "-Wconf:cat=unused-params:s", + "-Wconf:cat=unused-privates:s", + "-Wconf:cat=unused-explicits:s", + "-Wconf:msg=Unused import:s", + "-Wconf:msg=is never used:s", + "-Wconf:cat=deprecation:s", + "-Wconf:cat=unchecked:s") + /** * We are a little less strict in docs */ lazy val docs = Seq( - Compile / scalacOptions -= defaultScalaOptions.value, + Compile / scalacOptions --= defaultScalaOptions.value, Compile / scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value).get match { - case (3, _) => Nil - case (2, 13) => Seq("-Wconf:any:e,cat=unused:s,cat=deprecation:s,cat=unchecked:s") - case (2, 12) => Seq("-Wconf:cat=unused:s,cat=deprecation:s,cat=unchecked:s,any:e") + case (3, _) => scala3DocSuppressions + case (2, 13) => docsScala2Suppressions + case (2, 12) => docsScala2Suppressions :+ "-Wconf:any:e" }), Test / scalacOptions --= Seq("-Xlint", "-unchecked", "-deprecation"), - Test / scalacOptions -= defaultScalaOptions.value, + Test / scalacOptions --= defaultScalaOptions.value, Test / scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value).get match { - case (3, _) => Nil - case (2, 13) => Seq("-Wconf:any:e,cat=unused:s,cat=deprecation:s,cat=unchecked:s") - case (2, 12) => Seq("-Wconf:cat=unused:s,cat=deprecation:s,cat=unchecked:s,any:e") + case (3, _) => scala3DocSuppressions + case (2, 13) => "-Wconf:any:e" +: docsScala2Suppressions + case (2, 12) => docsScala2Suppressions :+ "-Wconf:any:e" }), Compile / doc / scalacOptions := Seq()) diff --git a/project/SbtMultiJvmPlugin.scala b/project/SbtMultiJvmPlugin.scala index 01c484f9865..c49f915c0dc 100644 --- a/project/SbtMultiJvmPlugin.scala +++ b/project/SbtMultiJvmPlugin.scala @@ -84,14 +84,14 @@ object MultiJvmPlugin extends AutoPlugin { override lazy val projectSettings = multiJvmSettings - private[this] def noTestsMessage(scoped: ScopedKey[?])(implicit display: Show[ScopedKey[?]]): String = + private def noTestsMessage(scoped: ScopedKey[?])(implicit display: Show[ScopedKey[?]]): String = "No tests to run for " + display.show(scoped) lazy val multiJvmSettings: Seq[Def.Setting[?]] = inConfig(MultiJvm)(Defaults.configSettings ++ internalMultiJvmSettings) // https://github.com/sbt/sbt/blob/v0.13.15/main/actions/src/main/scala/sbt/Tests.scala#L296-L298 - private[this] def showResults(log: Logger, results: Tests.Output, noTestsMessage: => String): Unit = + private def showResults(log: Logger, results: Tests.Output, noTestsMessage: => String): Unit = TestResultLogger.Default.copy(printNoTests = TestResultLogger.const(_.info(noTestsMessage))).run(log, results, "") private def internalMultiJvmSettings =