diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6b94b636..243f8705f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13, 2.12] + scala: [per-project-matrix] java: [temurin@8] runs-on: ${{ matrix.os }} timeout-minutes: 60 @@ -108,22 +108,12 @@ jobs: if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' run: sbt +update - - name: Download target directories (2.13) + - name: Download target directories (per-project-matrix) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13 + name: target-${{ matrix.os }}-${{ matrix.java }}-per-project-matrix - - name: Inflate target directories (2.13) - run: | - tar xf targets.tar - rm targets.tar - - - name: Download target directories (2.12) - uses: actions/download-artifact@v4 - with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12 - - - name: Inflate target directories (2.12) + - name: Inflate target directories (per-project-matrix) run: | tar xf targets.tar rm targets.tar diff --git a/.mergify.yml b/.mergify.yml index da3d98937..efc7fc616 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -12,8 +12,7 @@ pull_request_rules: - or: - body~=labels:.*early-semver-patch - body~=labels:.*early-semver-minor - - status-success=Build and Test (ubuntu-latest, 2.13, temurin@8) - - status-success=Build and Test (ubuntu-latest, 2.12, temurin@8) + - status-success=Build and Test (ubuntu-latest, per-project-matrix, temurin@8) - status-success=Validate Steward Config (ubuntu-latest, temurin@11) actions: merge: {} diff --git a/.sbtopts b/.sbtopts index 1ca6d0b39..2b63e3b2d 100644 --- a/.sbtopts +++ b/.sbtopts @@ -1 +1 @@ --J-Xmx2G +-J-Xmx4G diff --git a/build.sbt b/build.sbt index b0e0d41ec..5d94415d4 100644 --- a/build.sbt +++ b/build.sbt @@ -1,28 +1,3 @@ -ThisBuild / organization := "com.dwolla" -ThisBuild / homepage := Some(url("https://github.com/Dwolla/async-utils")) -ThisBuild / licenses := Seq(License.MIT) -ThisBuild / developers := List( - Developer( - "bpholt", - "Brian Holt", - "bholt+async-utils@dwolla.com", - url("https://dwolla.com") - ), -) -ThisBuild / startYear := Option(2021) -ThisBuild / tlSonatypeUseLegacyHost := true -ThisBuild / tlBaseVersion := "1.1" -ThisBuild / githubWorkflowScalaVersions := Seq("2.13", "2.12") -ThisBuild / tlCiReleaseBranches := Seq("main") -ThisBuild / mergifyRequiredJobs ++= Seq("validate-steward") -ThisBuild / mergifyStewardConfig ~= { _.map(_.copy( - author = "dwolla-oss-scala-steward[bot]", - mergeMinors = true, -))} -ThisBuild / tlJdkRelease := Option(8) - -ThisBuild / scalaVersion := "2.13.10" - lazy val `async-utils-root` = (project in file(".")) .aggregate(allProjects.map(_.project) *) .settings( diff --git a/finagle-natchez/src/main/scala/com/dwolla/util/async/finagle/TracedThriftClient.scala b/finagle-natchez/src/main/scala/com/dwolla/util/async/finagle/TracedThriftClient.scala index 25661bb35..0f61aa73e 100644 --- a/finagle-natchez/src/main/scala/com/dwolla/util/async/finagle/TracedThriftClient.scala +++ b/finagle-natchez/src/main/scala/com/dwolla/util/async/finagle/TracedThriftClient.scala @@ -1,18 +1,16 @@ package com.dwolla.util.async.finagle -import cats.data._ +import cats.data.* import cats.effect.std.Env -import cats.effect.syntax.all._ -import cats.effect.{Trace => _, tracing => _, _} -import cats.syntax.all._ -import cats.tagless._ +import cats.effect.syntax.all.* +import cats.effect.{Trace as _, tracing as _, *} +import cats.syntax.all.* +import cats.tagless.* import com.dwolla.util.async.finagle.ThriftClient.initialAcquire -import com.dwolla.util.async.twitter._ +import com.dwolla.util.async.twitter.* import com.twitter.util.{Closable, Future} import natchez.Trace -import scala.language.reflectiveCalls - /** * Build a Finagle Thrift client using the given Thrift * method-per-endpoint algebra and the given destination. Optionally, @@ -40,6 +38,7 @@ object TracedThriftClient { */ def apply[Alg[_[_]] <: AnyRef {def asClosable: Closable}] = new PartiallyAppliedThriftClient[Alg]() + @annotation.nowarn("msg=dubious usage of method hashCode with unit value") class PartiallyAppliedThriftClient[Alg[_[_]] <: AnyRef {def asClosable: Closable}] private[TracedThriftClient](val dummy: Unit = ()) extends AnyVal { /** * @param name the local service name to be set on the Zipkin tracer diff --git a/finagle-natchez/src/main/scala/com/dwolla/util/async/finagle/ZipkinTracer.scala b/finagle-natchez/src/main/scala/com/dwolla/util/async/finagle/ZipkinTracer.scala index 6f93873b8..a831b2ffa 100644 --- a/finagle-natchez/src/main/scala/com/dwolla/util/async/finagle/ZipkinTracer.scala +++ b/finagle-natchez/src/main/scala/com/dwolla/util/async/finagle/ZipkinTracer.scala @@ -3,8 +3,8 @@ package com.dwolla.util.async.finagle import cats.Functor import cats.effect.Sync import cats.effect.std.Env -import cats.syntax.all._ -import com.comcast.ip4s._ +import cats.syntax.all.* +import com.comcast.ip4s.* import com.dwolla.util.async.finagle.ZipkinTracer.alwaysSample import com.twitter.finagle.stats.NullStatsReceiver import com.twitter.finagle.tracing.Tracer @@ -35,7 +35,7 @@ object ZipkinTracerConfig { } } -class ZipkinTracerConfig(val host: SocketAddress[_], +class ZipkinTracerConfig(val host: SocketAddress[?], val initialSampleRate: Float, val tlsEnabled: Boolean, val localServiceName: String, @@ -47,14 +47,14 @@ class ZipkinTracerConfig(val host: SocketAddress[_], this( SocketAddress(host"127.0.0.1", port"9411"), alwaysSample, - false, + tlsEnabled = false, localServiceName, - true, + compressionEnabled = true, "zipkin", "/api/v2/spans" ) - def withHost(update: SocketAddress[_]): ZipkinTracerConfig = + def withHost(update: SocketAddress[?]): ZipkinTracerConfig = new ZipkinTracerConfig(update, initialSampleRate, tlsEnabled, localServiceName, compressionEnabled, hostHeader, path) private[finagle] def build(): HttpZipkinTracer.Config = diff --git a/project/AsyncUtilsBuildPlugin.scala b/project/AsyncUtilsBuildPlugin.scala index 3af6cb41b..73d5926e1 100644 --- a/project/AsyncUtilsBuildPlugin.scala +++ b/project/AsyncUtilsBuildPlugin.scala @@ -6,7 +6,13 @@ import org.scalajs.jsenv.JSEnv import org.scalajs.jsenv.nodejs.NodeJSEnv import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.* import org.typelevel.sbt.TypelevelMimaPlugin.autoImport.* +import org.typelevel.sbt.TypelevelSettingsPlugin +import org.typelevel.sbt.TypelevelSettingsPlugin.autoImport.* +import org.typelevel.sbt.TypelevelSonatypeCiReleasePlugin.autoImport.* +import org.typelevel.sbt.TypelevelSonatypePlugin.autoImport.* +import org.typelevel.sbt.TypelevelVersioningPlugin.autoImport.* import org.typelevel.sbt.gha.GenerativePlugin.autoImport.* +import org.typelevel.sbt.gha.GitHubActionsPlugin.autoImport.* import org.typelevel.sbt.mergify.MergifyPlugin import org.typelevel.sbt.mergify.MergifyPlugin.autoImport.* import sbt.* @@ -21,7 +27,7 @@ object AsyncUtilsBuildPlugin extends AutoPlugin { override def trigger = noTrigger override def requires: Plugins = - ProjectMatrixPlugin && ScalafixPlugin && MimaPlugin && MergifyPlugin + ProjectMatrixPlugin && ScalafixPlugin && MimaPlugin && MergifyPlugin && TypelevelSettingsPlugin && WarnNonUnitStatements object autoImport { lazy val allProjects: Seq[Project] = @@ -57,8 +63,8 @@ object AsyncUtilsBuildPlugin extends AutoPlugin { private val supportedVersions = (currentTwitterVersion :: oldVersions).sorted.reverse - private val SCALA_2_13: String = "2.13.12" - private val SCALA_2_12 = "2.12.18" + private val SCALA_2_13: String = "2.13.13" + private val SCALA_2_12 = "2.12.19" private val Scala2Versions: Seq[String] = Seq(SCALA_2_13, SCALA_2_12) private val CatsEffect3V = "3.5.4" @@ -304,7 +310,6 @@ object AsyncUtilsBuildPlugin extends AutoPlugin { .dependsOn(`async-utils-finagle`) override def buildSettings: Seq[Def.Setting[?]] = Seq( - scalaVersion := SCALA_2_13, mergifyLabelPaths := List( "core", @@ -318,22 +323,34 @@ object AsyncUtilsBuildPlugin extends AutoPlugin { .map(x => x -> file(x)) .toMap, - /* this is misleading, because we're actually running the build for all supported - * scala versions, but unfortunately this seems to be our best option until - * sbt-typelevel 0.5. - * - * sbt-projectmatrix creates separate projects for each crossed Scala version - * setting githubWorkflowScalaVersions to a single (ignored) version minimizes - * the build matrix, and setting githubWorkflowBuildSbtStepPreamble to an empty - * list ensures that the build phase ignores the scala version set in - * githubWorkflowScalaVersions. - * - * '++ ${{ matrix.scala }}' will still be used in the Publish stage, but it - * sounds like the tlCiRelease will do the right thing anyway. - */ - githubWorkflowScalaVersions := Seq("2.13"), + githubWorkflowScalaVersions := Seq("per-project-matrix"), githubWorkflowBuildSbtStepPreamble := Nil, + organization := "com.dwolla", + homepage := Some(url("https://github.com/Dwolla/async-utils")), + licenses := Seq(License.MIT), + developers := List( + Developer( + "bpholt", + "Brian Holt", + "bholt+async-utils@dwolla.com", + url("https://dwolla.com") + ), + ), + startYear := Option(2021), + tlSonatypeUseLegacyHost := true, + tlBaseVersion := "1.1", + tlCiReleaseBranches := Seq("main"), + mergifyRequiredJobs ++= Seq("validate-steward"), + mergifyStewardConfig ~= { + _.map(_.copy( + author = "dwolla-oss-scala-steward[bot]", + mergeMinors = true, + )) + }, + tlJdkRelease := Option(8), + tlFatalWarnings := githubIsWorkflowBuild.value, + nodeExecutable := scala.util.Try { import scala.sys.process.* @@ -357,3 +374,18 @@ object AsyncUtilsBuildPlugin extends AutoPlugin { private lazy val nvmJsEnv: TaskKey[JSEnv] = taskKey("use nvm to find node") private lazy val nodeExecutable: TaskKey[Option[File]] = taskKey("path to Node executable for JS tasks") } + +object WarnNonUnitStatements extends AutoPlugin { + override def trigger = allRequirements + + override def projectSettings: Seq[Def.Setting[?]] = Seq( + scalacOptions ++= { + if (scalaVersion.value.startsWith("2.13")) + Seq("-Wnonunit-statement") + else if (scalaVersion.value.startsWith("2.12")) + Seq() + else + Nil + }, + ) +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 98d0ba87e..5c4ac68a7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ addSbtPlugin("org.typelevel" % "sbt-typelevel-ci-release" % "0.6.7") addSbtPlugin("org.typelevel" % "sbt-typelevel-mergify" % "0.6.7") addSbtPlugin("org.typelevel" % "sbt-typelevel-settings" % "0.6.7") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.15.0") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.1") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.12.0") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.10.0") diff --git a/scalafix/rules/src/main/scala/com/dwolla/scrooge/scalafix/AddCatsTaglessInstances.scala b/scalafix/rules/src/main/scala/com/dwolla/scrooge/scalafix/AddCatsTaglessInstances.scala index 4c40e7452..2d2946990 100644 --- a/scalafix/rules/src/main/scala/com/dwolla/scrooge/scalafix/AddCatsTaglessInstances.scala +++ b/scalafix/rules/src/main/scala/com/dwolla/scrooge/scalafix/AddCatsTaglessInstances.scala @@ -1,16 +1,18 @@ package com.dwolla.scrooge.scalafix -import com.dwolla.scrooge.scalafix.AddCatsTaglessInstances._ -import scalafix.v1._ +import com.dwolla.scrooge.scalafix.AddCatsTaglessInstances.* +import scalafix.v1.* -import scala.meta._ +import scala.meta.* import scala.meta.tokens.Token.Ident import GuardedPatchBuilder.toGuardedPatch +import scala.meta.Type.ParamClause + object ThriftServiceTrait { def unapply(subtree: Tree): Option[(String, Defn.Trait)] = PartialFunction.condOpt(subtree) { - case term@Defn.Trait(_, Type.Name(name), Nil, _, ExtendsThriftService(_)) => (name, term) + case term@Defn.Trait.After_4_6_0(_, Type.Name(name), ParamClause(Nil), _, ExtendsThriftService(_)) => (name, term) } } @@ -43,7 +45,7 @@ object TemplateExtends { } PartialFunction.condOpt(subtree) { - case t@Template(_, TemplateInitsContainName(_), _, _) => t + case t@Template.After_4_4_0(_, TemplateInitsContainName(_), _, _, _) => t } } } @@ -100,14 +102,14 @@ case class ThriftServiceTrait(private val generatedThriftServiceObject: Defn.Obj } .map(Patch.replaceToken(_, s"${generatedThriftServiceObject.name.value}[F[_]]")) .onlyIfMissing { - case Defn.Trait(_, Type.Name(name), _, _, _) if name == generatedThriftServiceObject.name.value => () + case Defn.Trait.After_4_6_0(_, Type.Name(name), _, _, _) if name == generatedThriftServiceObject.name.value => () } .in(generatedThriftServiceObject) private val addServiceCompanionObjectWithImplicits: Patch = Patch.addRight(methodPerEndpointTrait, companionObjectWithImplicits) .onlyIfMissing { - case Defn.Object(_, Term.Name(name), Template(_, List(), _, _)) if name == generatedThriftServiceObject.name.value => () + case Defn.Object(_, Term.Name(name), Template.After_4_4_0(_, List(), _, _, _)) if name == generatedThriftServiceObject.name.value => () } .in(generatedThriftServiceObject) @@ -116,7 +118,7 @@ case class ThriftServiceTrait(private val generatedThriftServiceObject: Defn.Obj s""" | trait MethodPerEndpoint extends ${generatedThriftServiceObject.name.value}[Future]""".stripMargin) .onlyIfMissing { - case Defn.Trait(_, Type.Name("MethodPerEndpoint"), _, _, ExtendsServiceTrait(_)) => () + case Defn.Trait.After_4_6_0(_, Type.Name("MethodPerEndpoint"), _, _, ExtendsServiceTrait(_)) => () } .in(generatedThriftServiceObject) @@ -138,7 +140,7 @@ case class ThriftServiceTrait(private val generatedThriftServiceObject: Defn.Obj t.parent.fold(Patch.empty) { Patch.replaceToken(t.tokens.head, "F") .onlyIfMissing { - case Type.Apply(Type.Name(name), _) if name == generatedThriftServiceObject.name.value => () + case Type.Apply.After_4_6_0(Type.Name(name), _) if name == generatedThriftServiceObject.name.value => () } .in(_) } @@ -163,8 +165,8 @@ case class ThriftServiceTrait(private val generatedThriftServiceObject: Defn.Obj methodPerEndpointTrait .templ .collect { - case d@Decl.Def(_, name, _, paramss, _) => - s"override ${d.toString()} = fa.$name${invokeEachParamList(paramss)}" + case d@Decl.Def.After_4_7_3(_, name, paramss, _) => + s"override ${d.toString()} = fa.$name${invokeEachParamList(paramss.flatMap(_.paramClauses.map(_.values)))}" } val code = @@ -182,7 +184,7 @@ case class ThriftServiceTrait(private val generatedThriftServiceObject: Defn.Obj modifyMethodPerEndpointTrait ++ modifyMethodPerEndpointCompanionObject } -class GuardedPatch(private val tuple: (Patch, PartialFunction[Tree, _])) extends AnyVal { +class GuardedPatch(private val tuple: (Patch, PartialFunction[Tree, ?])) extends AnyVal { def in(tree: Tree): Patch = { val empty = tree.collect(tuple._2).isEmpty if (empty) tuple._1 @@ -191,7 +193,7 @@ class GuardedPatch(private val tuple: (Patch, PartialFunction[Tree, _])) extends } class GuardedPatchBuilder(private val patch: Patch) extends AnyVal { - def onlyIfMissing(pf: PartialFunction[Tree, _]) = new GuardedPatch(patch -> pf) + def onlyIfMissing(pf: PartialFunction[Tree, ?]) = new GuardedPatch(patch -> pf) } object GuardedPatchBuilder { @@ -210,13 +212,13 @@ case class Unapplied(patch: Patch) extends IdempotentPatch object IdempotentPatch { def apply(patch: Patch) (tree: Tree) - (pf: PartialFunction[Tree, _]): IdempotentPatch = + (pf: PartialFunction[Tree, ?]): IdempotentPatch = if (tree.collect(pf).nonEmpty) AlreadyApplied else Unapplied(patch) def apply(patch: Option[Patch]) (tree: Tree) - (pf: PartialFunction[Tree, _]): IdempotentPatch = + (pf: PartialFunction[Tree, ?]): IdempotentPatch = IdempotentPatch(patch.getOrElse(Patch.empty))(tree)(pf) } diff --git a/scalafix/rules/src/test/scala/com/dwolla/scrooge/scalafix/AddCatsTaglessInstancesTest.scala b/scalafix/rules/src/test/scala/com/dwolla/scrooge/scalafix/AddCatsTaglessInstancesTest.scala index cbd953323..feedc8a88 100644 --- a/scalafix/rules/src/test/scala/com/dwolla/scrooge/scalafix/AddCatsTaglessInstancesTest.scala +++ b/scalafix/rules/src/test/scala/com/dwolla/scrooge/scalafix/AddCatsTaglessInstancesTest.scala @@ -55,21 +55,21 @@ class AddCatsTaglessInstancesTest extends FunSuite { test("rename MethodPerEndpoint to SimpleService[F[_]]") { val expectedPatchContent = - s"Add(MethodPerEndpoint, MethodPerEndpoint [${range("MethodPerEndpoint")(input.indexOf)}), SimpleService[F[_]])" + s"Add(MethodPerEndpoint, Ident(MethodPerEndpoint) [${range("MethodPerEndpoint")(input.indexOf)}), SimpleService[F[_]])" expect(patch.toString.contains(expectedPatchContent)) } test("replace Future[*] with F[*]") { val expectedPatchContent = - s"Add(Future, Future [${range("Future")(input.indexOf)}), F)" + s"Add(Future, Ident(Future) [${range("Future")(input.indexOf)}), F)" expect(patch.toString.contains(expectedPatchContent)) } test("add SimpleService companion object with implicits") { val expectedPatchContent = - """Add(}, } [467..468), } + """Add(}, RightBrace(}) [467..468), } | | object SimpleService { | implicit def SimpleServiceInReaderT[F[_]]: SimpleService[({type Λ[β0] = _root_.cats.data.ReaderT[F, SimpleService[F], β0]})#Λ] = @@ -92,7 +92,7 @@ class AddCatsTaglessInstancesTest extends FunSuite { test("add replacement MethodPerEndpoint that extends SimpleService[Future]") { val expectedPatchContent = - """Add(}, } [467..468), } + """Add(}, RightBrace(}) [467..468), } | trait MethodPerEndpoint extends SimpleService[Future])""".stripMargin expect(patch.toString.contains(expectedPatchContent)) diff --git a/twitter-finagle/src/main/scala/com/dwolla/util/async/finagle/ThriftClient.scala b/twitter-finagle/src/main/scala/com/dwolla/util/async/finagle/ThriftClient.scala index bdcf6ccfe..63626d598 100644 --- a/twitter-finagle/src/main/scala/com/dwolla/util/async/finagle/ThriftClient.scala +++ b/twitter-finagle/src/main/scala/com/dwolla/util/async/finagle/ThriftClient.scala @@ -1,18 +1,17 @@ package com.dwolla.util.async.finagle -import cats.data._ -import cats.effect._ -import cats.syntax.all._ -import cats.tagless._ -import com.dwolla.util.async.twitter._ +import cats.data.* +import cats.effect.* +import cats.syntax.all.* +import cats.tagless.* +import com.dwolla.util.async.twitter.* import com.twitter.finagle.Thrift import com.twitter.util.{Closable, Future} -import scala.language.reflectiveCalls - object ThriftClient { def apply[Alg[_[_]] <: AnyRef {def asClosable: Closable}] = new PartiallyAppliedThriftClient[Alg]() + @annotation.nowarn("msg=dubious usage of method hashCode with unit value") class PartiallyAppliedThriftClient[Alg[_[_]] <: AnyRef {def asClosable: Closable}] private[ThriftClient] (val dummy: Unit = ()) extends AnyVal { def apply[G[_] : Async](dest: String) (implicit