Skip to content

Commit 5760a22

Browse files
authored
Merge pull request #449 from soundcloud/scala3
Support Scala 3 cross compile
2 parents 6d79a9d + a316597 commit 5760a22

File tree

12 files changed

+302
-45
lines changed

12 files changed

+302
-45
lines changed

.github/workflows/build.yml

+5-4
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ jobs:
1414
runs-on: ubuntu-latest
1515

1616
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
1721
- name: Install JDK 1.8
1822
uses: actions/setup-java@v4
1923
with:
2024
java-version: 8
2125
distribution: temurin
22-
- name: Checkout repository
23-
uses: actions/checkout@v4
24-
with:
25-
fetch-depth: 0
26+
- uses: sc-actions-forks/setup-sbt@96cf3f09dc501acdad7807fffe97dba9fa0709be #v1.1.5
2627
- name: Run tests
2728
run: sbt +test scripted
2829
- name: Lint

build.sbt

+64-23
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
lazy val scala212 = "2.12.18"
2-
lazy val scala213 = "2.13.7"
1+
import sbt.CrossVersion
2+
3+
lazy val scala212 = "2.12.18"
4+
lazy val scala213 = "2.13.14"
5+
lazy val scala3LTS = "3.3.4"
6+
lazy val scala3 = "3.6.3"
37

48
lazy val commonSettings = List(
59
scalaVersion := scala212,
6-
scalacOptions ++= Seq(
7-
"-encoding",
8-
"utf8",
9-
"-deprecation",
10-
"-unchecked",
11-
"-Xlint",
12-
"-Xfatal-warnings"
13-
),
14-
Compile / console / scalacOptions --= Seq("-deprecation", "-Xfatal-warnings", "-Xlint")
10+
scalacOptions ++= {
11+
CrossVersion.partialVersion(scalaVersion.value) match {
12+
case Some((2, _)) => scalacOptions ++= Seq("-Xlint")
13+
case _ => ()
14+
}
15+
Seq(
16+
"-encoding",
17+
"utf8",
18+
"-deprecation",
19+
"-unchecked",
20+
"-Xfatal-warnings"
21+
)
22+
},
23+
Compile / console / scalacOptions --= Seq("-deprecation", "-Xfatal-warnings", "-Xlint"),
24+
scalafmtOnCompile := true
1525
)
1626

1727
lazy val codegen = (project in file("codegen"))
@@ -32,20 +42,51 @@ lazy val codegen = (project in file("codegen"))
3242
lazy val runtime = (project in file("runtime")).settings(
3343
commonSettings,
3444
name := "twinagle-runtime",
35-
crossScalaVersions := Seq(scala212, scala213),
36-
libraryDependencies ++= Seq(
37-
"com.twitter" %% "finagle-http" % "21.12.0",
38-
"com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion,
39-
"com.thesamet.scalapb" %% "scalapb-json4s" % "0.12.1",
40-
"org.json4s" %% "json4s-native" % "4.0.7",
41-
"org.specs2" %% "specs2-core" % "4.20.5" % Test,
42-
"org.specs2" %% "specs2-mock" % "4.20.5" % Test
43-
),
45+
crossScalaVersions := Seq(scala212, scala213, scala3LTS, scala3),
46+
// finagle uses 2.13 heavily so we will ignore our project runtime compat
47+
excludeDependencies += "org.scala-lang.modules" % "scala-collection-compat_3",
48+
libraryDependencies ++= {
49+
Seq(
50+
"com.twitter" %% "finagle-http" % "24.2.0" cross CrossVersion.for3Use2_13,
51+
"com.thesamet.scalapb" %% "scalapb-runtime" % "0.11.17",
52+
"com.thesamet.scalapb" %% "scalapb-json4s" % "0.12.1",
53+
"org.specs2" %% "specs2-core" % "4.20.8" % Test cross CrossVersion.for3Use2_13
54+
)
55+
},
56+
libraryDependencies ++= {
57+
CrossVersion.partialVersion(scalaVersion.value) match {
58+
case Some((2, 13)) | Some((2, 12)) =>
59+
Seq(
60+
"org.json4s" %% "json4s-native" % "4.0.7",
61+
"org.specs2" %% "specs2-mock" % "4.20.8" % Test
62+
)
63+
case Some((3, 3)) =>
64+
Seq(
65+
"org.playframework" %% "play-json" % "3.0.4",
66+
"org.scalamock" %% "scalamock" % "6.1.1" % Test
67+
)
68+
case Some((3, _)) =>
69+
Seq(
70+
"org.playframework" %% "play-json" % "3.0.4",
71+
"org.scalamock" %% "scalamock" % "7.1.0" % Test
72+
)
73+
case _ => Seq.empty
74+
}
75+
},
4476
// compile protobuf messages for unit tests
4577
Project.inConfig(Test)(sbtprotoc.ProtocPlugin.protobufConfigSettings),
46-
Test / PB.targets := Seq(
47-
scalapb.gen(flatPackage = true) -> (Test / sourceManaged).value
48-
)
78+
Test / scalacOptions += {
79+
CrossVersion.partialVersion(scalaVersion.value) match {
80+
case Some((3, minor)) if minor > 3 => "-experimental"
81+
case _ => ""
82+
}
83+
},
84+
Test / PB.targets := {
85+
val gen3 = CrossVersion.partialVersion(scalaVersion.value).exists(a => a._1 == 3L)
86+
Seq(
87+
scalapb.gen(flatPackage = true, scala3Sources = gen3) -> (Test / sourceManaged).value
88+
)
89+
}
4990
)
5091

5192
lazy val root = (project in file("."))

codegen/src/main/scala/com/soundcloud/twinagle/codegen/Twinagle.scala

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.soundcloud.twinagle.codegen
22

33
import protocbridge.{JvmGenerator, Target}
4-
import sbt.Keys._
54
import sbt._
5+
import sbt.Keys._
66
import sbt.plugins.JvmPlugin
77
import sbtprotoc.ProtocPlugin.autoImport.PB
88

@@ -14,9 +14,19 @@ object Twinagle extends AutoPlugin {
1414
override def trigger: PluginTrigger = NoTrigger
1515

1616
override def projectSettings: Seq[Def.Setting[_]] = List(
17-
scalapbCodeGeneratorOptions := Set(
18-
scalapb.GeneratorOption.FlatPackage // don't include proto filename in scala package name
19-
),
17+
scalapbCodeGeneratorOptions := {
18+
CrossVersion.partialVersion(scalaVersion.value) match {
19+
case Some((3, _)) =>
20+
Set(
21+
scalapb.GeneratorOption.FlatPackage, // don't include proto filename in scala package name
22+
scalapb.GeneratorOption.Scala3Sources
23+
)
24+
case _ =>
25+
Set(
26+
scalapb.GeneratorOption.FlatPackage // don't include proto filename in scala package name
27+
)
28+
}
29+
},
2030
Compile / PB.targets := Seq(
2131
Target(
2232
scalapb.gen(scalapbCodeGeneratorOptions.value - scalapb.GeneratorOption.Grpc),
@@ -30,6 +40,12 @@ object Twinagle extends AutoPlugin {
3040
),
3141
libraryDependencies ++= Seq(
3242
"com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf"
33-
)
43+
),
44+
excludeDependencies ++= {
45+
CrossVersion.partialVersion(scalaVersion.value) match {
46+
case Some((3, _)) => Seq("org.scala-lang.modules" % "scala-collection-compat_2.13")
47+
case _ => Seq.empty
48+
}
49+
}
3450
)
3551
}

project/build.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=1.8.3
1+
sbt.version=1.10.7

project/plugins.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ libraryDependencies ++= Seq(
1111
"com.thesamet.scalapb" %% "compilerplugin" % "0.11.15"
1212
)
1313
// only necessary so we can generate protos for tests
14-
addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.7")
14+
addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.7")

release.sbt

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ inThisBuild(
1919
name = "Oren Berkowitz",
2020
email = "[email protected]",
2121
url = url("https://github.com/oberkowitz")
22+
),
23+
Developer(
24+
id = "rbscgh",
25+
name = "Rahul Bhonsale",
26+
email = "[email protected]",
27+
url = url("https://github.com/rbscgh")
2228
)
2329
),
2430
scmInfo := Some(

runtime/src/main/scala/com/soundcloud/twinagle/JsonError.scala runtime/src/main/scala-2/com/soundcloud/twinagle/JsonError.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ private[twinagle] object JsonError {
1919
import org.json4s.native.Serialization.{read, write}
2020

2121
import scala.util.control.Exception._
22-
implicit val formats = Serialization.formats(NoTypeHints)
22+
implicit val formats: AnyRef with Formats = Serialization.formats(NoTypeHints)
2323

2424
def fromString(str: String): Option[JsonError] = allCatch opt {
2525
read[JsonError](str)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.soundcloud.twinagle
2+
3+
import play.api.libs.json.{Json, OFormat}
4+
5+
/** JsonError is the JSON representation of `TwinagleException`s.
6+
*
7+
* If only there were some Language we could use to Define these kind of Interfaces
8+
* so that we could generate code to (de-)serialize the errors ;).
9+
*/
10+
private[twinagle] case class JsonError(
11+
code: String,
12+
msg: String,
13+
meta: Option[Map[String, String]]
14+
)
15+
16+
private[twinagle] object JsonError {
17+
import scala.util.control.Exception.*
18+
19+
implicit val _format: OFormat[JsonError] = Json.format[JsonError]
20+
21+
def fromString(str: String): Option[JsonError] = allCatch opt {
22+
Json.parse(str).as[JsonError]
23+
}
24+
25+
def toString(err: JsonError): String = Json.stringify(Json.toJson(err))
26+
}

runtime/src/main/scala/com/soundcloud/twinagle/ClientEndpointBuilder.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import scalapb.{GeneratedMessage, GeneratedMessageCompanion}
99
*/
1010
class ClientEndpointBuilder(
1111
httpClient: Service[Request, Response],
12-
extension: EndpointMetadata => Filter.TypeAgnostic,
12+
`extension`: EndpointMetadata => Filter.TypeAgnostic,
1313
prefix: String
1414
) {
1515

@@ -24,7 +24,7 @@ class ClientEndpointBuilder(
2424
](
2525
endpointMetadata: EndpointMetadata
2626
): Service[Req, Resp] = {
27-
extension(endpointMetadata).toFilter andThen
27+
`extension`(endpointMetadata).toFilter andThen
2828
new TracingFilter[Req, Resp](endpointMetadata) andThen
2929
new JsonClientFilter[Req, Resp](s"$prefix/${endpointMetadata.service}/${endpointMetadata.rpc}") andThen
3030
new TwirpHttpClient andThen
@@ -37,7 +37,7 @@ class ClientEndpointBuilder(
3737
](
3838
endpointMetadata: EndpointMetadata
3939
): Service[Req, Resp] = {
40-
extension(endpointMetadata).toFilter andThen
40+
`extension`(endpointMetadata).toFilter andThen
4141
new TracingFilter[Req, Resp](endpointMetadata) andThen
4242
new ProtobufClientFilter[Req, Resp](s"$prefix/${endpointMetadata.service}/${endpointMetadata.rpc}") andThen
4343
new TwirpHttpClient andThen

runtime/src/test/scala/com/soundcloud/twinagle/ServerSpec.scala runtime/src/test/scala-2/com/soundcloud/twinagle/ServerSpec.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.soundcloud.twinagle
22

33
import com.soundcloud.twinagle.test.TestMessage
4+
import com.twitter.finagle.http._
45
import com.twitter.finagle.{CancelledRequestException, Failure, Filter, Service}
5-
import com.twitter.finagle.http.{MediaType, Method, Request, Response, Status}
66
import com.twitter.util.{Await, Future}
77
import org.specs2.mock.Mockito
88
import org.specs2.mutable.Specification
@@ -14,12 +14,12 @@ import scala.collection.mutable.ListBuffer
1414
class ServerSpec extends Specification with Mockito {
1515
trait Context extends Scope {
1616
val rpc = mock[TestMessage => Future[TestMessage]]
17-
val protoService = ProtoService(
17+
val protoService: ProtoService = ProtoService(
1818
Seq(
1919
ProtoRpcBuilder(EndpointMetadata("svc", "rpc"), rpc)
2020
)
2121
)
22-
val server = ServerBuilder()
22+
val server: Service[Request, Response] = ServerBuilder()
2323
.register(protoService)
2424
.build
2525
}

0 commit comments

Comments
 (0)