Skip to content

Commit

Permalink
Initial work on custom options.
Browse files Browse the repository at this point in the history
  • Loading branch information
thesamet committed Jun 5, 2016
1 parent 3735d45 commit 31e5ce4
Show file tree
Hide file tree
Showing 17 changed files with 459 additions and 73 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ compiler-plugin/src/main/scala/com/trueaccord/scalapb/compiler/Version.scala
e2e/project/project/Version.scala
e2e/project/Version.scala
e2e/.bin
e2e/nojava/.bin
atlassian-ide-plugin.xml
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ lazy val runtime = crossProject.crossType(CrossType.Full).in(file("scalapb-runti
.jsSettings(
// Add JS-specific settings here
libraryDependencies ++= Seq(
"com.trueaccord.scalapb" %%% "protobuf-runtime-scala" % "0.1.7"
"com.trueaccord.scalapb" %%% "protobuf-runtime-scala" % "0.1.8"
),
scalacOptions += {
val a = (baseDirectory in LocalRootProject).value.toURI.toString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,27 @@ trait DescriptorPimps {

def typeMapperValName = "_typemapper_" + scalaName

def typeMapper = fd.getContainingType.scalaTypeName + "." + typeMapperValName
def typeMapper = {
if (!fd.isExtension)
fd.getContainingType.scalaTypeName + "." + typeMapperValName
else {
val c = if (fd.getExtensionScope == null) fd.getFile.fileDescriptorObjectFullName
else fd.getExtensionScope.scalaTypeName
c + "." + typeMapperValName
}
}

def isEnum = fd.getType == FieldDescriptor.Type.ENUM

def isMessage = fd.getType == FieldDescriptor.Type.MESSAGE

def javaExtensionFieldFullName = {
require(fd.isExtension)
val inClass =
if (fd.getExtensionScope == null) fd.getFile.javaFullOuterClassName
else fd.getExtensionScope.javaTypeName
s"$inClass.${fd.scalaName}"
}
}

implicit class OneofDescriptorPimp(val oneof: OneofDescriptor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ case class MethodApplication(name: String) extends LiteralExpression {
def isFunctionApplication: Boolean = false
}

case class OperatorApplication(op: String) extends LiteralExpression {
def isIdentity: Boolean = false
def isFunctionApplication: Boolean = false
}

object ExpressionBuilder {
def runSingleton(es: List[LiteralExpression])(e: String): String = es match {
case Nil => e
case Identity :: tail => runSingleton(tail)(e)
case FunctionApplication(name) :: tail => s"$name(${runSingleton(tail)(e)})"
case MethodApplication(name) :: tail => s"${runSingleton(tail)(e)}.$name"
case OperatorApplication(name) :: tail => s"${runSingleton(tail)(e)} $name"
}

def runCollection(es: List[LiteralExpression])(e: String): String = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package com.trueaccord.scalapb.compiler

import com.google.protobuf.Descriptors._
import com.google.protobuf.CodedOutputStream
import com.google.protobuf.Descriptors.FieldDescriptor.Type
import com.google.protobuf.{ByteString => GoogleByteString}
import com.google.protobuf.compiler.PluginProtos.{CodeGeneratorRequest, CodeGeneratorResponse}
import fastparse.Utils.FuncName

import scala.collection.JavaConversions._

case class GeneratorParams(
Expand Down Expand Up @@ -181,18 +184,22 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
}


def javaFieldToScala(container: String, field: FieldDescriptor) = {
val javaHas = container + ".has" + field.upperScalaName
def javaFieldToScala(javaHazzer: String, javaGetter: String, field: FieldDescriptor): String = {
val valueConversion: Expression = javaToScalaConversion(field)

if (field.supportsPresence)
s"if ($javaHazzer) Some(${valueConversion.apply(javaGetter, isCollection = false)}) else None"
else valueConversion(javaGetter, isCollection = field.isRepeated)
}

def javaFieldToScala(container: String, field: FieldDescriptor): String = {
val javaHazzer = container + ".has" + field.upperScalaName
val javaGetter = if (field.isRepeated)
container + ".get" + field.upperScalaName + "List"
else
container + ".get" + field.upperScalaName

val valueConversion = javaToScalaConversion(field)

if (field.supportsPresence)
s"if ($javaHas) Some(${valueConversion.apply(javaGetter, isCollection = false)}) else None"
else valueConversion(javaGetter, isCollection = field.isRepeated)
javaFieldToScala(javaHazzer, javaGetter, field)
}

def javaMapFieldToScala(container: String, field: FieldDescriptor) = {
Expand Down Expand Up @@ -499,7 +506,8 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {

def generateMergeFrom(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val myFullScalaName = message.scalaTypeName
printer.add(
printer
.add(
s"def mergeFrom(__input: com.google.protobuf.CodedInputStream): $myFullScalaName = {")
.indent
.print(message.fieldsWithoutOneofs)((field, printer) =>
Expand Down Expand Up @@ -752,9 +760,9 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
}
}

def generateTypeMappers(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
def generateTypeMappers(fields: Seq[FieldDescriptor])(printer: FunctionalPrinter): FunctionalPrinter = {
val customizedFields: Seq[(FieldDescriptor, String)] = for {
field <- message.fields
field <- fields
custom <- field.customSingleScalaTypeName
} yield (field, custom)

Expand Down Expand Up @@ -827,6 +835,57 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
else fp.add(signature + "throw new MatchError(__field)")
}

def printExtension(fd: FieldDescriptor, fp: FunctionalPrinter) = {
val optionType = fd.getContainingType.getFullName match {
case "google.protobuf.FileOptions" => true
case "google.protobuf.MessageOptions" => true
case "google.protobuf.FieldOptions" => true
case "google.protobuf.EnumOptions" => true
case "google.protobuf.EnumValueOptions" => true
case "google.protobuf.ServiceOptions" => true
case "google.protobuf.MethodOptions" => true
case _ => false
}
if (!optionType) fp
else fp
.add(s"val ${fd.scalaName}: com.trueaccord.scalapb.GeneratedExtension[${fd.getContainingType.javaTypeName}, ${fd.scalaTypeName}] =")
.when(params.javaConversions) {
fp =>
val hazzer = s"__valueIn.hasExtension(${fd.javaExtensionFieldFullName})"
val getter = s"__valueIn.getExtension(${fd.javaExtensionFieldFullName})"
fp.add(s" com.trueaccord.scalapb.GeneratedExtension.forExtension({__valueIn => ${javaFieldToScala(hazzer, getter, fd)}})")
}
.when(!params.javaConversions) {
fp =>
val (container, baseExpr) = fd.getType match {
case Type.DOUBLE => ("getFixed64List", FunctionApplication("java.lang.Double.longBitsToDouble"))
case Type.FLOAT => ("getFixed32List", FunctionApplication("java.lang.Float.intBitsToFloat"))
case Type.INT64 => ("getVarintList", Identity)
case Type.UINT64 => ("getVarintList", Identity)
case Type.INT32 => ("getVarintList", MethodApplication("toInt"))
case Type.FIXED64 => ("getFixed64List", Identity)
case Type.FIXED32 => ("getFixed32List", Identity)
case Type.BOOL => ("getVarintList", OperatorApplication("!= 0"))
case Type.STRING => ("getLengthDelimitedList", MethodApplication("toStringUtf8"))
case Type.GROUP => throw new RuntimeException("Not supported")
case Type.MESSAGE => ("getLengthDelimitedList", FunctionApplication(s"com.trueaccord.scalapb.GeneratedExtension.readMessageFromByteString(${fd.baseSingleScalaTypeName})"))
case Type.BYTES => ("getLengthDelimitedList", Identity)
case Type.UINT32 => ("getVarintList", MethodApplication("toInt"))
case Type.ENUM => ("getVarintList", MethodApplication("toInt") andThen FunctionApplication(fd.baseSingleScalaTypeName + ".fromValue"))
case Type.SFIXED32 => ("getFixed32List", Identity)
case Type.SFIXED64 => ("getFixed64List", Identity)
case Type.SINT32 => ("getVarintList", MethodApplication("toInt") andThen FunctionApplication("com.google.protobuf.CodedInputStream.decodeZigZag32"))
case Type.SINT64 => ("getVarintList", FunctionApplication("com.google.protobuf.CodedInputStream.decodeZigZag64"))
}
val customExpr = baseExpr andThen toCustomTypeExpr(fd)

val factoryMethod =
if (fd.isOptional) "com.trueaccord.scalapb.GeneratedExtension.forOptionalUnknownField"
else if (fd.isRepeated) "com.trueaccord.scalapb.GeneratedExtension.forRepeatedUnknownField"
fp.add(s" $factoryMethod(${fd.getNumber}, _.$container)({__valueIn => ${customExpr("__valueIn", false)}})")
}
}

def generateMessageCompanion(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val className = message.nameSymbol
val companionType = message.companionBaseClasses.mkString(" with ")
Expand All @@ -844,9 +903,10 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
.print(message.getEnumTypes)(printEnum)
.print(message.getOneofs)(printOneof)
.print(message.nestedTypes)(printMessage)
.print(message.getExtensions)(printExtension)
.call(generateMessageLens(message))
.call(generateFieldNumbers(message))
.when(!message.isMapEntry)(generateTypeMappers(message))
.when(!message.isMapEntry)(generateTypeMappers(message.fields ++ message.getExtensions))
.when(message.isMapEntry)(generateTypeMappersForMapEntry(message))
.outdent
.add("}")
Expand Down Expand Up @@ -992,22 +1052,6 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
}
}

def generateSingleScalaFileForFileDescriptor(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
val code =
scalaFileHeader(file)
.print(file.getEnumTypes)(printEnum)
.print(file.getMessageTypes)(printMessage)
.add(s"object ${file.fileDescriptorObjectName} {")
.indent
.call(generateFileDescriptor(file))
.outdent
.add("}").result()
val b = CodeGeneratorResponse.File.newBuilder()
b.setName(file.scalaPackageName.replace('.', '/') + "/" + file.fileDescriptorObjectName + ".scala")
b.setContent(code)
generateServiceFiles(file) :+ b.build
}

def generateServiceFiles(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
if(params.grpc) {
file.getServices.map { service =>
Expand All @@ -1021,6 +1065,28 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
} else Nil
}

def createFileDescriptorCompanionObject(file: FileDescriptor)(fp: FunctionalPrinter): FunctionalPrinter = {
fp.add(s"object ${file.fileDescriptorObjectName} {")
.indent
.call(generateFileDescriptor(file))
.print(file.getExtensions)(printExtension)
.call(generateTypeMappers(file.getExtensions))
.outdent
.add("}")
}

def generateSingleScalaFileForFileDescriptor(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
val code =
scalaFileHeader(file)
.print(file.getEnumTypes)(printEnum)
.print(file.getMessageTypes)(printMessage)
.call(createFileDescriptorCompanionObject(file)).result()
val b = CodeGeneratorResponse.File.newBuilder()
b.setName(file.scalaPackageName.replace('.', '/') + "/" + file.fileDescriptorObjectName + ".scala")
b.setContent(code)
generateServiceFiles(file) :+ b.build
}

def generateMultipleScalaFilesForFileDescriptor(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
val serviceFiles = generateServiceFiles(file)

Expand Down Expand Up @@ -1051,11 +1117,7 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
b.setName(file.scalaPackageName.replace('.', '/') + s"/${file.fileDescriptorObjectName}.scala")
b.setContent(
scalaFileHeader(file)
.add(s"object ${file.fileDescriptorObjectName} {")
.indent
.call(generateFileDescriptor(file))
.outdent
.add("}").result())
.call(createFileDescriptorCompanionObject(file)).result())
b.build
}

Expand Down
2 changes: 1 addition & 1 deletion e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ set -e
sbt ++2.10.6 compilerPlugin/publishLocal runtimeJVM/publishLocal createVersionFile \
++2.11.7 runtimeJVM/publishLocal grpcRuntime/publishLocal
cd e2e
sbt clean test
sbt clean noJava/clean noJava/test test

74 changes: 39 additions & 35 deletions e2e/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,10 @@ import com.trueaccord.scalapb.{ScalaPbPlugin => PB}

scalaVersion := "2.11.7"

PB.protobufSettings

PB.scalapbVersion in PB.protobufConfig := com.trueaccord.scalapb.Version.scalapbVersion

PB.javaConversions in PB.protobufConfig := true

PB.runProtoc in PB.protobufConfig := { args0 =>
val args = args0 ++ Array(
s"--plugin=protoc-gen-java_rpc=${grpcExePath.value.get}",
s"--java_rpc_out=${((sourceManaged in Compile).value / "compiled_protobuf").getAbsolutePath}"
)
com.github.os72.protocjar.Protoc.runProtoc("-v300" +: args.toArray)
}

val grpcVersion = "0.14.0"

val grpcArtifactId = "protoc-gen-grpc-java"

libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "2.2.1" % "test",
"io.grpc" % "grpc-netty" % grpcVersion, //netty transport of grpc
"io.grpc" % "grpc-protobuf" % grpcVersion, //protobuf message encoding for java implementation
"org.scalacheck" %% "scalacheck" % "1.12.4" % "test",
"com.trueaccord.scalapb" %% "scalapb-runtime-grpc" % com.trueaccord.scalapb.Version.scalapbVersion,
"com.trueaccord.scalapb" %% "scalapb-runtime" % com.trueaccord.scalapb.Version.scalapbVersion % PB.protobufConfig,
"com.trueaccord.scalapb" %% "scalapb-json4s" % "0.1"
)

def grpcExeFileName = {
val os = if (scala.util.Properties.isMac){
"osx-x86_64"
Expand All @@ -46,15 +22,43 @@ lazy val grpcExeUrl =

val grpcExePath = SettingKey[xsbti.api.Lazy[File]]("grpcExePath")

grpcExePath := xsbti.SafeLazy {
val exe: File = baseDirectory.value / ".bin" / grpcExeFileName
if (!exe.exists) {
println("grpc protoc plugin (for Java) does not exist. Downloading.")
IO.download(grpcExeUrl, exe)
exe.setExecutable(true)
} else {
println("grpc protoc plugin (for Java) exists.")
}
exe
}

val commonSettings = PB.protobufSettings ++ Seq(
PB.scalapbVersion in PB.protobufConfig := com.trueaccord.scalapb.Version.scalapbVersion,
PB.runProtoc in PB.protobufConfig := { args0 =>
val args = args0 ++ Array(
s"--plugin=protoc-gen-java_rpc=${grpcExePath.value.get}",
s"--java_rpc_out=${((sourceManaged in Compile).value / "compiled_protobuf").getAbsolutePath}"
)
com.github.os72.protocjar.Protoc.runProtoc("-v300" +: args.toArray)
},
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "2.2.1" % "test",
"io.grpc" % "grpc-netty" % grpcVersion, //netty transport of grpc
"io.grpc" % "grpc-protobuf" % grpcVersion, //protobuf message encoding for java implementation
"org.scalacheck" %% "scalacheck" % "1.12.4" % "test",
"com.trueaccord.scalapb" %% "scalapb-runtime" % com.trueaccord.scalapb.Version.scalapbVersion % PB.protobufConfig,
"com.trueaccord.scalapb" %% "scalapb-json4s" % "0.1"
),
grpcExePath := xsbti.SafeLazy {
val exe: File = baseDirectory.value / ".bin" / grpcExeFileName
if (!exe.exists) {
println("grpc protoc plugin (for Java) does not exist. Downloading.")
IO.download(grpcExeUrl, exe)
exe.setExecutable(true)
} else {
println("grpc protoc plugin (for Java) exists.")
}
exe
})

lazy val root = (project in file("."))
.settings(commonSettings)
.settings(
PB.javaConversions in PB.protobufConfig := true,
libraryDependencies ++= Seq(
"com.trueaccord.scalapb" %% "scalapb-runtime-grpc" % com.trueaccord.scalapb.Version.scalapbVersion
))

lazy val noJava = (project in file("nojava"))
.settings(commonSettings)
1 change: 1 addition & 0 deletions e2e/nojava/src/main/protobuf/custom_options.proto
1 change: 1 addition & 0 deletions e2e/nojava/src/main/protobuf/custom_options_use.proto
1 change: 1 addition & 0 deletions e2e/nojava/src/main/protobuf/custom_types.proto
1 change: 1 addition & 0 deletions e2e/nojava/src/main/scala/PersonId.scala
1 change: 1 addition & 0 deletions e2e/nojava/src/test/scala/CustomOptionsSpec.scala
Loading

0 comments on commit 31e5ce4

Please # to comment.