typelevel / fs2-grpc

gRPC implementation for FS2/cats-effect
MIT License
271 stars 57 forks source link

Running as standalone or with `mill` #714

Open otto-dev opened 6 months ago

otto-dev commented 6 months ago

Hi, I'm moving a project from sbt to mill and did not find a way to use the codegen outside of sbt. Is it possible to run the fs2-grpc codegen as a standalone, e.g. with protoc?

otto-dev commented 6 months ago

Okay, after searching maven for a while and a lot of trial and error, I seem to have gotten somewhere with

./bin/scalapbc  --plugin-artifact=org.typelevel:protoc-gen-fs2-grpc:2.7.14:default,classifier=unix,ext=sh,type=jar -v3.11.1 myprotofile.proto  --scala_out=flat_package,java_conversions,single_line_to_proto_string:out --fs2-grpc_out=out
otto-dev commented 6 months ago

I managed to use this library in mill with the following build setup.

import contrib.scalapblib._ and extends ScalaPBModule.

Add the following libraryDeps

    ivy"org.typelevel::fs2-grpc-runtime:${version.fs2Grpc}",
    ivy"io.grpc:grpc-netty-shaded:${version.grpcJava}",

and overwrite scalaPBAdditionalArgs as follows:

  override def scalaPBAdditionalArgs: T[Seq[String]] = T {
    Seq(
      s"--plugin-artifact=org.typelevel:protoc-gen-fs2-grpc:${version.fs2Grpc}:default,classifier=unix,ext=sh,type=jar",
      s"--fs2-grpc_out=${(T.dest / os.up / "compileScalaPB.dest").toIO.getCanonicalPath}",
    )
  }

Example:

import mill._, scalalib._
import contrib.scalapblib._
...

object ... extends ScalaPBModule with ...  {
  def scalaPBVersion = version.scalaPb

  def moduleDeps = Seq(...)

  def libraryDeps = Agg(
    ivy"org.typelevel::fs2-grpc-runtime:${version.fs2Grpc}",
    ivy"io.grpc:grpc-netty-shaded:${version.grpcJava}",
  )

  override def scalaPBIncludePath: T[Seq[PathRef]] = T {
    Seq(scalaPBUnpackProto()) ++ T
      .sequence(moduleDeps.collect { case m: ScalaPBModule =>
        m.scalaPBIncludePath
      })()
      .flatten
  }

  override def scalaPBAdditionalArgs: T[Seq[String]] = T {
    Seq(
      s"--plugin-artifact=org.typelevel:protoc-gen-fs2-grpc:${version.fs2Grpc}:default,classifier=unix,ext=sh,type=jar",
      s"--fs2-grpc_out=${(T.dest / os.up / "compileScalaPB.dest").toIO.getCanonicalPath}",
    )
  }
}

The T.dest / os.up / "compileScalaPB.dest is a hack, but this way the generated files end up in the correct place and are tracked by mill. The version.grpcJava should also ideally be provided by the scalapb plugin. I'm just glad I got something working for now.

mkrajc commented 2 months ago

@otto-dev Little improvement over your setup. Using scalaPBSearchDeps to include protos from dependencies and dedicated task with destination folder with generated fs2-grpc files to get rid of T.dest / os.up / "compileScalaPB.dest" hack.

import $ivy.`com.lihaoyi::mill-contrib-scalapblib:`

import mill._
import mill.contrib.scalapblib._
import mill.scalalib._
import mill.scalalib.scalafmt._

// define your versions
object Versions {
  val scala = "3.3.3"
  val scalaPB = "0.11.17"
  val fs2grpc = "2.7.16"
}

object ... extends ScalaPBModule  {
  def scalaVersion = Versions.scala
  def scalaPBVersion = Versions.scalaPB

  val fs2GrpcCodegen = s"org.typelevel:protoc-gen-fs2-grpc:${Versions.fs2grpc}"
  val fs2Grpc = s"org.typelevel::fs2-grpc-runtime:${Versions.fs2grpc}"

  override def scalaPBGrpc = true

  override def ivyDeps = T {
    // add fs2 grpc runtime to dependencies
    super.ivyDeps() ++ Agg(ivy"$fs2Grpc")
  }

  // creates separate destination for generated fs2 files
  def fs2GrpcOut: T[PathRef] = T.persistent {
    mill.api.Result.Success(PathRef(T.dest))
  }

  // extend module's generated sources with fs2 generated files
  override def generatedSources = T { super.generatedSources() :+ fs2GrpcOut() }

  // (optional) if you need to include .protos from dependencies into scalapbc compiler's path
  override def scalaPBSearchDeps = true

  // setup scalapbc additional arguments
  override def scalaPBAdditionalArgs = T {
    Seq(
      s"--plugin-artifact=$fs2GrpcCodegen:default,classifier=unix,ext=sh,type=jar",
      s"--fs2-grpc_out=${fs2GrpcOut().path.toIO.getCanonicalPath}"
    )
  }
}