All Downloads are FREE. Search and download functionalities are using the official Maven repository.

protocbridge.ExtraEnv.scala Maven / Gradle / Ivy

The newest version!
package protocbridge

import com.google.protobuf.CodedOutputStream
import com.google.protobuf.WireFormat
import com.google.protobuf.DescriptorProtos.FileDescriptorSet
import com.google.protobuf.TextFormat
import com.google.protobuf.Descriptors.FileDescriptor
import com.google.protobuf.DescriptorProtos.FileDescriptorProto
import com.google.protobuf.DynamicMessage
import com.google.protobuf.Descriptors.Descriptor
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest

/** Ad-hoc message-like class that represents extra environment available for
  * protoc code generators running through protocbridge. This message gets
  * appended by protocbridge to CodeGeneratorRequest so JVM plugins get access
  * to the environment. We do not generate Java or Scala code for this message
  * to prevent potential binary compatibility issues between the bridge and the
  * plugin. Instead, we serialize directly to bytes. Parsing is done in a
  * sandboxed environment, so we rely on DynamicMessage then.
  */
final class ExtraEnv(val secondaryOutputDir: String) {
  def toEnvMap: Map[String, String] = Map(
    ExtraEnv.ENV_SECONDARY_DIR -> secondaryOutputDir
  )

  // Serializes this message as a field. Used to append it to a CodeGeneratorRequest
  private[protocbridge] def toByteArrayAsField: Array[Byte] = {
    val out = Array.newBuilder[Byte]
    ProtoUtils.writeTag(out, ExtraEnv.EXTRA_ENV_FIELD_NUMBER, 2)
    val sz = ProtoUtils.computeStringSize(1, secondaryOutputDir)
    ProtoUtils.writeRawVarint32(out, sz)
    ProtoUtils.writeString(out, 1, secondaryOutputDir)

    out.result()
  }

  override def toString(): String =
    s"ExtraEnv(secondaryOutputDir=$secondaryOutputDir)"
}

object ExtraEnv {
  val EXTRA_ENV_FIELD_NUMBER = 1020 // ScalaPB assigned extension number
  val ENV_SECONDARY_DIR = "SCALAPB_SECONDARY_OUTPUT_DIR"
}

// Seperated to a different object since it depends on protobuf-java and expected to be run
// only in sandboxed environment.
object ExtraEnvParser {
  def fromDynamicMessage(dm: DynamicMessage): ExtraEnv = {
    new ExtraEnv(
      dm.getField(extraEnvDescriptor.findFieldByNumber(1)).asInstanceOf[String]
    )
  }

  def fromCodeGeneratorRequest(req: CodeGeneratorRequest): ExtraEnv = {
    val ll = req
      .getUnknownFields()
      .getField(ExtraEnv.EXTRA_ENV_FIELD_NUMBER)
      .getLengthDelimitedList
    if (ll.size() == 0) new ExtraEnv("")
    else
      fromDynamicMessage(
        DynamicMessage.parseFrom(
          extraEnvDescriptor,
          ll.get(0)
        )
      )
  }

  private val (extraEnvDescriptor, request): (Descriptor, Descriptor) = {
    val proto = TextFormat.parse(
      s"""
         |message_type {
         |  name: "ExtraEnv"
         |  field {
         |    name: "secondary_output_dir"
         |    number: 1
         |    label: LABEL_OPTIONAL
         |    type: TYPE_STRING
         |    json_name: "secondaryOutputDir"
         |  }
         |}
         |
         |message_type {
         |  name: "Request"
         |  field {
         |    name: "extra_env"
         |    number: ${ExtraEnv.EXTRA_ENV_FIELD_NUMBER}
         |    label: LABEL_OPTIONAL
         |    type: TYPE_MESSAGE
         |    type_name: ".ExtraEnv"
         |    json_name: "extraEnv"
         |  }
         |}
         |""".stripMargin,
      classOf[FileDescriptorProto]
    )
    val fd = FileDescriptor
      .buildFrom(proto, Array.empty)

    (fd.findMessageTypeByName("ExtraEnv"), fd.findMessageTypeByName("Request"))
  }
  private val secondaryOutputFieldDescriptor =
    extraEnvDescriptor.findFieldByName("secondary_output_dir")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy