• Stars
    star
    139
  • Rank 262,021 (Top 6 %)
  • Language
    Kotlin
  • License
    MIT License
  • Created over 6 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Kotlin Code Generator and Runtime for Protocol Buffers

This product is not actively maintained. See streem/pbandk for a more actively maintained fork.

PBAndK

PBAndK is a Kotlin code generator and runtime for Protocol Buffers. It is built to work across multiple Kotlin platforms.

Features

  • Clean data class generation
  • Works for JVM and JS
  • Support for proto2 and proto3 syntaxes
  • Oneof's are properly handled as sealed classes
  • JVM platform leverages Protobuf's Java library for best performance
  • JS platform leverages protobuf.js for best performance
  • Support for custom service/gRPC code generator

Not Yet Implemented

  • Kotlin Native runtime support
  • Protobuf code generator in Kotlin Native for easier importing
  • Specialized support for well known types instead of just referencing them
  • Code comments on generated code
  • JSON support

Read below for more information and see the examples.

Beta

This project is currently in beta yet has the features needed to solve the author's original goals. The "Not Yet Implemented" features above will be implemented only if this project garners reasonable interest. If the features are implemented, it is possible they may be done in a backwards incompatible way.

NOTE: This product is not actively maintained. See https://github.com/streem/pbandk for a more actively maintained fork.

Generated Code

For the following addressbook.proto file:

syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";

message Person {
    string name = 1;
    int32 id = 2;
    string email = 3;

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }

    repeated PhoneNumber phones = 4;

    google.protobuf.Timestamp last_updated = 5;
}

message AddressBook {
    repeated Person people = 1;
}

The following file will be generated at tutorial/addressbook.kt:

package tutorial

data class Person(
    val name: String = "",
    val id: Int = 0,
    val email: String = "",
    val phones: List<tutorial.Person.PhoneNumber> = emptyList(),
    val lastUpdated: pbandk.wkt.Timestamp? = null,
    val unknownFields: Map<Int, pbandk.UnknownField> = emptyMap()
) : pbandk.Message<Person> {
    override operator fun plus(other: Person?) = protoMergeImpl(other)
    override val protoSize by lazy { protoSizeImpl() }
    override fun protoMarshal(m: pbandk.Marshaller) = protoMarshalImpl(m)
    companion object : pbandk.Message.Companion<Person> {
        override fun protoUnmarshal(u: pbandk.Unmarshaller) = Person.protoUnmarshalImpl(u)
    }

    data class PhoneType(override val value: Int) : pbandk.Message.Enum {
        companion object : pbandk.Message.Enum.Companion<PhoneType> {
            val MOBILE = PhoneType(0)
            val HOME = PhoneType(1)
            val WORK = PhoneType(2)

            override fun fromValue(value: Int) = when (value) {
                0 -> MOBILE
                1 -> HOME
                2 -> WORK
                else -> PhoneType(value)
            }
        }
    }

    data class PhoneNumber(
        val number: String = "",
        val type: tutorial.Person.PhoneType = tutorial.Person.PhoneType.fromValue(0),
        val unknownFields: Map<Int, pbandk.UnknownField> = emptyMap()
    ) : pbandk.Message<PhoneNumber> {
        override operator fun plus(other: PhoneNumber?) = protoMergeImpl(other)
        override val protoSize by lazy { protoSizeImpl() }
        override fun protoMarshal(m: pbandk.Marshaller) = protoMarshalImpl(m)
        companion object : pbandk.Message.Companion<PhoneNumber> {
            override fun protoUnmarshal(u: pbandk.Unmarshaller) = PhoneNumber.protoUnmarshalImpl(u)
        }
    }
}

data class AddressBook(
    val people: List<tutorial.Person> = emptyList(),
    val unknownFields: Map<Int, pbandk.UnknownField> = emptyMap()
) : pbandk.Message<AddressBook> {
    override operator fun plus(other: AddressBook?) = protoMergeImpl(other)
    override val protoSize by lazy { protoSizeImpl() }
    override fun protoMarshal(m: pbandk.Marshaller) = protoMarshalImpl(m)
    companion object : pbandk.Message.Companion<AddressBook> {
        override fun protoUnmarshal(u: pbandk.Unmarshaller) = AddressBook.protoUnmarshalImpl(u)
    }
}

// Omitted multiple supporting private extension methods

To see a full version of the file, see here. See the "Generated Code" section below under "Usage" for more details.

Usage

Generating Code

PBAndK's code generator leverages protoc. Download the latest protoc and make sure protoc is on the PATH. Then download the latest protoc-gen-kotlin and make sure protoc-gen-kotlin is on the PATH. To generate code from sample.proto and put in src/main/kotlin, run:

protoc --kotlin_out=src/main/kotlin sample.proto

For Windows however, protoc doesn't support finding protoc-gen-kotlin.bat on the PATH. So it has to be specified explicitly as a plugin:

protoc --kotlin_out=src/main/kotlin --plugin=protoc-gen-kotlin=path/to/protoc-gen-kotlin.bat sample.proto

The file is generated as sample.kt in the subdirectories specified by the package. Like other X_out arguments, comma-separated options can be added to --kotlin_out before the colon and out dir path. To explicitly set the Kotlin package to my.pkg, use the kotlin_package option like so:

protoc --kotlin_out=kotlin_package=my.pkg:src/main/kotlin sample.proto

To log debug logs during generation, log=debug can be set as well.

In addition to running protoc manually, the Protobuf Plugin for Gradle can be used. See this example to see how.

Runtime Library

PBAndK's runtime library is a thin layer over the preferred Protobuf library for each platform. The libraries are present on Maven Central. Using Gradle and assuming mavenCentral() is one of the repositories, the dependency can be added for JVM libraries:

dependencies {
    compile 'com.github.cretz.pbandk:pbandk-runtime-jvm:0.3.0'
}

It has a dependency on the Google Protobuf Java library. The code targets Java 1.6 to be Android friendly. For Kotlin JS, change pbandk-runtime-jvm to pbandk-runtime-js and for common multiplatform code, change pbandk-runtime-jvm to pbandk-runtime-common.

Service Code Generation

PBAndK does not generate gRPC code itself, but offers a pbandk.gen.ServiceGenerator interface in the protoc-gen-kotlin-jvm project (really in the protoc-gen-kotlin-common project and inherited) with a single method that can be implemented to generate the code.

To do this, first depend on the project but it will only be needed at compile time because it's already there at runtime:

dependencies {
    compileOnly 'com.github.cretz.pbandk:protoc-gen-kotlin-jvm:0.3.0'
}

Then, the kotlin_service_gen option can be given to protoc to use the generator. The option is a path-separated collection of JAR files to put on the classpath. It can end with a pipe (i.e. |) following by the fully-qualified class name of the implementation of the ServiceGenerator to use. If the last part is not present, it will use the ServiceLoader mechanism to find the first implementation to use. For example, to gen with my.Generator from gen.jar, it might look like:

protoc --kotlin_out=kotlin_service_gen=gen.jar|my.Generator,kotlin_package=my.pkg:src/main/kotlin some.proto

For more details, see the custom-service-gen example.

Generated Code

Package

The package is either the kotlin_package plugin option or the package set in the message. If the google.protobuf package is referenced, it is assumed to be a well-known type and is changed to reference pbandk.wkt.

Message

Each Protobuf message extends pbandk.Message and has two overloaded protoMarshal methods, the most useful of which marshals to a byte array. The companion object of every message has two overloaded protoUnmarshal methods, the most useful of which accepts a byte array and returns an instance of the class. The other protoMarshal and protoUnmarshal methods accept Marshaller and Unmarshaller instances respectively which are different for each platform. For example, the JVM Marshaller uses com.google.protobuf.CodedOutputStream.

Messages are immutable Kotlin data classes. This means they automatically implement hashCode, equals, and toString. Each class has an unknownFields map which contains information about extra fields the unmarshaller didn't recognize. If there are values in this map, they will be marshalled on output. The Unmarshaller instances have a constructor option to discard unknown fields when reading.

For proto3, the only nullable fields are other messages and oneof fields. Other values have defaults. For proto2, optional fields are nullable and defaulted as such. Types are basically the same as they would be in Java. However, bytes fields actually use a pbandk.ByteArr class which is a simple wrapper around a byte array. This was done because Kotlin does not handle array fields in data classes predictably and it wasn't worth overriding equals and hashCode every time.

Regardless of optimize_for options, the generated code is always the same. Each message has a protoSize field that lazily calculates the size of the message when first invoked. Also, each message has the plus operator defined which follows protobuf merge semantics.

Oneof

Oneof fields are generated as nested classes of a common sealed base class. Each oneof inner field is a data class that wraps a single value.

Enum

Enum fields are generated as single-integer-value immutable data classes with known values as vals on the companion object. This is preferred over traditional enum classes because enums in protobuf are open ended and may not be one of the specific known values. Traditional enum classes would not be able to capture this state and using data classes this way requires the user to do explicit checks for unknown ordinals during exhaustive when clauses. This does come at very negligible cost during equality checks. Although there is the normal single-integer-value constructor on the data class, developers should use the fromValue method present on the companion object. This will return an existing val if known. It is possible in future versions that the generated data class constructor will become private.

Note, there is no reflection outside of normal Kotlin reflection. This means Kotlin reflection has to be used to get string values of the enums currently. This may change as the JSON format is developed which requires the string form.

Repeated and Map

Repeated fields are normal lists. Developers should make no assumptions about which list implementation is used. Maps are represented by Kotlin maps. In proto2, due to how map entries are serialized, both the key and the value are considered nullable.

Well-Known Types

Well known types (i.e. those in the google/protobuf imports) are shipped with the runtime. At this early stage, specialized support (e.g. using Kotlin Any for google.protobuf.Any) is not implemented.

Services

Services can be handled with a custom service generator. See the "Service Code Generation" section above and the custom-service-gen example.

Building

The project is built with Gradle and has several sub projects. In alphabetical order, they are:

  • conformance/conformance-common - Multiplatform common code for conformance tests
  • conformance/conformance-js - JS code for conformance tests
  • conformance/conformance-jvm - JVM code for conformance tests
  • conformance/conformance-native - Native code for conformance tests (not built yet)
  • protoc-gen-kotlin/protoc-gen-kotlin-common - Multiplatform common code for the code generator
  • protoc-gen-kotlin/protoc-gen-kotlin-jvm - JVM code for the code generator
  • protoc-gen-kotlin/protoc-gen-kotlin-native - Native code for the code generator (not built yet)
  • runtime/runtime-common - Multiplatform common code for runtime Protobuf support
  • runtime/runtime-js - JS code for runtime Protobuf support
  • runtime/runtime-jvm - JVM code for runtime Protobuf support
  • runtime/runtime-native - Native code for runtime Protobuf support

Due to current issues, Gradle must be version 4.7 in the steps below.

Code Generator

To generate the protoc-gen-kotlin distribution, run:

path/to/gradle :protoc-gen-kotlin:protoc-gen-kotlin-jvm:assembleDist

Or use installDist to just put it locally and it will be in the build/install folder.

Runtime Library

To build the runtime library for both JS and the JVM, run:

path/to/gradle :runtime:runtime-js:assemble
path/to/gradle :runtime:runtime-jvm:assemble

Conformance Tests

To run conformance tests, the conformance-test-runner must be built (does not work on Windows). Then both the JS and JVM projects must be built via:

path/to/gradle :conformance:conformance-js:assemble
path/to/gradle :conformance:conformance-jvm:installDist

Set the CONF_TEST_PATH environment variable to the full path to path/to/protobuf/conformance/conformance-test-runner. The JS tests need to have npm install run in conformance/conformance-js. Then simply run conformance/test-conformance.sh.

More Repositories

1

bine

Go library for accessing and embedding Tor clients and servers
Go
719
star
2

asmble

Compile WebAssembly to JVM and other WASM tools
Kotlin
599
star
3

doogie

A Chromium-based web browser with tree-style pages
C++
278
star
4

kastree

Simple Kotlin Source AST and Syntax Parsing, Editing, and Writing
Kotlin
225
star
5

stackparam

JVM agent to add method parameters to Java stack traces
Rust
116
star
6

node-tds

Pure JS implementation of TDS protocol for Microsoft SQL Server
CoffeeScript
104
star
7

gwt-node

GWT implementation of standard the node.js library
Java
87
star
8

software-ideas

85
star
9

myscreen.live

P2P Screen Sharing with WebRTC
TypeScript
84
star
10

go-scrap

Go library to capture screen pixels for screenshots or screen recording
Go
76
star
11

tor-static

Helpers to build Tor statically
Go
74
star
12

dust-php

Powerful PHP templating engine based off of Dust JS
PHP
67
star
13

pgnio

Asynchronous PostgreSQL client for Java and the JVM
Java
62
star
14

gopaque

Go implementation of OPAQUE (hidden password user registration and auth)
Go
58
star
15

prost-twirp

Code generator and library for calling/serving Twirp services in Rust using prost and hyper
Rust
57
star
16

webrtc-ipfs-signaling

Tech demo using JS-IPFS to do signaling for WebRTC
HTML
56
star
17

javan-warty-pig

AFL-like fuzzer for the Java Virtual Machine
Java
49
star
18

scala-web-ideal

Akka HTTP + Scala.js + ScalaTags + ScalaCSS + Autowire + Prickle + akka-sse + Gradle + etc... (outdated/stale)
Scala
49
star
19

tor-dht-poc

Anonymous DHT Accessible from Executable or Tor-Enabled Browser
Go
48
star
20

pratphall

A typed language targeting PHP (abandoned)
JavaScript
45
star
21

qt_cef_poc

Proof of concept of simple browser w/ CEF and Qt
Go
26
star
22

kotlin-native-sample-agent

Sample JVMTI Agent Using Kotlin Native
Kotlin
23
star
23

chrome-screen-rec-poc

C++
21
star
24

go-sqleet

Encrypted SQLite for Go using go-sqlite3 and sqleet
C
17
star
25

msplit

Simple algorithm to split a JVM ASM method into two
Java
16
star
26

goahead

A JVM AOT
Scala
16
star
27

temporal-sdk-go-advanced

Go
15
star
28

go-mental-poker

Mental Poker Demonstration in Go
Go
12
star
29

go-wasm-bake

Experimenting with eager evaluation of Go WASM code
Kotlin
12
star
30

rtsw-poc

Rust + Tor (embedded) + Static (compile) + Windows + Proof of Concept
Rust
12
star
31

esgopeta

Go implementation of the Gun distributed graph database
Go
11
star
32

owncast-old

Go
11
star
33

superpose

Go library for compile-time code transformation
Go
10
star
34

SqlChop

Read the Transaction Log from SQL Server
C#
10
star
35

temporal-debug-go

Go
10
star
36

safeclient.js

JS Client to the SAFE Launcher API
TypeScript
9
star
37

imedge

Imedge - Image Manipulation in Edge Service Workers
Rust
8
star
38

rust-qt_cef_poc

Rust
8
star
39

temporal-protoc-gen-go-activity

Go
7
star
40

takecast

Go
7
star
41

go2k

Kotlin
5
star
42

rwtxt-crypt

The minimalist rwtxt CMS using encrypted SQLite and Tor
Go
5
star
43

rust-qthello

Rust
5
star
44

go-safeclient

CLI and Go library for accessing SAFE network
Go
4
star
45

statmantis

Java based baseball stat analyzer/parser
Java
4
star
46

gulliver

Scala
4
star
47

capnp-scala

Scala
3
star
48

ffembedpoc

Go
3
star
49

nexus-poc-old

Go
3
star
50

javan-warty-pig-kotlin

Kotlin
2
star
51

temporal-wasm

Go
2
star
52

teleworker

Go
2
star
53

temporal-determinist

Go
2
star
54

systrument

Go
2
star
55

badads

JavaScript
2
star
56

safe-poc-browser

Simple Proof-of-Concept Browser for SAFE Network
JavaScript
2
star
57

forsook

Java
2
star
58

smail

Scala
2
star
59

go-dump

Go
1
star
60

irnie

1
star
61

mlbdash

HTML, client-side only live MLB dashboard
Java
1
star
62

clicknkick

Go
1
star
63

shrewd-old

TypeScript
1
star
64

gwtson

GWT emulation layer for Gson
Java
1
star
65

monitoral

Go
1
star
66

distinct

JS diffing utility
CoffeeScript
1
star
67

yocalist

Java
1
star
68

fusty

Network Device Backup Tool/System
Go
1
star
69

narrow

1
star
70

gat-cdc

Trigger based Change Data Capture (CDC)
C#
1
star
71

yocalist-coffee

CoffeeScript
1
star
72

one-left

Go
1
star
73

sbn-stat

Generate posting statistics for SBN sites
Java
1
star
74

ripoata

1
star
75

elixoral

Elixir
1
star
76

rusty-zipper

Bindings and wrapper for libzip (incomplete)
Rust
1
star
77

caddy-tlsconsul

Go
1
star