Skip to content

Commit

Permalink
Introduce a code gen that supports metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
thesamet committed May 2, 2020
1 parent c40c6f9 commit d47a8ab
Show file tree
Hide file tree
Showing 18 changed files with 505 additions and 136 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ target/
project/target/
*.class

.bloop
.metals
metals.sbt
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ stages:
- name: release
if: (branch = master AND type = push) OR (tag IS present)

script:
- sbt ++$TRAVIS_SCALA_VERSION test

jobs:
include:
- stage: test
Expand Down
61 changes: 48 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,59 @@
ScalaPB can generate gRPC clients and services from a protocol buffer
definition.

This library provides a Scala.js runtime that makes the gRPC client generated
by ScalaPB work with [grpc-web](https://github.com/grpc/grpc-web).
This library provides a code generator and runtime that enables calling gRPC services
from your Scala.js code using [grpc-web](https://github.com/grpc/grpc-web).

## Usage:

1. Add `sbt-scalajs-bundler` to your `project/plugins.sbt`:
1. Add the following to your `project/plugins.sbt`:

```
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.17.0")
```
val grpcWebVersion = "0.3.0"

and enable it in your `build.sbt`:
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

```
enablePlugins(ScalaJSBundlerPlugin)
```
addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.31")

libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.10.2"

2. Add a dependency on `scalapb-grpcweb` to your Scala.js project:
libraryDependencies += "com.thesamet.scalapb.zio-grpc" %% "zio-grpc-codegen" % grpcWebVersion

addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.17.0")

2. Add a cross-project with the protos that will be shared by Scala.js and
JVM:

```
libraryDependencies += "com.thesamet.scalapb" %%% "scalapb-grpcweb" % "0.2.0"
lazy val protos =
crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
.in(file("protos"))
.settings(
PB.protoSources in Compile := Seq(
(baseDirectory in ThisBuild).value / "protos" / "src" / "main" / "protobuf"
),
libraryDependencies += "com.thesamet.scalapb" %%% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion
)
.jvmSettings(
libraryDependencies += "com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapb.compiler.Version.scalapbVersion,
PB.targets in Compile := Seq(
scalapb.gen() -> (sourceManaged in Compile).value
),
)
.jsSettings(
// publish locally and update the version for test
libraryDependencies += "com.thesamet.scalapb" %%% "scalapb-grpcweb" % scalapb.grpcweb.BuildInfo.version,
PB.targets in Compile := Seq(
scalapb.gen(grpc=false) -> (sourceManaged in Compile).value,
scalapb.grpcweb.GrpcWebCodeGenerator -> (sourceManaged in Compile).value
)
)
```

3. In your client code, instantiate the stub like this:

```
val stub = new TestServiceStub(Channels.grpcwebChannel("http://localhost:8081"))
val stub = TestServiceStub.stub(Channels.grpcwebChannel("http://localhost:8081"))
```

4. Now, you can call it like:
Expand All @@ -42,6 +68,15 @@ by ScalaPB work with [grpc-web](https://github.com/grpc/grpc-web).
f => println("Unary", f)
}
// You can also pass metadata
stub.unary(req, Metadata("header1" -> "value1").onComplete {
f => println("Unary", f)
}
stub.unary(req, Metadata().onComplete {
f => println("Unary", f)
}
// Make an async server streaming call
stub.serverStreaming(req, new StreamObserver[Res] {
override def onNext(value: Res): Unit = {
Expand Down
71 changes: 66 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,15 +1,76 @@
scalaVersion := "2.12.10"

lazy val root = project
.in(file("."))
val scalapbVersion = "0.10.2"

ThisBuild / crossScalaVersions := Seq(Scala212, Scala213)

skip in publish := true

sonatypeProfileName := "com.thesamet"

val Scala212 = "2.12.10"
val Scala213 = "2.13.1"

lazy val codeGen = project
.in(file("code-gen"))
.enablePlugins(BuildInfoPlugin)
.settings(
name := "scalapb-grpcweb-code-gen",
buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion),
buildInfoPackage := "scalapb.grpcweb",
libraryDependencies ++= Seq(
"com.thesamet.scalapb" %% "compilerplugin" % scalapbVersion
)
)

def projDef(name: String, shebang: Boolean) =
sbt
.Project(name, new File(name))
.enablePlugins(AssemblyPlugin)
.dependsOn(codeGen)
.settings(
assemblyOption in assembly := (assemblyOption in assembly).value.copy(
prependShellScript = Some(
sbtassembly.AssemblyPlugin.defaultUniversalScript(shebang = shebang)
)
),
skip in publish := true,
Compile / mainClass := Some("scalapb.grpcweb.GrpcWebCodeGenerator")
)

lazy val protocGenScalaGrpcWebUnix =
projDef("protoc-gen-scala-grpcweb-unix", shebang = true)

lazy val protocGenScalaGrpcWebWindows =
projDef("protoc-gen-scala-grpcweb-windows", shebang = false)

lazy val protocGenScalaGrpcWeb = project
.settings(
crossScalaVersions := List(Scala213),
name := "protoc-gen-scala-grpcweb",
publishArtifact in (Compile, packageDoc) := false,
publishArtifact in (Compile, packageSrc) := false,
crossPaths := false,
addArtifact(
Artifact("protoc-gen-scala-grpcweb", "jar", "sh", "unix"),
assembly in (protocGenScalaGrpcWebUnix, Compile)
),
addArtifact(
Artifact("protoc-gen-scala-grpcweb", "jar", "bat", "windows"),
assembly in (protocGenScalaGrpcWebWindows, Compile)
),
autoScalaLibrary := false
)

lazy val grpcweb = project
.in(file("grpcweb"))
.enablePlugins(ScalaJSBundlerPlugin)
.enablePlugins(ScalaJSPlugin)
.settings(
crossScalaVersions := Seq("2.12.10", "2.13.1"),
sonatypeProfileName := "com.thesamet",
crossScalaVersions := Seq(Scala212, Scala213),
name := "scalapb-grpcweb",
libraryDependencies ++= Seq(
"com.thesamet.scalapb" %%% "scalapb-runtime" % "0.10.2",
"com.thesamet.scalapb" %%% "scalapb-runtime" % scalapbVersion,
"com.thesamet.scalapb" %%% "protobuf-runtime-scala" % "0.8.5"
),
npmDependencies in Compile += "grpc-web" -> "1.0.7"
Expand Down
53 changes: 53 additions & 0 deletions code-gen/src/main/scala/scalapb/grpcweb/GrpcWebCodeGenerator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package scalapb.grpcweb

import com.google.protobuf.Descriptors.FileDescriptor
import com.google.protobuf.ExtensionRegistry
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
import protocbridge.codegen.{CodeGenApp, CodeGenRequest, CodeGenResponse}
import scalapb.compiler._
import scalapb.options.compiler.Scalapb
import scala.jdk.CollectionConverters._

object GrpcWebCodeGenerator extends CodeGenApp {
override def registerExtensions(registry: ExtensionRegistry): Unit =
Scalapb.registerAllExtensions(registry)

def process(request: CodeGenRequest): CodeGenResponse = {
ProtobufGenerator.parseParameters(request.parameter) match {
case Right(params) =>
try {
val implicits =
new DescriptorImplicits(params, request.allProtos)
val generatedFiles = request.filesToGenerate.flatMap { file =>
generateServiceFiles(file, implicits)
}
CodeGenResponse.succeed(
generatedFiles
)
} catch {
case e: GeneratorException =>
CodeGenResponse.fail(e.message)
}
case Left(error) =>
CodeGenResponse.fail(error)
}

}

private def generateServiceFiles(
file: FileDescriptor,
implicits: DescriptorImplicits
): Seq[CodeGeneratorResponse.File] = {
import implicits._
file.getServices.asScala.map { service =>
val p = new GrpcWebServicePrinter(service, implicits)
val code = p.printService(FunctionalPrinter()).result()
CodeGeneratorResponse.File
.newBuilder()
.setName(p.scalaFileName)
.setContent(code)
.build
}.toSeq
}

}
Loading

0 comments on commit d47a8ab

Please sign in to comment.