Skip to content

Commit

Permalink
fix: protoPaths as import params to protoc (Maven) (#1768)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-alfers authored Apr 17, 2023
1 parent 83c052c commit 8e07c1a
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ jobs:
- name: Test Maven Java
run: |-
cd plugin-tester-java
mvn -Dakka.grpc.project.version=`cat ~/.version` akka-grpc:generate compile
mvn -Dakka.grpc.project.version=`cat ~/.version` compile
- name: Test Maven Scala
run: |-
Expand Down
21 changes: 15 additions & 6 deletions docs/src/main/paradox/buildtools/maven.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,22 @@ which is a relative path to the project basedir. The below configuration overrid

## Loading proto files from artifacts

In gRPC it is common to make the version of the protocol you are supporting
explicit by duplicating the proto definitions in your project.
Instead of duplicating the `.proto` definitions between server and client projects, you can add artifacts that contain proto definitions to your build.

When using @ref[sbt](sbt.md) as a build system, we also support loading your
proto definitions from a dependency classpath. This is not yet supported
for Maven and @ref[Gradle](gradle.md). If you are interested in this feature
it is tracked in issue [#152](https://github.com/akka/akka-grpc/issues/152).
A full example of a maven build definition can be found [here](https://github.com/akka/akka-grpc/blob/main/plugin-tester-java/pom.xml) which allows to import external protos like this:

Java
: @@snip[proto imports](/plugin-tester-java/src/main/protobuf/helloworld.proto) { #import-external-proto-files }

The `pom.xml` has to be adjusted as follows. As a first step in the `<build>`, the `maven-dependency-plugin` needs to pull in the artifacts containing the protobuf file. The `<outputDirectory>` is the place where the protos from the dependencies are getting placed into (`target`):

Java
: @@snip[unpack protos](/plugin-tester-java/pom.xml) { #unpack-protos }

Finally, the `target/proto` directory has to be introduced to the `akka-grpc-maven-plugin` to be picket up during `protoc` compilation. Make sure to include all other folders from the project as well, since the definition of `<protoPaths>` overrides the default:

Java
: @@snip[unpack protos](/plugin-tester-java/pom.xml) { #all-proto-paths }

## JDK 8 support

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,21 +125,25 @@ abstract class AbstractGenerateMojo @Inject() (buildContext: BuildContext) exten

def addGeneratedSourceRoot(generatedSourcesDir: String): Unit

//https://maven.apache.org/plugin-developers/common-bugs.html#Resolving_Relative_Paths
def normalize(protoPath: String): File = {
val protoFile = new File(protoPath)
if (!protoFile.isAbsolute()) {
new File(project.getBasedir(), protoPath).toPath().normalize().toFile()
} else {
protoFile
}
}

lazy val normalizedProtoPaths = protoPaths.asScala.map(normalize)

override def execute(): Unit = {
val chosenLanguage = parseLanguage(language)

var directoryFound = false
protoPaths.forEach { protoPath =>

normalizedProtoPaths.foreach { protoDir =>
// verify proto dir exists
//https://maven.apache.org/plugin-developers/common-bugs.html#Resolving_Relative_Paths
val protoDir = {
val protoFile = new File(protoPath)
if (!protoFile.isAbsolute()) {
new File(project.getBasedir(), protoPath).toPath().normalize().toFile()
} else {
protoFile
}
}
if (protoDir.exists()) {
directoryFound = true
// generated sources should be compiled
Expand Down Expand Up @@ -213,15 +217,16 @@ abstract class AbstractGenerateMojo @Inject() (buildContext: BuildContext) exten
private[this] def executeProtoc(
protocCommand: Seq[String] => Int,
schemas: Set[File],
protoDir: File,
protocOptions: Seq[String],
targets: Seq[Target]): Int =
try {
val incPath = "-I" + protoDir.getCanonicalPath
val protocIncludePaths = normalizedProtoPaths.map { includePath =>
"-I" + includePath.getCanonicalPath
}
protocbridge.ProtocBridge.execute(
ProtocRunner.fromFunction((args, _) => protocCommand(args)),
targets,
Seq(incPath) ++ protocOptions ++ schemas.map(_.getCanonicalPath),
protocIncludePaths ++ protocOptions ++ schemas.map(_.getCanonicalPath),
artifact =>
throw new RuntimeException(
s"The version of sbt-protoc you are using is incompatible with '${artifact}' code generator. Please update sbt-protoc to a version >= 0.99.33"))
Expand Down Expand Up @@ -256,7 +261,7 @@ abstract class AbstractGenerateMojo @Inject() (buildContext: BuildContext) exten

getLog.info("Compiling protobuf")
val (out, err, exitCode) = captureStdOutAnderr {
executeProtoc(protocCommand, schemas, protoDir, protocOptions, generatedTargets)
executeProtoc(protocCommand, schemas, protocOptions, generatedTargets)
}
if (exitCode != 0) {
err.split("\n\r").map(_.trim).map(parseError).foreach {
Expand Down
4 changes: 2 additions & 2 deletions plugin-tester-java/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# sbt-plugin Tester for Scala
# sbt-plugin Tester for Java

Use this project to test the behavior of the sbt plugin without the need of separately releasing it from within the main build.

```
project akka-grpc-plugin-tester-java
test
```
```
77 changes: 63 additions & 14 deletions plugin-tester-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<akka.http.cors.version>1.1.0</akka.http.cors.version>
<grpc.version>1.48.1</grpc.version> <!-- checked synced by VersionSyncCheckPlugin -->
<project.encoding>UTF-8</project.encoding>
<build-helper-maven-plugin>3.3.0</build-helper-maven-plugin>
<protobuf-java.version>3.22.2</protobuf-java.version>
<proto-google-common-protos.version>2.15.0</proto-google-common-protos.version>
</properties>

<dependencies>
Expand All @@ -41,6 +44,57 @@
</dependencies>
<build>
<plugins>

<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven-dependency-plugin.version}</version>
<executions>
<execution>
<id>getClasspathFilenames</id>
<goals>
<!-- provides the jars of the classpath as properties inside of maven
so that we can refer to one of the jars in the exec plugin config below -->
<goal>properties</goal>
</goals>
</execution>

<!-- #unpack-protos -->
<execution>
<id>unpack</id>
<phase>generate-sources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf-java.version}</version>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/proto</outputDirectory>
<includes>**/*.proto</includes>
</artifactItem>
<artifactItem>
<groupId>com.google.api.grpc</groupId>
<artifactId>proto-google-common-protos</artifactId>
<version>${proto-google-common-protos.version}</version>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/proto</outputDirectory>
<includes>**/*.proto</includes>
</artifactItem>
</artifactItems>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</execution>
<!-- #unpack-protos -->

</executions>
</plugin>

<plugin>
<groupId>com.lightbend.akka.grpc</groupId>
<artifactId>akka-grpc-maven-plugin</artifactId>
Expand All @@ -57,23 +111,17 @@
<serverPowerApis>true</serverPowerApis>
</generatorSettings>
<includeStdTypes>true</includeStdTypes>
<language>Java</language>
<!-- #all-proto-paths -->
<protoPaths>
<protoPath>target/proto</protoPath>
<protoPath>src/main/proto</protoPath>
<protoPath>src/main/protobuf</protoPath>
</protoPaths>
<!-- #all-proto-paths -->
</configuration>
</plugin>

<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven-dependency-plugin.version}</version>
<executions>
<execution>
<id>getClasspathFilenames</id>
<goals>
<!-- provides the jars of the classpath as properties inside of maven
so that we can refer to one of the jars in the exec plugin config below -->
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
Expand All @@ -87,6 +135,7 @@
</arguments>
</configuration>
</plugin>

</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@
package example.myapp.helloworld;

import akka.actor.ActorSystem;
import akka.http.javadsl.ConnectHttp;
import akka.grpc.javadsl.ServerReflection;
import akka.grpc.javadsl.ServiceHandler;

import akka.http.javadsl.Http;
import akka.http.javadsl.ServerBinding;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.japi.function.Function;
import akka.stream.SystemMaterializer;
import akka.stream.Materializer;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import example.myapp.helloworld.grpc.GreeterService;
import example.myapp.helloworld.grpc.GreeterServiceHandlerFactory;

import java.util.Arrays;
import java.util.concurrent.CompletionStage;

class GreeterServer {
Expand All @@ -40,10 +46,17 @@ public static CompletionStage<ServerBinding> run(ActorSystem sys) throws Excepti
// Instantiate implementation
GreeterService impl = new GreeterServiceImpl(mat);

// Create the reflection handler for multiple services
Function<HttpRequest, CompletionStage<HttpResponse>> reflectionPartial =
ServerReflection.create(Arrays.asList(GreeterService.description), sys);

Function<HttpRequest, CompletionStage<HttpResponse>> serviceHandlers =
ServiceHandler.concatOrNotFound(GreeterServiceHandlerFactory.create(impl, sys), reflectionPartial);

return Http
.get(sys)
.newServerAt("127.0.0.1", 8090)
.bind(GreeterServiceHandlerFactory.create(impl, sys));
.bind(serviceHandlers);
}
}
//#full-server
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
import akka.stream.Materializer;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;

import com.google.api.HttpBody;
import com.google.protobuf.BytesValue;
import com.google.protobuf.Timestamp;
import example.myapp.helloworld.grpc.*;

Expand All @@ -35,6 +36,16 @@ public CompletionStage<HelloReply> sayHello(HelloRequest in) {
return CompletableFuture.completedFuture(reply);
}

@Override
public CompletionStage<HttpBody> sayHelloHttp(HelloRequest in) {
System.out.println("sayHelloHttp to " + in.getName());
HttpBody reply = HttpBody.newBuilder().setData(
com.google.protobuf.ByteString.copyFrom("test".getBytes())
).build();

return CompletableFuture.completedFuture(reply);
}

@Override
public CompletionStage<HelloReply> itKeepsTalking(Source<HelloRequest, NotUsed> in) {
System.out.println("sayHello to in stream...");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import akka.stream.Materializer;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import com.google.api.HttpBody;
import com.google.protobuf.ByteString;
import example.myapp.helloworld.grpc.GreeterServicePowerApi;
import example.myapp.helloworld.grpc.HelloReply;
import example.myapp.helloworld.grpc.HelloRequest;
Expand All @@ -34,6 +36,16 @@ public CompletionStage<HelloReply> sayHello(HelloRequest in, Metadata metadata)
return CompletableFuture.completedFuture(reply);
}

@Override
public CompletionStage<HttpBody> sayHelloHttp(HelloRequest in, Metadata metadata) {
System.out.println("sayHelloHttp to " + in.getName());
HttpBody reply = HttpBody.newBuilder().setData(
com.google.protobuf.ByteString.copyFrom("test".getBytes())
).build();

return CompletableFuture.completedFuture(reply);
}

@Override
public CompletionStage<HelloReply> itKeepsTalking(Source<HelloRequest, NotUsed> in, Metadata metadata) {
System.out.println("sayHello to in stream...");
Expand Down
7 changes: 7 additions & 0 deletions plugin-tester-java/src/main/protobuf/helloworld.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ option java_outer_classname = "HelloWorldProto";

package helloworld;

//#import-external-proto-files
import "google/api/annotations.proto";
import "google/api/httpbody.proto";
//#import-external-proto-files

////////////////////////////////////// The greeting service definition.
service GreeterService {
//////////////////////
Expand All @@ -17,6 +22,8 @@ service GreeterService {
////////*****/////////
rpc SayHello (HelloRequest) returns (HelloReply) {}

rpc SayHelloHttp (HelloRequest) returns (google.api.HttpBody) {}

// Comment spanning
// on several lines
rpc ItKeepsTalking (stream HelloRequest) returns (HelloReply) {}
Expand Down
9 changes: 8 additions & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ object Dependencies {
// If changing this, remember to update protoc plugin version to align in
// maven-plugin/src/main/maven/plugin.xml and akka.grpc.sbt.AkkaGrpcPlugin
val googleProtobuf = "3.21.9" // checked synced by VersionSyncCheckPlugin
val googleApi = "2.15.0"

val scalaTest = "3.2.12"

Expand Down Expand Up @@ -82,6 +83,11 @@ object Dependencies {
object Protobuf {
val protobufJava = "com.google.protobuf" % "protobuf-java" % Versions.googleProtobuf
val googleCommonProtos = "com.google.protobuf" % "protobuf-java" % Versions.googleProtobuf % "protobuf"

}

object GrpcApi {
val googleApiProtos = "com.google.api.grpc" % "proto-google-common-protos" % Versions.googleApi % "protobuf"
}

object Plugins {
Expand Down Expand Up @@ -143,5 +149,6 @@ object Dependencies {
Compile.akkaHttpCors,
Test.scalaTest,
Test.scalaTestPlusJunit,
Protobuf.googleCommonProtos)
Protobuf.googleCommonProtos,
GrpcApi.googleApiProtos)
}

0 comments on commit 8e07c1a

Please sign in to comment.