diff --git a/build.sbt b/build.sbt index 57c50117..b29ccbf9 100644 --- a/build.sbt +++ b/build.sbt @@ -7,7 +7,7 @@ name := """fs2-rabbit-root""" organization in ThisBuild := "dev.profunktor" -crossScalaVersions in ThisBuild := Seq("2.11.12", "2.12.8") +crossScalaVersions in ThisBuild := Seq("2.11.12", "2.12.8", "2.13.0") // makes `tut` fail :( -> https://github.com/tpolecat/tut/issues/255 //scalaVersion in ThisBuild := "2.12.8" // needed for metals @@ -19,23 +19,40 @@ promptTheme := PromptTheme(List( text(_ => "fs2-rabbit", fg(15)).padRight(" λ ") )) + + +// We use String as our input type because `scalaVersion.value` cannot be called +// in a lot of places in a build.sbt file where it would be convenient to do so +// and so we have to thread it through at the last moment instead and +// scalaVersion.value is a String. +def determineVersionSpecificDeps(scalaVersionStr: String) = CrossVersion.partialVersion(scalaVersionStr) match { + case Some((2, 13)) => Scala213Dependencies + case Some((2, 12)) => Scala212Dependencies + case Some((2, 11)) => Scala211Dependencies + // Fallback to 2.12 libraries as they're currently the most well-supported + case _ => Scala212Dependencies +} + val commonSettings = Seq( organizationName := "ProfunKtor", startYear := Some(2017), licenses += ("Apache-2.0", new URL("https://www.apache.org/licenses/LICENSE-2.0.txt")), homepage := Some(url("https://fs2-rabbit.profunktor.dev/")), headerLicense := Some(HeaderLicense.ALv2("2017-2019", "ProfunKtor")), - libraryDependencies ++= Seq( - compilerPlugin(Libraries.kindProjector), - compilerPlugin(Libraries.betterMonadicFor), - Libraries.amqpClient, - Libraries.catsEffect, - Libraries.fs2Core, - Libraries.scalaTest % "test", - Libraries.scalaCheck % "test" - ), + scalacOptions ++= determineVersionSpecificDeps(scalaVersion.value).scalacOptions, + libraryDependencies ++= { + val library = determineVersionSpecificDeps(scalaVersion.value) + Seq( + compilerPlugin(library.kindProjector), + compilerPlugin(library.betterMonadicFor), + library.amqpClient, + library.catsEffect, + library.fs2Core, + library.scalaTest % "test", + library.scalaCheck % "test" + ) + }, resolvers += "Apache public" at "https://repository.apache.org/content/groups/public/", - scalacOptions ++= Seq("-Xmax-classfile-name", "100"), scalafmtOnCompile := true, publishTo := { val sonatype = "https://oss.sonatype.org/" @@ -57,22 +74,41 @@ val commonSettings = Seq( ) -val CoreDependencies: Seq[ModuleID] = Seq( - Libraries.logback % "test" -) - -val JsonDependencies: Seq[ModuleID] = Seq( - Libraries.circeCore, - Libraries.circeGeneric, - Libraries.circeParser -) - -val ExamplesDependencies: Seq[ModuleID] = Seq( - Libraries.monix, - Libraries.zioCore, - Libraries.zioCats, - Libraries.logback % "runtime" -) +def CoreDependencies(scalaVersionStr: String): Seq[ModuleID] = { + val library = determineVersionSpecificDeps(scalaVersionStr) + Seq( + library.logback % "test" + ) +} + +def JsonDependencies(scalaVersionStr: String): Seq[ModuleID] = { + val library = determineVersionSpecificDeps(scalaVersionStr) + Seq( + library.circeCore, + library.circeGeneric, + library.circeParser + ) +} + +def ExamplesDependencies(scalaVersionStr: String): Seq[ModuleID] = { + determineVersionSpecificDeps(scalaVersionStr) match { + case library: Scala213Dependencies.type => Seq(library.logback % "runtime") + case library: Scala212Dependencies.type => + Seq( + library.logback % "runtime", + library.monix, + library.zioCore, + library.zioCats + ) + case library: Scala211Dependencies.type => + Seq( + library.logback % "runtime", + library.monix, + library.zioCore, + library.zioCats + ) + } +} lazy val noPublish = Seq( publish := {}, @@ -87,20 +123,20 @@ lazy val `fs2-rabbit-root` = project.in(file(".")) lazy val `fs2-rabbit` = project.in(file("core")) .settings(commonSettings: _*) - .settings(libraryDependencies ++= CoreDependencies) + .settings(libraryDependencies ++= CoreDependencies(scalaVersion.value)) .settings(parallelExecution in Test := false) .enablePlugins(AutomateHeaderPlugin) lazy val `fs2-rabbit-circe` = project.in(file("json-circe")) .settings(commonSettings: _*) - .settings(libraryDependencies ++= JsonDependencies) + .settings(libraryDependencies ++= JsonDependencies(scalaVersion.value)) .settings(parallelExecution in Test := false) .enablePlugins(AutomateHeaderPlugin) .dependsOn(`fs2-rabbit`) lazy val `fs2-rabbit-test-support` = project.in(file("test-support")) .settings(commonSettings: _*) - .settings(libraryDependencies += Libraries.scalaTest) + .settings(libraryDependencies += determineVersionSpecificDeps(scalaVersion.value).scalaTest) .enablePlugins(AutomateHeaderPlugin) .dependsOn(`fs2-rabbit`) @@ -113,7 +149,7 @@ lazy val tests = project.in(file("tests")) lazy val examples = project.in(file("examples")) .settings(commonSettings: _*) - .settings(libraryDependencies ++= ExamplesDependencies) + .settings(libraryDependencies ++= ExamplesDependencies(scalaVersion.value)) .settings(noPublish) .enablePlugins(AutomateHeaderPlugin) .dependsOn(`fs2-rabbit`, `fs2-rabbit-circe`) diff --git a/core/src/main/scala-2.11/dev/profunktor/fs2rabbit/javaConversion.scala b/core/src/main/scala-2.11/dev/profunktor/fs2rabbit/javaConversion.scala new file mode 100644 index 00000000..e4a4fd2f --- /dev/null +++ b/core/src/main/scala-2.11/dev/profunktor/fs2rabbit/javaConversion.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2017-2019 ProfunKtor + * + * Licensed 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. + */ + +package dev.profunktor.fs2rabbit + +import scala.collection.convert.{DecorateAsJava, DecorateAsScala} + +// This exists purely for compatibility between Scala 2.13 and 2.12 since the +// Java conversions have been moved into a different package between the two, +// allowing us to have a single, consistent import everywhere else in this +// codebase across both 2.13 and 2.12. +object javaConversion extends DecorateAsJava with DecorateAsScala diff --git a/core/src/main/scala-2.12/dev/profunktor/fs2rabbit/javaConversion.scala b/core/src/main/scala-2.12/dev/profunktor/fs2rabbit/javaConversion.scala new file mode 100644 index 00000000..e4a4fd2f --- /dev/null +++ b/core/src/main/scala-2.12/dev/profunktor/fs2rabbit/javaConversion.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2017-2019 ProfunKtor + * + * Licensed 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. + */ + +package dev.profunktor.fs2rabbit + +import scala.collection.convert.{DecorateAsJava, DecorateAsScala} + +// This exists purely for compatibility between Scala 2.13 and 2.12 since the +// Java conversions have been moved into a different package between the two, +// allowing us to have a single, consistent import everywhere else in this +// codebase across both 2.13 and 2.12. +object javaConversion extends DecorateAsJava with DecorateAsScala diff --git a/core/src/main/scala-2.13/dev/profunktor/fs2rabbit/javaConversion.scala b/core/src/main/scala-2.13/dev/profunktor/fs2rabbit/javaConversion.scala new file mode 100644 index 00000000..359e18f6 --- /dev/null +++ b/core/src/main/scala-2.13/dev/profunktor/fs2rabbit/javaConversion.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2017-2019 ProfunKtor + * + * Licensed 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. + */ + +package dev.profunktor.fs2rabbit + +import scala.collection.convert.{AsJavaExtensions, AsScalaExtensions} + +// This exists purely for compatibility between Scala 2.13 and 2.12 since the +// Java conversions have been moved into a different package between the two, +// allowing us to have a single, consistent import everywhere else in this +// codebase across both 2.13 and 2.12. +object javaConversion extends AsJavaExtensions with AsScalaExtensions diff --git a/core/src/main/scala/dev/profunktor/fs2rabbit/arguments.scala b/core/src/main/scala/dev/profunktor/fs2rabbit/arguments.scala index b2ede3e7..156de9ae 100644 --- a/core/src/main/scala/dev/profunktor/fs2rabbit/arguments.scala +++ b/core/src/main/scala/dev/profunktor/fs2rabbit/arguments.scala @@ -17,8 +17,7 @@ package dev.profunktor.fs2rabbit import scala.annotation.implicitNotFound -import scala.language.implicitConversions -import scala.collection.JavaConverters._ +import dev.profunktor.fs2rabbit.javaConversion._ object arguments { @@ -58,8 +57,6 @@ object arguments { def toJavaType(a: A) = f(a) } - import scala.collection.JavaConverters._ - implicit val stringInstance: SafeArgument[String] = instance(identity) implicit val bigDecimalInstance: SafeArgument[BigDecimal] = instance(_.bigDecimal) implicit val intInstance: SafeArgument[Int] = instance(Int.box) diff --git a/core/src/main/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoder.scala b/core/src/main/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoder.scala index 5901261f..004cd20a 100644 --- a/core/src/main/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoder.scala +++ b/core/src/main/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoder.scala @@ -54,8 +54,8 @@ object EnvelopeDecoder { def longHeader[F[_]: ApplicativeError[?[_], Throwable]](name: String): EnvelopeDecoder[F, Long] = headerPF[F, Long](name) { case LongVal(a) => a } - def arrayHeader[F[_]: ApplicativeError[?[_], Throwable]](name: String): EnvelopeDecoder[F, Seq[Any]] = - headerPF[F, Seq[Any]](name) { case ArrayVal(a) => a } + def arrayHeader[F[_]: ApplicativeError[?[_], Throwable]](name: String): EnvelopeDecoder[F, collection.Seq[Any]] = + headerPF[F, collection.Seq[Any]](name) { case ArrayVal(a) => a } def optStringHeader[F[_]: ApplicativeError[?[_], Throwable]](name: String): EnvelopeDecoder[F, Option[String]] = optHeaderPF[F, String](name) { case StringVal(a) => a } @@ -66,8 +66,9 @@ object EnvelopeDecoder { def optLongHeader[F[_]: ApplicativeError[?[_], Throwable]](name: String): EnvelopeDecoder[F, Option[Long]] = optHeaderPF[F, Long](name) { case LongVal(a) => a } - def optArrayHeader[F[_]: ApplicativeError[?[_], Throwable]](name: String): EnvelopeDecoder[F, Option[Seq[Any]]] = - optHeaderPF[F, Seq[Any]](name) { case ArrayVal(a) => a } + def optArrayHeader[F[_]: ApplicativeError[?[_], Throwable]]( + name: String): EnvelopeDecoder[F, Option[collection.Seq[Any]]] = + optHeaderPF[F, collection.Seq[Any]](name) { case ArrayVal(a) => a } private def headerPF[F[_], A](name: String)(pf: PartialFunction[AmqpHeaderVal, A])( implicit F: ApplicativeError[F, Throwable]): EnvelopeDecoder[F, A] = diff --git a/core/src/main/scala/dev/profunktor/fs2rabbit/interpreter/ConnectionEffect.scala b/core/src/main/scala/dev/profunktor/fs2rabbit/interpreter/ConnectionEffect.scala index e1220ba3..782646c1 100644 --- a/core/src/main/scala/dev/profunktor/fs2rabbit/interpreter/ConnectionEffect.scala +++ b/core/src/main/scala/dev/profunktor/fs2rabbit/interpreter/ConnectionEffect.scala @@ -19,15 +19,14 @@ package dev.profunktor.fs2rabbit.interpreter import cats.data.NonEmptyList import cats.effect.{Resource, Sync} import cats.implicits._ +import com.rabbitmq.client.{Address, ConnectionFactory} import dev.profunktor.fs2rabbit.algebra.Connection import dev.profunktor.fs2rabbit.config.Fs2RabbitConfig import dev.profunktor.fs2rabbit.effects.Log +import dev.profunktor.fs2rabbit.javaConversion._ import dev.profunktor.fs2rabbit.model.{AMQPChannel, AMQPConnection, RabbitChannel, RabbitConnection} -import com.rabbitmq.client.{Address, ConnectionFactory} import javax.net.ssl.SSLContext -import scala.collection.JavaConverters._ - class ConnectionEffect[F[_]: Log: Sync]( factory: ConnectionFactory, addresses: NonEmptyList[Address] diff --git a/core/src/main/scala/dev/profunktor/fs2rabbit/model.scala b/core/src/main/scala/dev/profunktor/fs2rabbit/model.scala index c07043f6..deb7150f 100644 --- a/core/src/main/scala/dev/profunktor/fs2rabbit/model.scala +++ b/core/src/main/scala/dev/profunktor/fs2rabbit/model.scala @@ -25,12 +25,11 @@ import cats.implicits._ import dev.profunktor.fs2rabbit.arguments.Arguments import dev.profunktor.fs2rabbit.effects.{EnvelopeDecoder, MessageEncoder} import dev.profunktor.fs2rabbit.model.AmqpHeaderVal._ +import dev.profunktor.fs2rabbit.javaConversion._ import com.rabbitmq.client.impl.LongStringHelper import com.rabbitmq.client.{AMQP, Channel, Connection, LongString} import fs2.Stream -import scala.collection.JavaConverters._ - object model { type StreamAckerConsumer[F[_], A] = (AckResult => F[Unit], Stream[F, AmqpEnvelope[A]]) @@ -92,10 +91,10 @@ object model { } object AmqpHeaderVal { - final case class IntVal(value: Int) extends AmqpHeaderVal - final case class LongVal(value: Long) extends AmqpHeaderVal - final case class StringVal(value: String) extends AmqpHeaderVal - final case class ArrayVal(v: Seq[Any]) extends AmqpHeaderVal + final case class IntVal(value: Int) extends AmqpHeaderVal + final case class LongVal(value: Long) extends AmqpHeaderVal + final case class StringVal(value: String) extends AmqpHeaderVal + final case class ArrayVal(v: collection.Seq[Any]) extends AmqpHeaderVal def from(value: AnyRef): AmqpHeaderVal = value match { case ls: LongString => StringVal(new String(ls.getBytes, "UTF-8")) @@ -161,7 +160,9 @@ object model { .expiration(props.expiration.orNull) .replyTo(props.replyTo.orNull) .clusterId(props.clusterId.orNull) - .headers(props.headers.mapValues[AnyRef](_.impure).asJava) + // Note we don't use mapValues here to maintain compatibility between + // Scala 2.12 and 2.13 + .headers(props.headers.map { case (key, value) => (key, value.impure) }.asJava) .build() } } diff --git a/examples/src/main/scala/dev/profunktor/fs2rabbit/examples/MonixAutoAckConsumer.scala b/examples/src/main/scala-2.12/dev/profunktor/fs2rabbit/examples/MonixAutoAckConsumer.scala similarity index 100% rename from examples/src/main/scala/dev/profunktor/fs2rabbit/examples/MonixAutoAckConsumer.scala rename to examples/src/main/scala-2.12/dev/profunktor/fs2rabbit/examples/MonixAutoAckConsumer.scala diff --git a/examples/src/main/scala/dev/profunktor/fs2rabbit/examples/ZIOAutoAckConsumer.scala b/examples/src/main/scala-2.12/dev/profunktor/fs2rabbit/examples/ZIOAutoAckConsumer.scala similarity index 93% rename from examples/src/main/scala/dev/profunktor/fs2rabbit/examples/ZIOAutoAckConsumer.scala rename to examples/src/main/scala-2.12/dev/profunktor/fs2rabbit/examples/ZIOAutoAckConsumer.scala index f3495e2b..a1d45028 100644 --- a/examples/src/main/scala/dev/profunktor/fs2rabbit/examples/ZIOAutoAckConsumer.scala +++ b/examples/src/main/scala-2.12/dev/profunktor/fs2rabbit/examples/ZIOAutoAckConsumer.scala @@ -19,9 +19,9 @@ package dev.profunktor.fs2rabbit.examples import dev.profunktor.fs2rabbit.config.Fs2RabbitConfig import dev.profunktor.fs2rabbit.interpreter.Fs2Rabbit -import scalaz.zio._ -import scalaz.zio.interop.catz._ -import scalaz.zio.interop.catz.implicits._ +import zio._ +import zio.interop.catz._ +import zio.interop.catz.implicits._ import dev.profunktor.fs2rabbit.resiliency.ResilientStream object ZIOAutoAckConsumer extends CatsApp { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bf9afd39..b7f93e5b 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -2,40 +2,49 @@ import sbt._ object Dependencies { - object Versions { + sealed trait Version { val catsEffect = "1.3.1" val fs2 = "1.0.5" val circe = "0.11.1" val amqpClient = "5.7.1" val logback = "1.2.3" - val monix = "3.0.0-RC2" - val zio = "1.0-RC5" - val kindProjector = "0.9.10" + val kindProjector = "0.10.3" val betterMonadicFor = "0.3.0" val scalaTest = "3.0.8" val scalaCheck = "1.14.0" } - object Libraries { - def circe(artifact: String): ModuleID = "io.circe" %% artifact % Versions.circe - def zio(artifact: String): ModuleID = "org.scalaz" %% artifact % Versions.zio + object Scala211Versions extends Version { + val monix = "3.0.0-RC3" + val zio = "1.0.0-RC8-4" + } + + object Scala212Versions extends Version { + val monix = "3.0.0-RC3" + val zio = "1.0.0-RC8-4" + } + + object Scala213Versions extends Version { + override val catsEffect = "2.0.0-M4" + override val fs2 = "1.1.0-M1" + override val circe = "0.12.0-M3" + } - lazy val amqpClient = "com.rabbitmq" % "amqp-client" % Versions.amqpClient - lazy val catsEffect = "org.typelevel" %% "cats-effect" % Versions.catsEffect - lazy val fs2Core = "co.fs2" %% "fs2-core" % Versions.fs2 + sealed abstract class VersionSpecificDeps[V <: Version](val version: V) { + def circe(artifact: String): ModuleID = "io.circe" %% artifact % version.circe + + lazy val amqpClient = "com.rabbitmq" % "amqp-client" % version.amqpClient + lazy val catsEffect = "org.typelevel" %% "cats-effect" % version.catsEffect + lazy val fs2Core = "co.fs2" %% "fs2-core" % version.fs2 // Compiler - lazy val kindProjector = "org.spire-math" % "kind-projector" % Versions.kindProjector cross CrossVersion.binary - lazy val betterMonadicFor = "com.olegpy" %% "better-monadic-for" % Versions.betterMonadicFor + lazy val kindProjector = "org.typelevel" % "kind-projector" % version.kindProjector cross CrossVersion.binary + lazy val betterMonadicFor = "com.olegpy" %% "better-monadic-for" % version.betterMonadicFor // Examples - lazy val monix = "io.monix" %% "monix" % Versions.monix - lazy val logback = "ch.qos.logback" % "logback-classic" % Versions.logback - - lazy val zioCore = zio("scalaz-zio") - lazy val zioCats = zio("scalaz-zio-interop-cats") + lazy val logback = "ch.qos.logback" % "logback-classic" % version.logback // Json libraries lazy val circeCore = circe("circe-core") @@ -43,8 +52,34 @@ object Dependencies { lazy val circeParser = circe("circe-parser") // Scala test libraries - lazy val scalaTest = "org.scalatest" %% "scalatest" % Versions.scalaTest - lazy val scalaCheck = "org.scalacheck" %% "scalacheck" % Versions.scalaCheck + lazy val scalaTest = "org.scalatest" %% "scalatest" % version.scalaTest + lazy val scalaCheck = "org.scalacheck" %% "scalacheck" % version.scalaCheck + + lazy val scalacOptions: Seq[String] = Seq.empty } + case object Scala211Dependencies extends VersionSpecificDeps(Scala211Versions) { + def zio(artifact: String): ModuleID = "dev.zio" %% artifact % version.zio + + // Example Libraries + lazy val monix = "io.monix" %% "monix" % version.monix + lazy val zioCore = zio("zio") + lazy val zioCats = zio("zio-interop-cats") + + override lazy val scalacOptions: Seq[String] = Seq("-Xmax-classfile-name", "100") + } + + case object Scala212Dependencies extends VersionSpecificDeps(Scala212Versions) { + def zio(artifact: String): ModuleID = "dev.zio" %% artifact % version.zio + + // Example Libraries + lazy val monix = "io.monix" %% "monix" % version.monix + lazy val zioCore = zio("zio") + lazy val zioCats = zio("zio-interop-cats") + + override lazy val scalacOptions: Seq[String] = Seq("-Xmax-classfile-name", "100") + } + + case object Scala213Dependencies extends VersionSpecificDeps(Scala213Versions) + } diff --git a/test-support/src/main/scala/dev/profunktor/fs2rabbit/DockerRabbit.scala b/test-support/src/main/scala/dev/profunktor/fs2rabbit/DockerRabbit.scala index de366b3f..8ff3f271 100644 --- a/test-support/src/main/scala/dev/profunktor/fs2rabbit/DockerRabbit.scala +++ b/test-support/src/main/scala/dev/profunktor/fs2rabbit/DockerRabbit.scala @@ -20,7 +20,7 @@ import cats.effect.{ContextShift, IO} import dev.profunktor.fs2rabbit.config.Fs2RabbitConfig import org.scalatest.{BeforeAndAfterAll, Suite} -import scala.concurrent.{ExecutionContext, SyncVar} +import scala.concurrent.ExecutionContext import scala.sys.process._ trait DockerRabbit extends BeforeAndAfterAll { self: Suite => @@ -79,7 +79,7 @@ object DockerRabbit { } def startDocker(port: Int, user: String, password: String, virtualHost: String): String = { - val dockerId = new SyncVar[String]() + val dockerId = new java.util.concurrent.LinkedBlockingQueue[String](1) val removeCmd = s"docker rm -f $dockerContainerName" @@ -115,7 +115,7 @@ object DockerRabbit { s"Docker $dockerImage startup observer" ) thread.start() - val id = dockerId.get + val id = dockerId.peek println(s"Docker ($dockerImage @ 127.0.0.1:$port) started successfully as $id ") id }