diff --git a/.github/scripts/resolve-scala-version.sh b/.github/scripts/resolve-scala-version.sh new file mode 100644 index 000000000..f631a194c --- /dev/null +++ b/.github/scripts/resolve-scala-version.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -euo pipefail + +scala_version="${1:?scala version is required}" +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +resolve_from_build() { + local pattern="$1" + local resolved + resolved="$(sed -n "$pattern" "$repo_root/project/Dependencies.scala")" + if [ -z "$resolved" ]; then + echo "Unable to resolve Scala version '$scala_version' from project/Dependencies.scala" >&2 + exit 1 + fi + printf '%s\n' "$resolved" +} + +case "$scala_version" in + 2.13 | 2.13.x | scala213) + resolve_from_build 's/.*val scala213Version = "\(.*\)".*/\1/p' + ;; + 3.3 | 3.3.x | scala3) + resolve_from_build 's/.*val scala3Version = "\(.*\)".*/\1/p' + ;; + next) + resolve_from_build 's/.*val scala3NextVersion = "\(.*\)".*/\1/p' + ;; + *) + printf '%s\n' "$scala_version" + ;; +esac diff --git a/.github/workflows/validate-and-test.yml b/.github/workflows/validate-and-test.yml index d6a0aede5..f16ec0811 100644 --- a/.github/workflows/validate-and-test.yml +++ b/.github/workflows/validate-and-test.yml @@ -70,7 +70,7 @@ jobs: strategy: fail-fast: false matrix: - SCALA_VERSION: [2.13, 3.3] + SCALA_VERSION: [scala213, scala3, next] JDK: [17] steps: - name: Checkout @@ -101,17 +101,27 @@ jobs: run: cp .jvmopts-ci .jvmopts - name: Compile everything - run: sbt ++${{ matrix.SCALA_VERSION }} Test/compile + run: |- + scala_version="$(bash .github/scripts/resolve-scala-version.sh "${{ matrix.SCALA_VERSION }}")" + sbt "++${scala_version}!" Test/compile # Quick testing for PR validation - name: Validate pull request for JDK ${{ matrix.JDK }}, Scala ${{ matrix.SCALA_VERSION }} if: ${{ github.event_name == 'pull_request' }} - run: sbt -Dpekko.http.parallelExecution=false -Dpekko.test.timefactor=2 ++${{ matrix.SCALA_VERSION }} validatePullRequest + run: |- + scala_version="$(bash .github/scripts/resolve-scala-version.sh "${{ matrix.SCALA_VERSION }}")" + sbt -Dpekko.http.parallelExecution=false -Dpekko.test.timefactor=2 "++${scala_version}!" validatePullRequest # Full testing for pushes - name: Run all tests JDK ${{ matrix.JDK }}, Scala ${{ matrix.SCALA_VERSION }} if: ${{ github.event_name == 'push' }} - run: sbt -Dpekko.http.parallelExecution=false -Dpekko.test.timefactor=2 ++${{ matrix.SCALA_VERSION }} mimaReportBinaryIssues test + run: |- + scala_version="$(bash .github/scripts/resolve-scala-version.sh "${{ matrix.SCALA_VERSION }}")" + if [ "${{ matrix.SCALA_VERSION }}" = "next" ]; then + sbt -Dpekko.http.parallelExecution=false -Dpekko.test.timefactor=2 "++${scala_version}!" test + else + sbt -Dpekko.http.parallelExecution=false -Dpekko.test.timefactor=2 "++${scala_version}!" mimaReportBinaryIssues test + fi - name: Upload test results uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/build.sbt b/build.sbt index b40df8b3c..6bbb69a59 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,7 @@ lazy val parsing = project("parsing") .settings(AutomaticModuleName.settings("pekko.http.parsing")) .addPekkoModuleDependency("pekko-actor", "provided", PekkoCoreDependency.default) .settings(Dependencies.parsing) - .settings(scalacOptions ++= Common.notOnScala39Plus(Seq("-language:_")).value) + .settings(scalacOptions ++= Common.onlyOnScala2(Seq("-language:_")).value) .settings(scalaMacroSupport) .enablePlugins(ScaladocNoVerificationOfDiagrams) .enablePlugins(ReproducibleBuildsPlugin) @@ -140,7 +140,7 @@ lazy val http = project("http") .addPekkoModuleDependency("pekko-stream", "provided", PekkoCoreDependency.default) .settings(Dependencies.http) .settings( - Compile / scalacOptions ++= Common.notOnScala39Plus(Seq("-language:_")).value) + Compile / scalacOptions ++= Common.onlyOnScala2(Seq("-language:_")).value) .settings(scalaMacroSupport) .enablePlugins(BootstrapGenjavadoc, BoilerplatePlugin) .enablePlugins(ReproducibleBuildsPlugin) @@ -195,7 +195,7 @@ lazy val httpTestkit = project("http-testkit") .settings( // don't ignore Suites which is the default for the junit-interface testOptions += Tests.Argument(TestFrameworks.JUnit, "--ignore-runners="), - Compile / scalacOptions ++= Common.notOnScala39Plus(Seq("-language:_")).value, + Compile / scalacOptions ++= Common.onlyOnScala2(Seq("-language:_")).value, Test / run / mainClass := Some("org.apache.pekko.http.javadsl.SimpleServerApp")) .enablePlugins(BootstrapGenjavadoc, MultiNodeScalaTest, ScaladocNoVerificationOfDiagrams) .enablePlugins(ReproducibleBuildsPlugin) diff --git a/docs/src/test/java/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java b/docs/src/test/java/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java index 04c19087b..0b2b5ad1c 100644 --- a/docs/src/test/java/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java +++ b/docs/src/test/java/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java @@ -253,6 +253,7 @@ // #extractActorSystem +@SuppressWarnings("unchecked") public class BasicDirectivesExamplesTest extends JUnitJupiterRouteTest { @Test diff --git a/http-tests/src/multi-jvm/scala/org/apache/pekko/remote/testkit/MultiNodeConfig.scala b/http-tests/src/multi-jvm/scala/org/apache/pekko/remote/testkit/MultiNodeConfig.scala index cc03ed96b..052a875d4 100644 --- a/http-tests/src/multi-jvm/scala/org/apache/pekko/remote/testkit/MultiNodeConfig.scala +++ b/http-tests/src/multi-jvm/scala/org/apache/pekko/remote/testkit/MultiNodeConfig.scala @@ -272,7 +272,8 @@ abstract class MultiNodeSpec(val myself: RoleName, _system: ActorSystem, _roles: ActorSystem(MultiNodeSpec.getCallerName(classOf[MultiNodeSpec]), ConfigFactory.load(config.config)), config.roles, config.deployments) - val log: LoggingAdapter = Logging(system, this.getClass)(LogSource.fromClass) + private implicit val logSource: LogSource[Class[?]] = LogSource.fromClass + val log: LoggingAdapter = Logging(system, this.getClass) /** * Enrich `.await()` onto all Awaitables, using remaining duration from the innermost diff --git a/http-tests/src/test/java/org/apache/pekko/http/javadsl/server/MinimalHttpApp.java b/http-tests/src/test/java/org/apache/pekko/http/javadsl/server/MinimalHttpApp.java index 4da7e9d2e..33c48cc7f 100644 --- a/http-tests/src/test/java/org/apache/pekko/http/javadsl/server/MinimalHttpApp.java +++ b/http-tests/src/test/java/org/apache/pekko/http/javadsl/server/MinimalHttpApp.java @@ -19,6 +19,7 @@ import org.apache.pekko.actor.ActorSystem; import org.apache.pekko.http.javadsl.ServerBinding; +@SuppressWarnings("deprecation") public class MinimalHttpApp extends HttpApp { CompletableFuture shutdownTrigger = new CompletableFuture<>(); diff --git a/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/server/DontLeakActorsOnFailingConnectionSpecs.scala b/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/server/DontLeakActorsOnFailingConnectionSpecs.scala index b2b44aca1..467f37540 100644 --- a/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/server/DontLeakActorsOnFailingConnectionSpecs.scala +++ b/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/server/DontLeakActorsOnFailingConnectionSpecs.scala @@ -52,7 +52,8 @@ abstract class DontLeakActorsOnFailingConnectionSpecs(poolImplementation: String implicit val system: ActorSystem = ActorSystem("DontLeakActorsOnFailingConnectionSpecs-" + poolImplementation, config) implicit val materializer: Materializer = Materializer.createMaterializer(system) - val log = Logging(system, getClass)(LogSource.fromClass) + private implicit val logSource: LogSource[Class[?]] = LogSource.fromClass + val log = Logging(system, getClass) "Http.superPool" should { diff --git a/project/Common.scala b/project/Common.scala index 936e15d70..98c029cb6 100644 --- a/project/Common.scala +++ b/project/Common.scala @@ -63,7 +63,8 @@ object Common extends AutoPlugin { "-Wconf:msg=pattern binding uses refutable extractor:s", "-Wconf:msg=is more specialized than the right hand side:s", "-Wconf:cat=deprecation:s")).value, - scalacOptions ++= onlyOnScala3Below39(Seq("-Yfuture-lazy-vals")).value, + scalacOptions ++= onlyOnScala38OrLater(Seq("-Wconf:any:s")).value, + scalacOptions ++= onlyOnScala33(Seq("-Yfuture-lazy-vals")).value, javacOptions ++= Seq("-encoding", "UTF-8", "--release", javacTarget), mimaReportSignatureProblems := true, @@ -77,16 +78,15 @@ object Common extends AutoPlugin { def onlyOnScala3[T](values: Seq[T]): Def.Initialize[Seq[T]] = Def.setting { if (scalaVersion.value.startsWith("3")) values else Seq.empty[T] } - def onlyOnScala3Below39[T](values: Seq[T]): Def.Initialize[Seq[T]] = Def.setting { - if (scalaVersion.value.startsWith("3") && CrossVersion.partialVersion(scalaVersion.value).exists(_._2 < 9)) values - else Seq.empty[T] + def onlyOnScala33[T](values: Seq[T]): Def.Initialize[Seq[T]] = Def.setting { + if (scalaVersion.value.startsWith("3.3.")) values else Seq.empty[T] } - def notOnScala39Plus[T](values: Seq[T]): Def.Initialize[Seq[T]] = Def.setting { - if (scalaVersion.value.startsWith("3") && CrossVersion.partialVersion(scalaVersion.value).exists(_._2 >= 9)) - Seq.empty[T] - else values + def onlyOnScala38OrLater[T](values: Seq[T]): Def.Initialize[Seq[T]] = Def.setting { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((3, minor)) if minor >= 8 => values + case _ => Seq.empty[T] + } } - def scalaMinorVersion: Def.Initialize[Long] = Def.setting { CrossVersion.partialVersion(scalaVersion.value).get._2 } } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 44a5314e6..daf2cbbe6 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -37,11 +37,12 @@ object Dependencies { val scala213Version = "2.13.18" val scala3Version = "3.3.8" - val allScalaVersions = Seq(scala213Version, scala3Version) + val scala3NextVersion = "3.8.4" + val publishedScalaVersions = Seq(scala213Version, scala3Version) val Versions = Seq( - crossScalaVersions := allScalaVersions, - scalaVersion := allScalaVersions.head) + crossScalaVersions := publishedScalaVersions, + scalaVersion := publishedScalaVersions.head) object Provided { val jsr305 = "com.google.code.findbugs" % "jsr305" % "3.0.2" % "provided" @@ -65,7 +66,7 @@ object Dependencies { val caffeine = "com.github.ben-manes.caffeine" % "caffeine" % "3.2.4" - val scalafix = "ch.epfl.scala" %% "scalafix-core" % Dependencies.scalafixVersion + val scalafix = ("ch.epfl.scala" %% "scalafix-core" % Dependencies.scalafixVersion).cross(CrossVersion.for3Use2_13) val parboiled = "org.parboiled" %% "parboiled" % "2.5.1" diff --git a/project/NoScala3.scala b/project/NoScala3.scala index e028a2bf8..6c5560e7d 100644 --- a/project/NoScala3.scala +++ b/project/NoScala3.scala @@ -12,5 +12,13 @@ import Keys._ object NoScala3 extends AutoPlugin { override def projectSettings: Seq[Def.Setting[?]] = Seq( - crossScalaVersions := crossScalaVersions.value.filterNot(_.startsWith("3."))) + crossScalaVersions := crossScalaVersions.value.filterNot(_.startsWith("3.")), + Compile / unmanagedSourceDirectories := { + if (scalaVersion.value.startsWith("3.")) Seq.empty + else (Compile / unmanagedSourceDirectories).value + }, + Test / unmanagedSourceDirectories := { + if (scalaVersion.value.startsWith("3.")) Seq.empty + else (Test / unmanagedSourceDirectories).value + }) } diff --git a/project/ValidatePullRequest.scala b/project/ValidatePullRequest.scala index 1fc73bd36..9d8ddddc4 100644 --- a/project/ValidatePullRequest.scala +++ b/project/ValidatePullRequest.scala @@ -33,6 +33,7 @@ import sbtunidoc.BaseUnidocPlugin.autoImport.unidoc import scala.collection.JavaConverters._ import scala.collection.immutable +import scala.language.postfixOps import scala.sys.process._ import scala.util.Try import scala.util.matching.Regex @@ -392,9 +393,13 @@ object AggregatePRValidation extends AutoPlugin { fw.println(msg) } + @scala.annotation.nowarn("cat=deprecation") val testLogger = LogExchange.logger("testLogger") val appender = MainAppender.defaultBacked(useFormat = false)(fw) - LogExchange.bindLoggerAppenders("testLogger", appender -> Level.Info :: Nil) + @scala.annotation.nowarn("cat=deprecation") + def installTestLoggerAppender(): Unit = + LogExchange.bindLoggerAppenders("testLogger", appender -> Level.Info :: Nil) + installTestLoggerAppender() log.info("") if (failed.nonEmpty || mimaFailures.nonEmpty || failedTasks.nonEmpty) { @@ -516,102 +521,105 @@ object MimaWithPrValidation extends AutoPlugin { override lazy val projectSettings = Seq( ValidatePR / additionalTasks += mimaResult, mimaResult := { - import com.typesafe.tools.mima.core - def reportModuleErrors(module: ModuleID, - backward: List[core.Problem], - forward: List[core.Problem], - filters: Seq[core.ProblemFilter], - backwardFilters: Map[String, Seq[core.ProblemFilter]], - forwardFilters: Map[String, Seq[core.ProblemFilter]], - log: String => Unit, projectName: String): Boolean = { - // filters * found is n-squared, it's fixable in principle by special-casing known - // filter types or something, not worth it most likely... - - val versionOrdering = { - // version string "x.y.z" is converted to an Int tuple (x, y, z) for comparison - val VersionRegex = """(\d+)\.?(\d+)?\.?(.*)?""".r - def int(versionPart: String) = - Try(versionPart.replace("x", Short.MaxValue.toString).filter(_.isDigit).toInt).getOrElse(0) - Ordering[(Int, Int, Int)].on[String] { case VersionRegex(x, y, z) => (int(x), int(y), int(z)) } - } - def isReported(module: ModuleID, verionedFilters: Map[String, Seq[core.ProblemFilter]])( - problem: core.Problem) = - (verionedFilters.collect { - // get all filters that apply to given module version or any version after it - case f @ (version, filters) if versionOrdering.gteq(version, module.revision) => filters - }.flatten ++ filters).forall { f => - if (f(problem)) { - true - } else { - // log(projectName + ": filtered out: " + problem.description + "\n filtered by: " + f) - false - } + if (scalaVersion.value == Dependencies.scala3NextVersion) NoErrors + else { + import com.typesafe.tools.mima.core + def reportModuleErrors(module: ModuleID, + backward: List[core.Problem], + forward: List[core.Problem], + filters: Seq[core.ProblemFilter], + backwardFilters: Map[String, Seq[core.ProblemFilter]], + forwardFilters: Map[String, Seq[core.ProblemFilter]], + log: String => Unit, projectName: String): Boolean = { + // filters * found is n-squared, it's fixable in principle by special-casing known + // filter types or something, not worth it most likely... + + val versionOrdering = { + // version string "x.y.z" is converted to an Int tuple (x, y, z) for comparison + val VersionRegex = """(\d+)\.?(\d+)?\.?(.*)?""".r + def int(versionPart: String) = + Try(versionPart.replace("x", Short.MaxValue.toString).filter(_.isDigit).toInt).getOrElse(0) + Ordering[(Int, Int, Int)].on[String] { case VersionRegex(x, y, z) => (int(x), int(y), int(z)) } } + def isReported(module: ModuleID, verionedFilters: Map[String, Seq[core.ProblemFilter]])( + problem: core.Problem) = + (verionedFilters.collect { + // get all filters that apply to given module version or any version after it + case f @ (version, filters) if versionOrdering.gteq(version, module.revision) => filters + }.flatten ++ filters).forall { f => + if (f(problem)) { + true + } else { + // log(projectName + ": filtered out: " + problem.description + "\n filtered by: " + f) + false + } + } - val backErrors = backward.filter(isReported(module, backwardFilters)) - val forwErrors = forward.filter(isReported(module, forwardFilters)) + val backErrors = backward.filter(isReported(module, backwardFilters)) + val forwErrors = forward.filter(isReported(module, forwardFilters)) - val filteredCount = backward.size + forward.size - backErrors.size - forwErrors.size - val filteredNote = if (filteredCount > 0) " (filtered " + filteredCount + ")" else "" + val filteredCount = backward.size + forward.size - backErrors.size - forwErrors.size + val filteredNote = if (filteredCount > 0) " (filtered " + filteredCount + ")" else "" - // TODO - Line wrapping an other magikz - def prettyPrint(p: core.Problem, affected: String): String = { - " * " + p.description(affected) + p.howToFilter.map("\n filter with: " + _).getOrElse("") - } + // TODO - Line wrapping an other magikz + def prettyPrint(p: core.Problem, affected: String): String = { + " * " + p.description(affected) + p.howToFilter.map("\n filter with: " + _).getOrElse("") + } - log( - s"$projectName: found ${backErrors.size + forwErrors.size} potential binary incompatibilities while checking against $module $filteredNote") - ((backErrors.map { p: core.Problem => prettyPrint(p, "current") }) ++ - (forwErrors.map { p: core.Problem => prettyPrint(p, "other") })).foreach { p => - log(p) + log( + s"$projectName: found ${backErrors.size + forwErrors.size} potential binary incompatibilities while checking against $module $filteredNote") + ((backErrors.map { p: core.Problem => prettyPrint(p, "current") }) ++ + (forwErrors.map { p: core.Problem => prettyPrint(p, "other") })).foreach { p => + log(p) + } + backErrors.nonEmpty || forwErrors.nonEmpty } - backErrors.nonEmpty || forwErrors.nonEmpty - } - def withLogger[T](f: (String => Unit) => T): (String, T) = { - val stringWriter = new StringWriter() - val printWriter = new PrintWriter(stringWriter) + def withLogger[T](f: (String => Unit) => T): (String, T) = { + val stringWriter = new StringWriter() + val printWriter = new PrintWriter(stringWriter) - val result = f(printWriter.println) + val result = f(printWriter.println) - printWriter.close() - stringWriter.close() - (stringWriter.toString, result) - } - - val allResults = - mimaPreviousClassfiles.value.toSeq.map { - case (moduleId, file) => - val problems = SbtMima.runMima( - file, - mimaCurrentClassfiles.value, - (mimaFindBinaryIssues / fullClasspath).value, - mimaCheckDirection.value, - scalaVersion.value, - streams.value.log, - Nil) - - val binary = mimaBinaryIssueFilters.value - val backward = mimaBackwardIssueFilters.value - val forward = mimaForwardIssueFilters.value - - withLogger { logger => - reportModuleErrors( - moduleId, - problems._1, problems._2, - binary, - backward, - forward, - logger, - name.value) - } + printWriter.close() + stringWriter.close() + (stringWriter.toString, result) } - val noErrors = allResults.forall(!_._2) - if (noErrors) NoErrors - else { - val erroneous = allResults.filter(_._2) - Problems(erroneous.map(_._1).mkString("\n")) + val allResults = + mimaPreviousClassfiles.value.toSeq.map { + case (moduleId, file) => + val problems = SbtMima.runMima( + file, + mimaCurrentClassfiles.value, + (mimaFindBinaryIssues / fullClasspath).value, + mimaCheckDirection.value, + scalaVersion.value, + streams.value.log, + Nil) + + val binary = mimaBinaryIssueFilters.value + val backward = mimaBackwardIssueFilters.value + val forward = mimaForwardIssueFilters.value + + withLogger { logger => + reportModuleErrors( + moduleId, + problems._1, problems._2, + binary, + backward, + forward, + logger, + name.value) + } + } + + val noErrors = allResults.forall(!_._2) + if (noErrors) NoErrors + else { + val erroneous = allResults.filter(_._2) + Problems(erroneous.map(_._1).mkString("\n")) + } } }) }