Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: adds proto-kotlin and protoc-plugin-kotlin #7254

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions kotlin/core/.gitignore
@@ -0,0 +1,3 @@
# Gradle
build
.gradle
1 change: 1 addition & 0 deletions kotlin/core/README.md
@@ -0,0 +1 @@
# proto-kotlin: Google's Kotlin bindings for protobuf
118 changes: 118 additions & 0 deletions kotlin/core/build.gradle
@@ -0,0 +1,118 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version'1.3.61'
id 'com.google.protobuf' version '0.8.8'

// Generate IntelliJ IDEA's .idea & .iml project files
// Starting with 0.8.4 of protobuf-gradle-plugin, *.proto and the gen output files are added
// to IntelliJ as sources. It is no longer necessary to add them manually to the idea {} block
// to jump to definitions from Java and Kotlin files.
// For best results, install the Protobuf and Kotlin plugins for IntelliJ.
id 'idea'

// Provide convenience executables for trying out the examples.
id 'application'
id 'maven-publish'
}

apply plugin : "java"

mainClassName = 'com.google.protobuf.kotlin.generator.GeneratorRunner'
applicationName = 'proto-kotlin'

// Generate the jar file to be used by our custom plugin
jar {
manifest {
attributes 'Main-Class': mainClassName
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}

repositories {
google()
jcenter()
mavenCentral()
mavenLocal()
}

// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
def coroutinesVersion = '1.3.3'
def kotlinVersion = '1.3.61'

dependencies {
// Kotlin
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}"

// Protobuf
compile "com.google.protobuf:protobuf-kotlin-util:0.1"
compile 'com.google.protobuf:protobuf-gradle-plugin:0.8.11'
compile 'com.google.protobuf:protobuf-java:3.11.0'

compile 'com.squareup:kotlinpoet:1.5.0'
compile "org.jetbrains.kotlin:kotlin-reflect:1.3.61"

// Testing
testImplementation "com.google.truth:truth:1.0.1"
testImplementation "com.google.truth.extensions:truth-proto-extension:1.0"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.2'
testImplementation "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion"
testImplementation "junit:junit:4.12"
testImplementation "com.google.jimfs:jimfs:1.1"
}

protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.11.0' }
plugins {
// Specify protoc to generate using our kotlin plugin
kotlin {
path = 'build/install/proto-kotlin/bin/proto-kotlin'
}
}
generateProtoTasks {
all().each { task ->
// Generate the protoc binary first so we can generate the files for
// our tests
if (task.name.startsWith('generateTestProto')) {
task.dependsOn { installDist }
}
task.plugins {
// Add kotlin output without any option. This yields
// "--custom_out=/path/to/output" on the protoc commandline.
kotlin {}
}
}
}
}

def artifactForGradlePlugin(MavenPublication pub, String os, String arch) {
if (os == "windows") {
pub.artifact("src/main/dist/protoc-gen-kotlin.bat" as File) {
classifier os + "-" + arch
extension "exe"
}
} else {
pub.artifact("src/main/dist/protoc-gen-kotlin" as File) {
classifier os + "-" + arch
extension "exe"
}
}
}

publishing {
publications {
maven(MavenPublication) {
groupId = 'com.google.protobuf'
artifactId = 'protobuf-kotlin'
version = '0.1'
from components.java

// Generate the artifacts expected by protobuf-gradle-plugin
artifactForGradlePlugin(it, 'linux', 'aarch_64')
artifactForGradlePlugin(it, 'linux', 'x86_32')
artifactForGradlePlugin(it, 'linux', 'x86_64')
artifactForGradlePlugin(it, 'osx', 'x86_64')
artifactForGradlePlugin(it, 'windows', 'x86_32')
artifactForGradlePlugin(it, 'windows', 'x86_64')
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.tutorial

import com.example.tutorial.AddressBookProtos.AddressBook
import com.example.tutorial.AddressBookProtos.Person
import com.example.tutorial.AddressBookProtosKt.PersonKt.phoneNumber
import com.example.tutorial.AddressBookProtosKt.person
import java.io.BufferedReader
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.InputStreamReader
import java.io.PrintStream
import kotlin.system.exitProcess

object AddPerson {
fun promptForAddress(input: BufferedReader, output: PrintStream): Person {
return person {
output.print("Enter person ID: ")
id = input.readLine().toInt()

output.print("Enter name: ")
name = input.readLine()

output.print("Enter email address (blank for none): ")
val email = input.readLine()
if (email.isNotEmpty()) {
this.email = email
}

while (true) {
output.print("Enter a phone number (or leave blank to finish): ")
val number = input.readLine()
if (number.isEmpty()) {
break
}

phone += phoneNumber {
this.number = number
output.print("Is this a mobile, home, or work phone? ")
when (input.readLine()) {
"mobile" -> type = Person.PhoneType.MOBILE
"home" -> type = Person.PhoneType.HOME
"work" -> type = Person.PhoneType.WORK
else -> output.println("Unknown phone type. Using default.")
}
}
}
}
}

@JvmStatic
fun main(args: Array<String>) {
if (args.size != 1) {
System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE")
exitProcess(-1)
}

val addressBook = try {
FileInputStream(args.single()).use {
AddressBook.parseFrom(it)
}
} catch (e: FileNotFoundException) {
AddressBook.getDefaultInstance()
}

val newAddressBook = addressBook.copy {
BufferedReader(InputStreamReader(System.`in`, Charsets.UTF_8)).use {
person += promptForAddress(it, System.out)
}
}

FileOutputStream(args.single()).use {
newAddressBook.writeTo(it)
}
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.tutorial

import com.example.tutorial.AddressBookProtos.AddressBook
import java.io.FileInputStream
import kotlin.system.exitProcess

object ListPeople {
fun print(addressBook: AddressBook) {
for (person in addressBook.personList) {
println("Person ID: ${person.id}")
println(" Name: ${person.name}")
if (person.hasEmail()) {
println(" E-mail address: ${person.email}")
}
for (phoneNumber in person.phoneList) {
when (phoneNumber.type) {
AddressBookProtos.Person.PhoneType.MOBILE -> print(" Mobile phone #: ")
AddressBookProtos.Person.PhoneType.HOME -> print(" Home phone #: ")
AddressBookProtos.Person.PhoneType.WORK -> print(" Work phone #: ")
}
println(phoneNumber.number)
}
}
}

@JvmStatic
fun main(args: Array<String>) {
if (args.size != 1) {
System.err.println("Usage: ListPeople ADDRESS_BOOK_FILE")
exitProcess(-1)
}
print(FileInputStream(args.single()).use {
AddressBook.parseFrom(it)
})
}
}
29 changes: 29 additions & 0 deletions kotlin/core/example/src/main/proto/addressbook.proto
@@ -0,0 +1,29 @@
syntax = "proto2";

package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

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

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

message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}

repeated PhoneNumber phone = 4;
}

message AddressBook {
repeated Person person = 1;
}
26 changes: 26 additions & 0 deletions kotlin/core/src/main/dist/protoc-gen-kotlin
@@ -0,0 +1,26 @@
#!/bin/bash
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`" >/dev/null
APP_HOME="`pwd -P`"
APP_BASE_NAME=`basename "$0"`

# Get jar file name from current filename by stripping os and arch
JAR_FILENAME=${APP_BASE_NAME%.*}
JAR_FILENAME=${JAR_FILENAME/-linux-aarch_64/}
JAR_FILENAME=${JAR_FILENAME/-linux-x86_32/}
JAR_FILENAME=${JAR_FILENAME/-linux-x86_64/}
JAR_FILENAME=${JAR_FILENAME/-osx-x86_64/}

# Call the jar and pass our args
java -jar $JAR_FILENAME.jar $1 $2