gRPC Kotlin Coroutines, Protobuf DSL, Scripting for Protoc

Overview

Kroto+

gRPC Kotlin Coroutines, Protobuf DSL, Scripting for Protoc

Build Status GitHub license JCenter Maven Central Awesome Kotlin Badge Awesome gRPC Slack

Community Contributions are Welcomed

ℹ️ | Docs are being expanded and moved to Readme.io

Quick Start: gRPC Coroutines

Run the following command to get started with a preconfigured template project. (kotlin-coroutines-gRPC-template)

git clone https://github.com/marcoferrer/kotlin-coroutines-gRPC-template && \
cd kotlin-coroutines-gRPC-template && \
./gradlew run 

Getting Started

Code Generators


Proto Builder Generator (Message DSL)

Setup & Documentation

This generator creates lambda based builders for message types

     val starPlatinum = Stand {
        name = "Star Platinum"
        powerLevel = 500
        speed = 550
        attack {
            name = "ORA ORA ORA"
            damage = 100
            range = Attack.Range.CLOSE
        }
    }
    
    val attack = Attack {
        name = "ORA ORA ORA"
        damage = 100
        range = Attack.Range.CLOSE
    }
    
    // Copy extensions
    val newAttack = attack.copy { damage = 200 }            
    
    // orDefault() will return the messages default instance when null
    val nullableAttack: Attack? = null
    nullableAttack.orDefault()
    
    // Plus operator extensions 
    val mergedAttack = attack + Attack { name = "Sunlight Yellow Overdrive" }            

gRPC Coroutines Client & Server    codecov

This option requires the artifact kroto-plus-coroutines as a dependency.

Configuration Options

Client / Server Examples
Method Signature Option Support

  • Design
  • Client Stubs
    • Designed to work well with Structured Concurrency
    • Cancellation of the client CoroutineScope will propagate to the server.
    • Cancellations can now be propagated across usages of a specific stub instance.
    • Rpc methods are overloaded with inline builders for request types
    • The request parameter for rpc methods defaults to RequestType.defaultsInstance
// Creates new stub with a default coroutine context of `EmptyCoroutineContext`
val stub = GreeterCoroutineGrpc.newStub(channel)

// Suspends and creates new stub using the current coroutine context as the default. 
val stub = GreeterCoroutineGrpc.newStubWithContext(channel)

// An existing stub can replace its current coroutine context using either 
stub.withCoroutineContext()
stub.withCoroutineContext(coroutineContext)

// Stubs can accept message builder lambdas as an argument  
stub.sayHello { name = "John" }

// For more idiomatic usage in coroutines, stubs can be created
// with an explicit coroutine scope using the `newGrpcStub` scope extension function.
launch {
    // Using `newGrpcStub` makes it clear that the resulting stub will use the receiving 
    // coroutine scope to launch any concurrent work. (usually for manual flow control in streaming apis) 
    val stub = newGrpcStub(GreeterCoroutineGrpc.GreeterCoroutineStub, channel)
    
    val (requestChannel, responseChannel) = stub.sayHelloStreaming()
    ...
}

  • Service Base Impl
    • Rpc calls are wrapped within a scope initialized with the following context elements.
      • CoroutineName set to MethodDescriptor.fullMethodName
      • GrpcContextElement set to io.grpc.Context.current()
    • Base services implement ServiceScope and allow overriding the initial coroutineContext used for each rpc method invocation.
    • Each services initialContext defaults to EmptyCoroutineContext
    • A common case for overriding the initialContext is for setting up application specific ThreadContextElement or CoroutineDispatcher, such as MDCContext() or newFixedThreadPoolContext(...)

Cancellation Propagation

  • Client
    • Both normal and exceptional coroutine scope cancellation will cancel the underlying call stream. See ClientCall.cancel() in io.grpc.ClientCall.java for more details.
    • In the case of service implementations using coroutines, this client call stream cancellation will cancel the coroutine scope of the rpc method being invoked on the server.
  • Server
    • Exceptional cancellation of the coroutine scope for the rpc method will be mapped to an instance of StatusRuntimeException and returned to the client.
    • Normal cancellation of the coroutine scope for the rpc method will be mapped to an instance of StatusRuntimeException with a status of Status.CANCELLED, and returned to the client.
    • Cancellation signals from the corresponding client will cancel the coroutine scope of the rpc method being invoked.

Examples

Unary

Client: Unary calls will suspend until a response is received from the corresponding server. In the event of a cancellation or the server responds with an error the call will throw the appropriate StatusRuntimeException

val response = stub.sayHello { name = "John" }

Server: Unary rpc methods can respond to client requests by either returning the expected response type, or throwing an exception.

override suspend fun sayHello(request: HelloRequest): HelloReply {

    if (isValid(request.name))
        return HelloReply { message = "Hello there, ${request.name}!" } else
        throw Status.INVALID_ARGUMENT.asRuntimeException()
}

Client Streaming

Client: requestChannel.send() will suspend until the corresponding server signals it is ready by requesting a message. In the event of a cancellation or the server responds with an error, both requestChannel.send() and response.await(), will throw the appropriate StatusRuntimeException.

val (requestChannel, response) = stub.sayHelloClientStreaming()

launchProducerJob(requestChannel){
    repeat(5){
        send { name = "name #$it" }
    }
}

println("Client Streaming Response: ${response.await()}")

Server: Client streaming rpc methods can respond to client requests by either returning the expected response type, or throwing an exception. Calls to requestChannel.receive() will suspend and notify the corresponding client that the server is ready to accept a message.

override suspend fun sayHelloClientStreaming(
    requestChannel: ReceiveChannel<HelloRequest>
): HelloReply =  HelloReply {
    message = requestChannel.toList().joinToString()
}

Server Streaming

Client: responseChannel.receive() will suspend and notify the corresponding server that the client is ready to accept a message.

val responseChannel = stub.sayHelloServerStreaming { name = "John" }

responseChannel.consumeEach {
    println("Server Streaming Response: $it")
}

Server: Server streaming rpc methods can respond to client requests by submitting messages of the expected response type to the response channel. Completion of service method implementations will automatically close response channels in order to prevent abandoned rpcs.

Calls to responseChannel.send() will suspend until the corresponding client signals it is ready by requesting a message. Error responses can be returned to clients by either throwing an exception or invoking close on responseChannel with the desired exception.

For an example of how to implement long lived response streams please reference MultipleClientSubscriptionsExample.kt.

override suspend fun sayHelloServerStreaming(
    request: HelloRequest,
    responseChannel: SendChannel<HelloReply>
) {        
    for(char in request.name) {
        responseChannel.send {
            message = "Hello $char!"
        }
    }
}

Bi-Directional Streaming

Client: requestChannel.send() will suspend until the corresponding server signals it is ready by requesting a message. In the event of a cancellation or the server responds with an error, both requestChannel.send() and response.await(), will throw the appropriate StatusRuntimeException.

val (requestChannel, responseChannel) = stub.sayHelloStreaming()

launchProducerJob(requestChannel){
    repeat(5){
        send { name = "person #$it" }
    }
}

responseChannel.consumeEach {
    println("Bidi Response: $it")
}

Server: Bidi streaming rpc methods can respond to client requests by submitting messages of the expected response type to the response channel. Completion of service method implementations will automatically close response channels in order to prevent abandoned rpcs.

Calls to responseChannel.send() will suspend until the corresponding client signals it is ready by requesting a message. Error responses can be returned to clients by either throwing an exception or invoking close on responseChannel with the desired exception.

For an example of how to implement long lived response streams please reference MultipleClientSubscriptionsExample.kt.

override suspend fun sayHelloStreaming(
    requestChannel: ReceiveChannel<HelloRequest>,
    responseChannel: SendChannel<HelloReply>
) {
    requestChannel.mapTo(responseChannel){
    
        HelloReply {
            message = "Hello there, ${it.name}!"
        }
    }
}

gRPC Stub Extensions

Configuration Options

This modules generates convenience extensions that overload the request message argument for rpc methods with a builder lambda block and a default value. It also supports generating overloads based off (google.api.method_signature) method options. More info available here

               
    //Kroto+ Generated Extension
    val response = serviceStub.myRpcMethod {
         id = 100
         name = "some name"
    }

    //Original Java Fluent builders
    val response = serviceStub.myRpcMethod(ExampleServiceGrpc.MyRpcMethodRequest
        .newBuilder()
        .setId(100)
        .setName("some name")
        .build())                  

For unary rpc methods, the generator will create the following extensions

    //Future Stub with default argument
    fun ServiceBlockingStub.myRpcMethod(request: Request = Request.defaultInstance): ListenableFuture<Response>
    
    //Future Stub with builder lambda 
    inline fun ServiceFutureStub.myRpcMethod(block: Request.Builder.() -> Unit): ListenableFuture<Response>
        
    //Blocking Stub with default argument
    fun ServiceBlockingStub.myRpcMethod(request: Request = Request.defaultInstance): Response
    
    //Blocking Stub with builder lambda
    inline fun ServiceBlockingStub.myRpcMethod(block: Request.Builder.() -> Unit): Response 

Coroutine Support

In addition to request message arguments as builder lambda rpc overloads, coroutine overloads for rpc calls can also be generated. This provides the same functionality as the generated coroutine stubs. Usage is identical to the client examples outlined in Coroutine Client Examples.

  • This is accomplished by defining extension functions for async service stubs.
  • This option requires the artifact kroto-plus-coroutines as a dependency.
  • If using rpc interceptors or other code that relies on io.grpc.Context then you need to be sure to add a GrpcContextElement to your CoroutineContext when launching a coroutine. Child coroutines will inherit this ThreadContextElement and the dispatcher will ensure that your grpc context is present on the executing thread.
    Context.current().withValue(MY_KEY, myValue).attach()
    
    val myGrpcContext = Context.current()
    
    val job = launch( GrpcContextElement() ) { //Alternate usage:  myGrpcContext.asContextElement() 
       
        launch {
            assertEquals(myGrpcContext, Context.current())
        }
       
        GlobalScope.launch{
            assertNotEquals(myGrpcContext, Context.current())
        } 
    }

Mock Service Generator

Configuration Options

This generator creates mock implementations of proto service definitions. This is useful for orchestrating a set of expected responses, aiding in unit testing methods that rely on rpc calls. Full example for mocking services in unit tests. The code generated relies on the kroto-plus-test artifact as a dependency. It is a small library that provides utility methods used by the mock services.

  • If no responses are added to the response queue then the mock service will return the default instance of the response type.
  • Currently only unary methods are being mocked, with support for other method types on the way
@Test fun `Test Unary Response Queue`(){
    
    MockStandService.getStandByNameResponseQueue.apply {
       //Queue up a valid response message
       addMessage {
           name = "Star Platinum"
           powerLevel = 500
           speed = 550
           addAttacks {
               name = "ORA ORA ORA"
               damage = 100
               range = StandProto.Attack.Range.CLOSE
           }
       }   
          
       //Queue up an error
       addError(Status.INVALID_ARGUMENT)
   }
   
   val standStub = StandServiceGrpc.newBlockingStub(grpcServerRule.channel)
   
   standStub.getStandByName { name = "Star Platinum" }.let{ response ->
       assertEquals("Star Platinum",response.name)
       assertEquals(500,response.powerLevel)
       assertEquals(550,response.speed)
       response.attacksList.first().let{ attack ->
           assertEquals("ORA ORA ORA",attack.name)
           assertEquals(100,attack.damage)
           assertEquals(StandProto.Attack.Range.CLOSE,attack.range)
       }
   }
   
   try{
       standStub.getStandByName { name = "The World" }
       fail("Exception was expected with status code: ${Status.INVALID_ARGUMENT.code}")
   }catch (e: StatusRuntimeException){
       assertEquals(Status.INVALID_ARGUMENT.code, e.status.code)
   }
}

Extendable Messages Generator (Experimental)

Configuration Options

Generated code relies on the kroto-plus-message artifact. This generator adds tagging interfaces to the java classes produce by protoc. It also adds pseudo companion objects to provide a way to access proto message APIs in a non static manner. The following is a small example of how to write generic methods and extensions that resolve both message and builders type.

inline fun <reified M, B> M.copy( block: B.() -> Unit ): M
        where M : KpMessage<M, B>, B : KpBuilder<M> {
    return this.toBuilder.apply(block).build()
}

// Usage
myMessage.copy { ... }

inline fun <reified M, B> build( block: B.() -> Unit ): M
        where M : KpMessage<M, B>, B : KpBuilder<M> {

    return KpCompanion.Registry[M::class.java].build(block)
}

// Usage
build<MyMessage> { ... }

inline fun <M, B> KpCompanion<M, B>.build( block: B.() -> Unit ): M
        where B : KpBuilder<M>,M : KpMessage<M,B> {

    return newBuilder().apply(block).build()
}

// Usage
MyMessage.Companion.build { ... }

User Defined Generator Scripts

Users can define kotlin scripts that they would like to run during code generation. For type completion, scripts can be couple with a small gradle build script, although this is completely optional. Samples are available in the kp-script directory of the example project.

There are two categories of scripts available.

  • Insertion Scripts

    • Configuration Options
    • Using the insertion api from the java protoc plugin, users can add code at specific points in generated java classes.
    • This is useful for adding code to allow more idiomatic use of generated java classes from Kotlin.
    • The entire ExtendableMessages generator can be implemented using an insertion script, an example can be in the example script extendableMessages.kts.
    • Additional information regarding the insertion api can be found in the official docs
  • Generator Scripts

    • Configuration Options
    • These scripts implement the Generator interface used by all internal kroto+ code generators.
    • Generators rely on the GeneratorContext, which is available via the property context.
    • The context is used for iterating over files, messages, and services submitted by protoc.
    • Example usage can be found in the kp-script directory of the example project, as well as inside the generators package of the protoc-gen-kroto-plus artifact.

Community Scripts

Community contributions for scripts are welcomed and more information regarding guidelines will be published soon.


Method Signature Options Support

Usage of (google.api.method_signature) method option is now supported. This allows users to customize the method parameters outputted in generated clients as well as stub extensions. To config your rpc methods, first add the google common proto dependency to your build

dependencies{
    compileOnly "com.google.api.grpc:proto-google-common-protos:1.16.0"
}

Then add the following import to your proto definition.

import "google/api/client.proto";

Now the method option should be available for usage in your method definition

// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply){
    option (google.api.method_signature) = "name";
};

This will result in the following method signature being outputed from gRPC and stub extension code generators.

 fun GreeterStub.sayHello(name: String): HelloReply{
    val request = HelloRequest.newBuilder()
        .setName(name)
        .build()
    return sayHello(request)
 }

Getting Started With Gradle

Repositories

  • Available on jcenter() or mavenCentral()
  • SNAPSHOT
repositories {
    maven { url 'https://oss.jfrog.org/artifactory/oss-snapshot-local' }
}
  • Bintray
// Useful when syncronization to jcenter or maven central are taking longer than expected
repositories {
    maven { url 'https://dl.bintray.com/marcoferrer/kroto-plus/' }
}
Configuring Protobuf Gradle Plugin
plugins{
    id 'com.google.protobuf' version '0.8.6'
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:$protobufVersion"}

    plugins {
        kroto {
            artifact = "com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:$krotoPlusVersion"
        }
    }

    generateProtoTasks {
        def krotoConfig = file("krotoPlusConfig.asciipb") // Or .json

        all().each{ task ->
            // Adding the config file to the task inputs lets UP-TO-DATE checks
            // include changes to configuration
            task.inputs.files krotoConfig

            task.plugins {
                kroto {
                    outputSubDir = "java"
                    option "ConfigPath=$krotoConfig"
                }
            }
        }
    }
}

Getting Started With Maven

Repositories

  • Available on jcenter or mavenCentral
  • SNAPSHOT
<repository>
    <id>oss-snapshot</id>
    <name>OSS Snapshot Repository</name>
    <url>https://oss.jfrog.org/artifactory/oss-snapshot-local</url>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>
  • Bintray
<!-- Useful when syncronization to jcenter or maven central are taking longer than expected-->
<repository>
    <id>kroto-plus-bintray</id>
    <name>Kroto Plus Bintray Repository</name>
    <url>https://dl.bintray.com/marcoferrer/kroto-plus/</url>
</repository>
Configuring Protobuf Maven Plugin
<plugin>
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <configuration>
        <protocArtifact>com.google.protobuf:protoc:3.6.1:exe:${os.detected.classifier}</protocArtifact>
    </configuration>
    <executions>
        <execution>
            <goals><goal>compile</goal></goals>
        </execution>
        <execution>
            <id>grpc-java</id>
            <goals><goal>compile-custom</goal></goals>
            <configuration>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.17.1:exe:${os.detected.classifier}</pluginArtifact>
            </configuration>
        </execution>
        <execution>
            <id>kroto-plus</id>
            <goals>
                <goal>compile-custom</goal>
            </goals>
            <configuration>
                <pluginId>kroto-plus</pluginId>
                <pluginArtifact>com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:${krotoPlusVersion}:exe:${os.detected.classifier}</pluginArtifact>
                <pluginParameter>ConfigPath=${project.basedir}/krotoPlusConfig.asciipb</pluginParameter>
            </configuration>
        </execution>
    </executions>
</plugin>

Add generated sources to Kotlin plugin

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>
    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
            </goals>
            <configuration>
                <sourceDirs>
                    <sourceDir>${project.basedir}/target/generated-sources/protobuf/kroto-plus</sourceDir>
                </sourceDirs>
            </configuration>
        </execution>
    </executions>
</plugin>

Configuring Generators

Credit

This project relies on Kotlin Poet for building Kotlin sources. A big thanks to all of its contributors.

Comments
  • Issues running on Windows

    Issues running on Windows

    Ok, I don't use Windows myself (ugh), but some of my coworkers do. There are issues running this on Windows:

    1. The compiler is run with an invalid path that contains a leading "/". Here is the debug output from Gradle:
    11:48:22.846 [INFO] [org.gradle.process.internal.DefaultExecHandle] Starting process 'command 'C:\jdk1.8.0_191\bin\java.exe''. Working directory: H:\source\myproject\lib-proto Command: C:\jdk1.8.0_191\bin\java.exe -Dfile.encoding=windows-1252 -Duser.country=US -Duser.language=en -Duser.variant -jar /C:/Users/Me/.gradle/caches/modules-2/files-2.1/com.github.marcoferrer.krotoplus/kroto-plus-compiler/0.1.3/620b2a278b4d4beed80320bb4800339967f850d7/kroto-plus-compiler-0.1.3.jar H:\source\myproject\lib-proto/src/main/proto H:\source\myproject\lib-proto\build/extracted-include-protos/main -default-out H:\source\myproject\lib-proto\build\generated\source\proto\main\kotlin -writers 3 -StubOverloads -o|H:\source\myproject\lib-proto\build\generated\source\proto\main\kotlin|-coroutines -MockServices -o|H:\source\myproject\lib-proto\build\generated\source\proto\main\kotlin -ProtoTypeBuilder
    ...
    11:48:22.874 [ERROR] [system.err] Error: Unable to access jarfile /C:/Users/Me/.gradle/caches/modules-2/files-2.1/com.github.marcoferrer.krotoplus/kroto-plus-compiler/0.1.3/620b2a278b4d4beed80320bb4800339967f850d7/kroto-plus-compiler-0.1.3.jar
    

    Note the leading slash on the -jar argument.

    1. I don't know if this is an issue in this plugin or in the upstream protobuf gradle plugin, but when adding the kroto code gen plugin to the protobuf plugin, the build fails with the error:
    Execution failed for task ':generateProto'.
    > protoc: stdout: . stderr: --kroto_out: protoc-gen-kroto: %1 is not a valid Win32 application.
    

    Looking at the --debug logs, it looks like a --plugin parameter is addd to the call to protoc, with the following value:

     --plugin=protoc-gen-kroto=C:\Users\Me\.gradle\caches\modules-2\files-2.1\com.github.marcoferrer.krotoplus\protoc-gen-kroto-plus\0.1.3\3c41d071d04c8558822447643894490e33a857b7\protoc-gen-kroto-plus-0.1.3-jvm8.jar
    

    but protoc is assuming the jar file is an executable it can run. I have tried associating ".jar" files with "java" (and this seems to have worked), but for some reason when running via protoc, the same "not a valid Win32 application" error still happens.

    bug plugin 
    opened by rocketraman 26
  • gRPC Client / Server Code gen and api

    gRPC Client / Server Code gen and api

    This issue is for tracking any discussions related to https://github.com/marcoferrer/kroto-plus/pull/16 PR.

    The PR introduces new apis with subtle differences to those introduced in the stub ext generator. Please reference the client / server example included. It contains a sample of the expected output for discussion.

    Here are some key points and examples from the PR

    1. All around better integration with Structured Concurrency
    2. Backpressure Support has been implemented.
    3. Generation of Client Stubs which implement the CoroutineScope interface
      • Allows client stubs to work well with Structured Concurrency
      • Cancellations can now be propagated across usages of a specific stub instance.
    4. Generation of abstract Service Base Impl's
      • Allows services to still support common grpc-java patterns, while still fully embracing coroutine idioms and features.
    5. Convenience exts for sending requests and responses in both client, server code.
      • Full support for familiar Kroto-plus convenience lambda exts on SendChannel.send { } and CompletableDeferred.complete { }
    6. Support for annotation processor assistance via RpcMethod annotation from grpc-java. RpcMethod.java
    suspend fun performUnaryCall(stub: GreeterCoroutineGrpc.GreeterCoroutineStub){
    
        val unaryResponse = stub.sayHello { name = "John" }
    
        println("Unary Response: ${unaryResponse.message}")
    }
    
    suspend fun performServerStreamingCall(stub: GreeterCoroutineGrpc.GreeterCoroutineStub){
    
        val responseChannel = stub.sayHelloServerStreaming { name = "John" }
    
        responseChannel.consumeEach {
            println("Server Streaming Response: ${it.message}")
        }
    }
    
    suspend fun CoroutineScope.performClientStreamingCall(stub: GreeterCoroutineGrpc.GreeterCoroutineStub){
    
        // Client Streaming RPC
        val (requestChannel, response) = stub.sayHelloClientStreaming()
    
        launch {
            repeat(5){
                requestChannel.send { name = "person #$it" }
            }
            requestChannel.close()
        }
    
        println("Client Streaming Response: ${response.await().toString().trim()}")
    }
    
    suspend fun CoroutineScope.performBidiCall(stub: GreeterCoroutineGrpc.GreeterCoroutineStub){
    
        val (requestChannel, responseChannel) = stub.sayHelloStreaming()
    
        launch {
            repeat(5){
                requestChannel.send { name = "person #$it" }
            }
            requestChannel.close()
        }
    
        launch {
            responseChannel.consumeEach {
                println("Bidi Response: ${it.message}")
            }
        }
    }
    
    enhancement grpc-coroutines design-discussion 
    opened by marcoferrer 13
  • Client cancellations aren't propagated to the server

    Client cancellations aren't propagated to the server

    When I close a streaming client request with an exception I would expect the corresponding server receive channel to throw an exception upon calling receive (or iterating the channel) but it doesn't seem to do so - am I doing something wrong?

    Client:

    call.requestChannel.close(Exception("Testing Exception"))
    

    Server:

    try {
        for (request in requestChannel) {
            logger.info("Iterating")
        }
        logger.info("Closed normally")
    } catch (e: Exception){
        logger.error("Caught exception", e)
    }
    
    opened by jebbench 10
  • Bugfix for race condition in outbound flow control

    Bugfix for race condition in outbound flow control

    A bidi stream call with a high throughput of messages can hang up.

    This occurred reliably in a throughput test where 100000 messages of 1kB were transmitted over an in-process channel to a server and echoed back to the caller. With kroto plus, the message transfer gets simply stuck and never completes.

    The root cause is a race condition in FlowControl.kt applyOutboundFlowControl(): Reading streamObserver.isReady and setting isOutboundJobRunning are not atomic, thus the outBoundJob can terminate and misses to be relaunched by an onReady-event.

    Here I propose a minimally invasive fix though I also considered refactoring the coroutine to be long running - suspend while not ready and resume on ready. Please consider addressing this because the hangup is a deal breaker for my use case and most likely others as well.

    opened by blachris 8
  • compile-custom fails on Windows 10

    compile-custom fails on Windows 10

    It works on Ubuntu, MacOS but fails on Windows 10. Using below execution configutration (with kroto-plus.version = 0.5.0)

    <execution>
        <id>grpc-coroutines</id>
        <goals>
            <goal>compile-custom</goal>
        </goals>
        <configuration>
            <pluginId>kroto-plus</pluginId>
            <pluginArtifact>com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:${kroto-plus.version}:jar:jvm8</pluginArtifact>
            <pluginParameter>ConfigPath=./krotoPlusConfig.asciipb</pluginParameter>
        </configuration>
    </execution>
    

    Windows Info:

    [INFO] ------------------------------------------------------------------------
    [INFO] Detecting the operating system and CPU architecture
    [INFO] ------------------------------------------------------------------------
    [INFO] os.detected.name: windows
    [INFO] os.detected.arch: x86_64
    [INFO] os.detected.version: 10.0
    [INFO] os.detected.version.major: 10
    [INFO] os.detected.version.minor: 0
    [INFO] os.detected.classifier: windows-x86_64
    

    Failed Error Logs:

    [INFO] --- protobuf-maven-plugin:0.6.1:compile-custom (grpc-coroutines) @ blueprint-proto ---
    [INFO] Compiling 4 proto file(s) to C:\git\modules\target\generated-sources\protobuf\kroto-plus
    [ERROR] PROTOC FAILED: --kroto-plus_out: protoc-gen-kroto-plus: This version of %1 is not compatible with the version of Windows you're running. Check your computer's system information and then contact the software publisher.
    
    [ERROR] C:\git\modules\components\proto-definition\proto\CommandExecutor.proto [0:0]: --kroto-plus_out: protoc-gen-kroto-plus: This version of %1 is not compatible with the version of Windows you're running. Check your computer's system information and then contact the software publisher.
    
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE
    [INFO] ------------------------------------------------------------------------
    
    opened by kasingal 7
  • Support for `google/protobuf/wrappers.proto`

    Support for `google/protobuf/wrappers.proto`

    The Google protobuf standard wrappers are useful with proto3, to handle nullable fields. It would be nice if kroto-plus added explicit support for these wrappers e.g. generate builders for them if they are referenced in the proto files.

    opened by rocketraman 6
  • Extending CompilerConfig: InvalidProtocolBufferException

    Extending CompilerConfig: InvalidProtocolBufferException "cannot find field"

    I try to extend the CompilerConfig with the ProtobufMessages as provided in a PR here (https://github.com/marcoferrer/kroto-plus/pull/13/files).

    I migrated all the code, but I can't seem to solve this error when I run generateProto on my sample project app:

    Caused by: com.google.protobuf.InvalidProtocolBufferException: 
       Cannot find field: mpProtobufMessages in message krotoplus.compiler.CompilerConfig
    

    I do have this field in my config.proto:

    // Configuration entries for the 'Multi-Platform Protobuf Messages' code generator.
    repeated MpProtobufMessagesGenOptions mp_protobuf_messages = 27;
    

    Also this field is present in CompilerConfig.java:

    public static final int MP_PROTOBUF_MESSAGES_FIELD_NUMBER = 27;
    private java.util.List<com.github.marcoferrer.krotoplus.config.MpProtobufMessagesGenOptions> mpProtobufMessages_;
    

    And it is also in the CompilerConfig initialization

      case 218: {
        if (!((mutable_bitField0_ & 0x00000080) == 0x00000080)) {
          mpProtobufMessages_ = new java.util.ArrayList<com.github.marcoferrer.krotoplus.config.MpProtobufMessagesGenOptions>();
          mutable_bitField0_ |= 0x00000080;
        }
        mpProtobufMessages_.add(
            input.readMessage(com.github.marcoferrer.krotoplus.config.MpProtobufMessagesGenOptions.parser(), extensionRegistry));
        break;
      }
    

    In krotoPlusConfig.yaml:

    mpProtobufMessages:
      - filter:
          excludePath:
            - google/*
    

    Complete error message from the sample project:

    Execution failed for task ':generateProto'.
    > protoc: stdout: . stderr: Exception in thread "main" java.lang.reflect.InvocationTargetException
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
      	at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
      	at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
      	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
      Caused by: com.google.protobuf.InvalidProtocolBufferException: Cannot find field: mpProtobufMessages in message krotoplus.compiler.CompilerConfig
      	at com.google.protobuf.util.JsonFormat$ParserImpl.mergeMessage(JsonFormat.java:1348)
      	at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1308)
      	at com.google.protobuf.util.JsonFormat$ParserImpl.merge(JsonFormat.java:1190)
      	at com.google.protobuf.util.JsonFormat$Parser.merge(JsonFormat.java:370)
      	at com.github.marcoferrer.krotoplus.generators.GeneratorContextKt.getCompilerConfig(GeneratorContext.kt:61)
      	at com.github.marcoferrer.krotoplus.generators.GeneratorContext.<init>(GeneratorContext.kt:36)
      	at com.github.marcoferrer.krotoplus.generators.GeneratorKt$contextInstance$2.invoke(Generator.kt:75)
      	at com.github.marcoferrer.krotoplus.generators.GeneratorKt$contextInstance$2.invoke(Generator.kt)
      	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
      	at com.github.marcoferrer.krotoplus.generators.GeneratorKt.getContextInstance(Generator.kt)
      	at com.github.marcoferrer.krotoplus.generators.GeneratorKt.initializeContext(Generator.kt:61)
      	at com.github.marcoferrer.krotoplus.generators.GeneratorKt.initializeContext$default(Generator.kt:59)
      	at com.github.marcoferrer.krotoplus.KrotoPlusProtoCMain.main(KrotoPlusProtoCMain.kt:32)
      	... 8 more
      --krotoPlus_out: protoc-gen-krotoPlus: Plugin failed with status code 1.
    

    I am unsure what else I could be doing wrong?

    Hopefully someone can help me out, been stuck on this wayyy too long.

    opened by RdeWilde 5
  • Cannot build provided template project

    Cannot build provided template project

    Hi,

    The instructions given here: https://github.com/marcoferrer/kroto-plus/tree/master/example-grpc-client-server#quick-start:

    git clone https://github.com/marcoferrer/kotlin-coroutines-gRPC-template && \
    cd kotlin-coroutines-gRPC-template && \
    ./gradlew run
    

    produce the following output:

    Cloning into 'kotlin-coroutines-gRPC-template'...
    remote: Enumerating objects: 57, done.
    remote: Counting objects: 100% (57/57), done.
    remote: Compressing objects: 100% (37/37), done.
    remote: Total 57 (delta 18), reused 44 (delta 9), pack-reused 0
    Unpacking objects: 100% (57/57), done.
    e: ~/Workspace/kotlin-coroutines-gRPC-template/api/build/generated/source/proto/main/coroutines/io/grpc/examples/helloworld/GreeterCoroutineGrpc.kt: (25, 25): Unresolved reference: Generated
    e: ~/Workspace/kotlin-coroutines-gRPC-template/api/build/generated/source/proto/main/coroutines/io/grpc/examples/helloworld/GreeterCoroutineGrpc.kt: (37, 2): Unresolved reference: Generated
    > Task :api:compileKotlin FAILED
    
    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':api:compileKotlin'.
    > Compilation error. See log for more details
    
    * Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
    
    * Get more help at https://help.gradle.org
    
    BUILD FAILED in 1s
    6 actionable tasks: 6 executed
    
    opened by jcornaz 5
  • pb-and-k support

    pb-and-k support

    It would be nice to be able to fully utilise kotlin features, also for the protobuf creation itself. What do you think of supporting https://github.com/cretz/pb-and-k - would this be something feasible/useful?

    opened by Globegitter 4
  • Naming collision with nested fields

    Naming collision with nested fields

    I am working on a project that includes the Google pre-built .proto files as explicit files in its proto sources. Kroto-plus works fine in compiling code for most of them (as well as all of our own protos), but comiplation fails when the file .../google/rpc/error_details.proto is included for the protoBuilders generator.

    Looking at the generated code, the issue is that objects with the same name as the proto classes are generated which shadow the (imported) classes, so other (generated) code (in the same file) that refers to them by their short names finds the objects instead and breaks.

    We've put in a temporary fix of excluding that file, but it would be much more convenient for a proper fix to be available.

    opened by keyserbrian1 4
  • Use kroto-plus as protoc plugin

    Use kroto-plus as protoc plugin

    Hey !

    I would like to know if this repo can be used as plugin for protoc? In fact I would like to generate the sources manually and using protoc, is that possible?

    Thanks

    opened by Emixam23 4
  • Apple Silicon support  is missing

    Apple Silicon support is missing

    compiling on a Mac OS running the new Apple M1 computers fails due to: Could not find protoc-gen-kroto-plus-0.6.1-osx-aarch_64.exe (com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:0.6.1).

    opened by ramib-doordash 2
  • Kotlin code generated for deprecated protocol buffer elements should be marked as deprecated

    Kotlin code generated for deprecated protocol buffer elements should be marked as deprecated

    It is possible to mark most protocol buffer elements as deprecated using the deprected option, for example, a message can be deprecated like this:

    message Request {
      option deprecated = true;
      string requestContent = 1;
    }
    

    When Java code is generated for deprecated elements, they are annotated with @java.lang.Deprecated. I believe that the @kotlin.Deprecated annotation should be applied to deprecated elements in generated Kotlin code.

    opened by wfhartford 0
  • Cannot configure krotoPlus protoc task via Kotlin gradle script, fallsback to asciipb file

    Cannot configure krotoPlus protoc task via Kotlin gradle script, fallsback to asciipb file

    Kroto Plus version: 0.6.1 com.google.protobuf version: 0.8.15


    When trying to adapt the example krotoPlusConfig.gradle to Kotlin Gradle script, I havent been successful. My only success has been with the config defined in a json file (and presumably will also work for asciipb etc).

    For the first code snippet, I get the following exception:

    Execution failed for task ':semanticdb-kotlin:generateProto'.
    > protoc: stdout: . stderr: Exception in thread "main" java.lang.reflect.InvocationTargetException
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.lang.reflect.Method.invoke(Method.java:498)
            at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
            at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
            at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
            at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
      Caused by: java.lang.IllegalStateException: Config file does not exist. '/home/noah/Sourcegraph/lsif-kotlin/semanticdb-kotlin/build/kroto/config/main.asciipb'
            at com.github.marcoferrer.krotoplus.generators.GeneratorContextKt.getConfigFile(GeneratorContext.kt:74)
            at com.github.marcoferrer.krotoplus.generators.GeneratorContextKt.getCompilerConfig(GeneratorContext.kt:53)
            at com.github.marcoferrer.krotoplus.generators.GeneratorContext.<init>(GeneratorContext.kt:36)
            at com.github.marcoferrer.krotoplus.generators.GeneratorKt.initializeContext(Generator.kt:86)
            at com.github.marcoferrer.krotoplus.generators.GeneratorKt.initializeContext$default(Generator.kt:75)
            at com.github.marcoferrer.krotoplus.KrotoPlusProtoCMain.main(KrotoPlusProtoCMain.kt:32)
            ... 8 more
      --kroto_out: protoc-gen-kroto: Plugin failed with status code 1.
    
    // ...
    
    krotoPlus {
        config {
            register("main") {
                builder.protoBuilders {
                    useDslMarkers = true
                    unwrapBuilders = true
                }
            }
        }
    }
    
    protobuf {
        protoc { artifact = "com.google.protobuf:protoc:3.15.7" }
    
        generatedFilesBaseDir = sourceSets.main.get().kotlin.sourceDirectories.asPath.split(":")[0].removeSuffix("main/kotlin")
    
        plugins {
            id("kroto") { artifact = "com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:0.6.1" }
        }
    
        generateProtoTasks {
            val krotoConfig = file("${projectDir}/krotoconfig.json")
            all().forEach { task ->
                task.inputs.files(krotoConfig)
    
                task.plugins {
                    id("kroto") {
                        outputSubDir = "java"
                        option(krotoPlus.config["main"].asOption())
    					//option("ConfigPath=${krotoConfig}")
                    }
                }
            }
        }
    }
    

    With the following diff, I get com.github.marcoferrer.krotoplus.config.CompilerConfig$Builder cannot be cast to com.github.marcoferrer.krotoplus.gradle.compiler.CompilerConfigWrapper:

    34c34
    <         register("main") {
    ---
    >         id("main") {
    
    opened by Strum355 0
  • Library support

    Library support

    Hey, i wonder if this library is still under support? I ask because there a little changes to protobuf, like "optional" field which is not supported by yours code generator.

    opened by outofdate 0
  • Proto Builder Generator uses wrong java builder function

    Proto Builder Generator uses wrong java builder function

    Hey folks, I'm having an issue with the generated builders. I'm using the following versions in my project

    • com.google.protobuf:3.10.0
    • com.google.protobuf:protobuf-java-util:3.10.0
    • com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:0.6.1

    With the following demo proto:

    syntax = "proto3";
    
    package somePackage;
    
    option java_multiple_files = true;
    option java_outer_classname = "ProtoMyTestOuter";
    option java_package = "com.example";
    
    message ProtoMyAwesomeMessage {
        repeated ProtoOtherMessage some_b2b_books = 1;
    }
    
    message ProtoOtherMessage {
        string demo = 1;
    }
    

    When building the java class from it the builder for the field looks like the following: public Builder addSomeB2BBooks(com.example.ProtoOtherMessage value) { But the generated kroto+ builders try to call: this.addSomeB2bBooks(ProtoOtherMessage.newBuilder().apply(block).build()). So BB vs bB. Full generated kroto+ code:

    inline fun ProtoMyAwesomeMessage.Builder.addSomeB2bBooks(block: ProtoOtherMessage.Builder.() ->
            Unit): ProtoMyAwesomeMessage.Builder =
            this.addSomeB2bBooks(ProtoOtherMessage.newBuilder().apply(block).build())
    

    So this references itself which makes my builds break.

    Is this a bug or do I need to adjust the generated casing on my side?

    Just in case someone asks my kroto+ settings look like the following:

    ---
    protoBuilders:
      - unwrapBuilders: false
        useDslMarkers: true
    
    grpcCoroutines: [{}]
    
    opened by hpuac 2
Releases(v0.6.1)
Owner
Marco Ferrer
Marco Ferrer
This repository contains RabbitMQ Protobuf starters with its usage samples for spring-rabbit and spring-cloud-starter-stream-rabbit modules

This repository contains RabbitMQ Protobuf starters with its usage samples for spring-rabbit and spring-cloud-starter-stream-rabbit modules

Maksim Kostromin 2 Nov 29, 2021
Kotlin scripting environment based on TabooLib

Artifex Artifex 提供了完善的 Kotlin Script (.kts) 运行环境,且支持 TabooLib 全特性。 val compiledScript = Artifex.api().scriptCompiler().compile { // 传入源文件 it.

TABOO-PROJECT 19 Sep 11, 2022
Modular Android architecture which showcase Kotlin, MVVM, Navigation, Hilt, Coroutines, Jetpack compose, Retrofit, Unit test and Kotlin Gradle DSL.

SampleCompose Modular Android architecture which showcase Kotlin, MVVM, Navigation, Hilt, Coroutines, Jetpack compose, Retrofit, Unit test and Kotlin

Mohammadali Rezaei 7 Nov 28, 2022
Kotlin microservices with REST, and gRPC using BFF pattern. This repository contains backend services. Everything is dockerized and ready to "Go" actually "Kotlin" :-)

Microservices Kotlin gRPC Deployed in EC2, Check it out! This repo contains microservices written in Kotlin with BFF pattern for performing CRUD opera

Oguzhan 18 Apr 21, 2022
Kotlin & Java class for gRPC client

jvm-minter-grpc-class Kotlin & Java class for gRPC client Use @TODO Разное Добавление git subtree add --prefix node-grpc-gateway https://github.com/Mi

Aleksey Kukhnovets 2 May 26, 2022
Aplicação Micronaut GRPC utilizando Kotlin e arquitetura Hexagonal

micronaut-grpc-demo Aplicação Micronaut GRPC utilizando Kotlin e arquitetura Hexagonal #Baixando e configurando um container Postgres: Postgres: docke

Paulo César de Souza 0 Nov 15, 2021
grpc stream fullstack example(spring+kotlin / next.js + typescript)

grpc-stream-fullstack chat application build with grpc named qhat Prerequisites server sync .proto files on src/main/proto/grpc/qhat/ $ ./gradlew sync

Dave Kwon 5 Nov 14, 2022
GRPC client for the Provenance Blockchain

GRPC client for the Provenance Blockchain Tip: Refer to the Cosmos Proto Docs and Provenance Blockchain Proto Docs for client interface definitions. M

Provenance Blockchain, Inc. 5 Dec 6, 2022
Playground server-client Android app using gRPC and protocol buffers

gRPC Playground The goal of this app is to connect to a server, exchange information using the gRPC protocol and lastly retrieve some mocked credentia

Thanos Psaridis 8 Sep 14, 2022
Nice and simple DSL for Espresso Compose UI testing in Kotlin

Kakao Compose Nice and simple DSL for Espresso Compose in Kotlin Benefits Readability Reusability Extensible DSL How to use it Create Screen Create yo

null 74 Dec 26, 2022
An Android template you can use to build your project with gradle kotlin dsl

Android Gradle KTS An Android template you can use to build your project with gradle kotlin dsl Build.gradle.kts You can use your project's build.grad

Deep 17 Sep 12, 2022
An idiomatic Kotlin DSL for creating regular expressions.

Ketex An idiomatic Kotlin DSL for creating regular expressions. For documentation and usage instructions, please take a look at the docs. Here's the m

TheOnlyTails 24 Nov 8, 2022
Gradle plugin which allows to use typed DSL for generating kubernetes/openshift YAML files

gr8s Gradle plugin which allows using typed DSL for generating kubernetes/openshift YAML files. Based on kuberig Usage import io.github.guai.gr8s.Gene

null 0 Jan 3, 2022
DSL for JPA Criteria API without generated metamodel and reflection.

Kotlin JDSL Kotlin JDSL is DSL for JPA Criteria API without generated metamodel and reflection. It helps you write a JPA query like writing an SQL sta

LINE 379 Jan 7, 2023
Saga pattern implementation in Kotlin build in top of Kotlin's Coroutines.

Module Saga Website can be found here Add in build.gradle.kts repositories { mavenCentral() } dependencies { implementation("io.github.nomisr

Simon Vergauwen 50 Dec 30, 2022
FlowExt is a Kotlin Multiplatform library, that provides many operators and extensions to Kotlin Coroutines Flow

FlowExt | Kotlinx Coroutines Flow Extensions | Kotlinx Coroutines Flow Extensions. Extensions to the Kotlin Flow library | kotlin-flow-extensions | Coroutines Flow Extensions | Kotlin Flow extensions | kotlin flow extensions | Flow extensions

Petrus Nguyễn Thái Học 151 Jan 1, 2023
Clean Android multi-module offline-first scalable app in 2022. Including Jetpack Compose, MVI, Kotlin coroutines/Flow, Kotlin serialization, Hilt and Room.

Android Kotlin starter project - 2022 edition Android starter project, described precisely in this article. Purpose To show good practices using Kotli

Krzysztof Dąbrowski 176 Jan 3, 2023
Shreyas Patil 2.2k Jan 4, 2023
🎓 Learning Kotlin Coroutines for Android by example. 🚀 Sample implementations for real-world Android use cases. 🛠 Unit tests included!

Kotlin Coroutines - Use Cases on Android ?? Learning Kotlin Coroutines for Android by example. ?? Sample implementations for real-world Android use ca

Lukas Lechner 2.1k Jan 3, 2023