Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ fileOverride {
"glob:**/kyo-stats-otel/**" {
runner.dialect = scala212source3
}
"glob:**/kyo-grpc-code-gen/**" {
runner.dialect = scala212source3
}
"glob:**/scala-3/**" {
runner.dialect = scala3
}
Expand Down
185 changes: 174 additions & 11 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import org.scalajs.jsenv.nodejs.*
import org.typelevel.scalacoptions.ScalacOption
import org.typelevel.scalacoptions.ScalacOptions
import org.typelevel.scalacoptions.ScalaVersion
import protocbridge.Target
import sbtdynver.DynVerPlugin.autoImport.*

val scala3Version = "3.8.1"
val scala3LTSVersion = "3.3.7"
val scala213Version = "2.13.18"
val scala212Version = "2.12.20"

val zioVersion = "2.1.24"
val catsVersion = "3.6.3"
Expand Down Expand Up @@ -56,7 +58,7 @@ lazy val `kyo-settings` = Seq(
crossScalaVersions := List(scala3Version),
scalacOptions ++= scalacOptionTokens(compilerOptions).value,
Test / scalacOptions --= scalacOptionTokens(Set(ScalacOptions.warnNonUnitStatement)).value,
scalafmtOnCompile := true,
scalafmtOnCompile := false,
scalacOptions += compilerOptionFailDiscard,
Test / testOptions += Tests.Argument("-oDG"),
ThisBuild / versionScheme := Some("early-semver"),
Expand Down Expand Up @@ -125,7 +127,8 @@ lazy val kyoJVM = project
`kyo-combinators`.jvm,
`kyo-playwright`.jvm,
`kyo-examples`.jvm,
`kyo-actor`.jvm
`kyo-actor`.jvm,
`kyo-grpc`.jvm
)

lazy val kyoJS = project
Expand All @@ -150,7 +153,8 @@ lazy val kyoJS = project
`kyo-zio`.js,
`kyo-cats`.js,
`kyo-combinators`.js,
`kyo-actor`.js
`kyo-actor`.js,
`kyo-grpc`.js
)

lazy val kyoNative = project
Expand Down Expand Up @@ -596,6 +600,124 @@ lazy val `kyo-cats` =
)
.jvmSettings(mimaCheck(false))

lazy val `kyo-grpc` =
crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.in(file("kyo-grpc"))
.settings(
crossScalaVersions := Seq.empty,
publishArtifact := false,
publish := {},
publishLocal := {}
)
.aggregate(
`kyo-grpc-core`,
`kyo-grpc-code-gen`,
`kyo-grpc-e2e`
)

lazy val `kyo-grpc-jvm` =
`kyo-grpc`
.jvm
.aggregate(`kyo-grpc-protoc-gen`.componentProjects.map(p => p: ProjectReference) *)

lazy val `kyo-grpc-core` =
crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("kyo-grpc-core"))
.dependsOn(`kyo-core`)
.settings(`kyo-settings`)
.settings(
libraryDependencies += "org.scalamock" %% "scalamock" % "7.5.0" % Test
)
.jvmSettings(
libraryDependencies ++= Seq(
"com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapb.compiler.Version.scalapbVersion,
"io.grpc" % "grpc-api" % "1.72.0",
// It is a little unusual to include this here but it greatly reduces the amount of generated code.
"io.grpc" % "grpc-stub" % "1.72.0",
"ch.qos.logback" % "logback-classic" % "1.5.18" % Test
)
).jsSettings(
`js-settings`,
libraryDependencies ++= Seq(
"com.thesamet.scalapb.grpcweb" %%% "scalapb-grpcweb" % "0.7.0")
)

lazy val `kyo-grpc-code-gen` =
crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("kyo-grpc-code-gen"))
.enablePlugins(BuildInfoPlugin)
.settings(
`kyo-settings`,
buildInfoKeys := Seq[BuildInfoKey](name, organization, version, scalaVersion, sbtVersion),
buildInfoPackage := "kyo.grpc.compiler",
crossScalaVersions := List(scala213Version, scala3Version),
scalacOptions ++= scalacOptionToken(ScalacOptions.source3).value,
libraryDependencies ++= Seq(
"com.thesamet.scalapb" %% "compilerplugin" % scalapb.compiler.Version.scalapbVersion,
"org.scala-lang.modules" %%% "scala-collection-compat" % "2.12.0",
"org.typelevel" %%% "paiges-core" % "0.4.3"
)
).jsSettings(
`js-settings`
)

lazy val `kyo-grpc-code-gen_2.12` =
`kyo-grpc-code-gen`
.jvm
.settings(scalaVersion := scala212Version)

lazy val `kyo-grpc-code-genJS_2.12` =
`kyo-grpc-code-gen`
.js
.settings(scalaVersion := scala212Version)

lazy val `kyo-grpc-protoc-gen` =
protocGenProject("kyo-grpc-protoc-gen", `kyo-grpc-code-gen_2.12`)
.settings(
`kyo-settings`,
scalaVersion := scala212Version,
crossScalaVersions := Seq(scala212Version),
Compile / mainClass := Some("kyo.grpc.compiler.CodeGenerator")
)
.aggregateProjectSettings(
scalaVersion := scala212Version,
crossScalaVersions := Seq(scala212Version)
)

lazy val `kyo-grpc-e2e` =
crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("kyo-grpc-e2e"))
.enablePlugins(LocalCodeGenPlugin)
.dependsOn(`kyo-grpc-core` % "compile->compile;test->test")
.settings(
`kyo-settings`,
publish / skip := true,
Compile / PB.protoSources += (ThisBuild / baseDirectory).value / "kyo-grpc-e2e/shared/src/main/protobuf",
Compile / PB.targets := Seq(
scalapb.gen() -> (Compile / sourceManaged).value / "scalapb",
// Users of the plugin can use: kyo.grpc.gen() -> (Compile / sourceManaged).value / "scalapb"
genModule("kyo.grpc.compiler.CodeGenerator$") -> (Compile / sourceManaged).value / "scalapb"
),
Compile / scalacOptions ++= scalacOptionToken(ScalacOptions.warnOption("conf:src=.*/src_managed/main/scalapb/kgrpc/.*:silent")).value
).jvmSettings(
codeGenClasspath := (`kyo-grpc-code-gen_2.12` / Compile / fullClasspath).value,
libraryDependencies ++= Seq(
"io.grpc" % "grpc-netty-shaded" % "1.72.0",
"ch.qos.logback" % "logback-classic" % "1.5.18" % Test
)
).jsSettings(
`js-settings`,
codeGenClasspath := (`kyo-grpc-code-genJS_2.12` / Compile / fullClasspath).value,
libraryDependencies += "com.thesamet.scalapb.grpcweb" %%% "scalapb-grpcweb" % "0.7.0"
)

lazy val `kyo-combinators` =
crossProject(JSPlatform, JVMPlatform, NativePlatform)
.withoutSuffixFor(JVMPlatform)
Expand Down Expand Up @@ -647,17 +769,52 @@ lazy val `kyo-bench` =
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("kyo-bench"))
.enablePlugins(JmhPlugin)
.dependsOn(`kyo-core`)
.dependsOn(`kyo-parse`)
.dependsOn(`kyo-sttp`)
.dependsOn(`kyo-stm`)
.dependsOn(`kyo-direct`)
.dependsOn(`kyo-scheduler-zio`)
.dependsOn(`kyo-scheduler-cats`)
.enablePlugins(Fs2Grpc, JmhPlugin, LocalCodeGenPlugin)
.disablePlugins(MimaPlugin)
.dependsOn(
`kyo-core`,
`kyo-direct`,
`kyo-grpc-core`,
`kyo-parse`,
`kyo-scheduler-cats`,
`kyo-scheduler-zio`,
`kyo-stm`,
`kyo-sttp`
)
.settings(
`kyo-settings`,
publish / skip := true,
Compile / PB.protoSources += baseDirectory.value.getParentFile / "src" / "main" / "protobuf",
Compile / PB.targets := {
val scalapbDir = (Compile / sourceManaged).value / "scalapb"
// This includes the base scalapb.gen.
val catsGen = Fs2GrpcPlugin.autoImport.scalapbCodeGenerators.value
catsGen ++ Seq[Target](
scalapb.gen(scala3Sources = true) -> scalapbDir / "vanilla",
scalapb.zio_grpc.ZioCodeGenerator -> scalapbDir,
genModule("kyo.grpc.compiler.CodeGenerator$") -> scalapbDir
)
},
Compile / PB.generate ~= { files =>
files.filter(_.isFile).filter(_.getPath.contains("/vanilla/")).foreach { file =>
val fileContent = IO.read(file)
val updatedContent = fileContent
// Workaround for https://github.com/scalapb/ScalaPB/issues/1816.
.replace(
"_unknownFields__.parseField(tag, _input__)",
"_unknownFields__.parseField(tag, _input__): Unit"
)
// Hacky workaround to not get a collision with the one generated by Kyo and ZIO.
.replace(
"kgrpc.bench",
"vanilla.kgrpc.bench",
)
IO.write(file, updatedContent)
}
files
},
codeGenClasspath := (`kyo-grpc-code-gen_2.12` / Compile / fullClasspath).value,
Compile / scalacOptions ++= scalacOptionToken(ScalacOptions.warnOption("conf:src=.*/src_managed/main/scalapb/kgrpc/.*:silent")).value,
Test / testForkedParallel := true,
// Forks each test suite individually
Test / testGrouping := {
Expand All @@ -681,6 +838,7 @@ lazy val `kyo-bench` =
)
}
},
libraryDependencies += "io.grpc" % "grpc-netty-shaded" % "1.72.0",
libraryDependencies += "dev.zio" %% "izumi-reflect" % "3.0.9",
libraryDependencies += "org.typelevel" %% "cats-effect" % catsVersion,
libraryDependencies += "org.typelevel" %% "log4cats-core" % "2.7.1",
Expand Down Expand Up @@ -713,6 +871,7 @@ lazy val readme =
.crossType(CrossType.Full)
.in(file("target/readme"))
.enablePlugins(MdocPlugin)
.disablePlugins(ProtocPlugin)
.settings(
`kyo-settings`,
mdocIn := new File("./../../README-in.md"),
Expand Down Expand Up @@ -773,6 +932,10 @@ def mimaCheck(failOnProblem: Boolean) =
mimaFailOnProblem := failOnProblem
)

def sharedSourceDir(conf: String) = Def.setting {
CrossType.Full.sharedSrcDir(baseDirectory.value, conf).get.getParentFile
}

// --- Scalafix

lazy val V = _root_.scalafix.sbt.BuildInfo
Expand Down
19 changes: 19 additions & 0 deletions kyo-bench/src/main/protobuf/bench.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
syntax = "proto3";

// Don't use kyo here because otherwise it cannot derive the Frame.
package kgrpc;

service TestService {
rpc OneToOne(Request) returns (Response);
rpc OneToMany(Request) returns (stream Response);
rpc ManyToOne(stream Request) returns (Response);
rpc ManyToMany(stream Request) returns (stream Response);
}

message Request {
string message = 1;
}

message Response {
string message = 1;
}
80 changes: 80 additions & 0 deletions kyo-bench/src/main/scala/kyo/bench/arena/ArenaBench2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package kyo.bench.arena

import kyo.bench.BaseBench
import org.openjdk.jmh.annotations.*
import scala.compiletime.uninitialized

// TODO: What to call this?
abstract class ArenaBench2[A](val expectedResult: A) extends BaseBench:

def forkCats(catsBench: cats.effect.IO[A])(using cats.effect.unsafe.IORuntime): A =
cats.effect.IO.cede.flatMap(_ => catsBench).unsafeRunSync()

def forkKyo(kyoBenchFiber: kyo.<[A, kyo.Async & kyo.Abort[Throwable] & kyo.Scope]): A =
import kyo.*
import AllowUnsafe.embrace.danger
given Frame = Frame.internal
kyoBenchFiber.handle(
kyo.Scope.run,
Fiber.initUnscoped(_),
_.map(_.block(Duration.Infinity)),
Sync.Unsafe.evalOrThrow
).getOrThrow
end forkKyo

def forkZIO(zioBench: zio.Task[A])(using zioRuntime: zio.Runtime[Any]): A = zio.Unsafe.unsafe(implicit u =>
zioRuntime.unsafe.run(zio.ZIO.yieldNow.flatMap(_ => zioBench)).getOrThrow()
)

end ArenaBench2

object ArenaBench2:

@State(Scope.Benchmark)
class CatsRuntime:

var ioRuntime: cats.effect.unsafe.IORuntime = uninitialized
given cats.effect.unsafe.IORuntime = ioRuntime

@Setup
def setup() =
ioRuntime =
if System.getProperty("replaceCatsExecutor", "false") == "true" then
kyo.KyoSchedulerIORuntime.global
else
cats.effect.unsafe.implicits.global
end setup

end CatsRuntime

@State(Scope.Benchmark)
class ZIORuntime:

var zioRuntime: zio.Runtime[Any] = uninitialized

private var finalizer: () => Unit = () => ()

@Setup
def setup(): Unit =
val zioRuntimeLayer: zio.ZLayer[Any, Any, Any] =
if System.getProperty("replaceZIOExecutor", "false") == "true" then
kyo.KyoSchedulerZIORuntime.layer
else
zio.ZLayer.empty

zioRuntime =
if zioRuntimeLayer ne zio.ZLayer.empty then
val (runtime, finalizer) = ZIORuntime.fromLayerWithFinalizer(zioRuntimeLayer)
this.finalizer = finalizer
runtime
else
zio.Runtime.default
end setup

@TearDown
def tearDown(): Unit =
finalizer()

end ZIORuntime

end ArenaBench2
Loading