Browse Source

Rename the plugin: protoc-gen-swiftgrpc -> protoc-gen-grpc-swift (#584)

Motivation:

We refer to this package as 'grpc-swift', the 'protoc' plugin should be
consistent with this.

The plugin was also not listed as a product of the package which means
that a dependency is not able to build the plugin withough checking out
the repository.

Modifications:

- Rename the protoc plugin to 'protoc-gen-grpc-swift'.
- Update references to this.
- Include the plugin as an executable product.

Result:

- More consistent naming.
- It is possible to build the plugin with `swift build --product
  protoc-gen-grpc-swift` when grpc-swift is used as a
  dependency.
George Barnett 6 years ago
parent
commit
a9be8ae0cf

+ 165 - 0
Sources/protoc-gen-grpc-swift/Generator-Client.swift

@@ -0,0 +1,165 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+import Foundation
+import SwiftProtobuf
+import SwiftProtobufPluginLibrary
+
+extension Generator {
+  internal func printClient() {
+    println()
+    printServiceClientProtocol()
+    println()
+    printServiceClientImplementation()
+  }
+
+  private func printServiceClientProtocol() {
+    println("/// Usage: instantiate \(serviceClassName)Client, then call methods of this protocol to make API calls.")
+    println("\(options.visibility.sourceSnippet) protocol \(serviceClassName) {")
+    indent()
+    for method in service.methods {
+      self.method = method
+      switch streamingType(method) {
+      case .unary:
+        println("func \(methodFunctionName)(_ request: \(methodInputName), callOptions: CallOptions?) -> UnaryCall<\(methodInputName), \(methodOutputName)>")
+
+      case .serverStreaming:
+        println("func \(methodFunctionName)(_ request: \(methodInputName), callOptions: CallOptions?, handler: @escaping (\(methodOutputName)) -> Void) -> ServerStreamingCall<\(methodInputName), \(methodOutputName)>")
+
+      case .clientStreaming:
+        println("func \(methodFunctionName)(callOptions: CallOptions?) -> ClientStreamingCall<\(methodInputName), \(methodOutputName)>")
+
+      case .bidirectionalStreaming:
+        println("func \(methodFunctionName)(callOptions: CallOptions?, handler: @escaping (\(methodOutputName)) -> Void) -> BidirectionalStreamingCall<\(methodInputName), \(methodOutputName)>")
+      }
+    }
+    outdent()
+    println("}")
+  }
+
+  private func printServiceClientImplementation() {
+    println("\(access) final class \(serviceClassName)Client: GRPCServiceClient, \(serviceClassName) {")
+    indent()
+    println("\(access) let connection: ClientConnection")
+    println("\(access) var serviceName: String { return \"\(servicePath)\" }")
+    println("\(access) var defaultCallOptions: CallOptions")
+    println()
+    println("/// Creates a client for the \(servicePath) service.")
+    println("///")
+    printParameters()
+    println("///   - connection: `ClientConnection` to the service host.")
+    println("///   - defaultCallOptions: Options to use for each service call if the user doesn't provide them.")
+    println("\(access) init(connection: ClientConnection, defaultCallOptions: CallOptions = CallOptions()) {")
+    indent()
+    println("self.connection = connection")
+    println("self.defaultCallOptions = defaultCallOptions")
+    outdent()
+    println("}")
+    println()
+
+    for method in service.methods {
+      self.method = method
+      switch streamingType(method) {
+      case .unary:
+        println("/// Asynchronous unary call to \(method.name).")
+        println("///")
+        printParameters()
+        printRequestParameter()
+        printCallOptionsParameter()
+        println("/// - Returns: A `UnaryCall` with futures for the metadata, status and response.")
+        println("\(access) func \(methodFunctionName)(_ request: \(methodInputName), callOptions: CallOptions? = nil) -> UnaryCall<\(methodInputName), \(methodOutputName)> {")
+        indent()
+        println("return self.makeUnaryCall(path: self.path(forMethod: \"\(method.name)\"),")
+        println("                          request: request,")
+        println("                          callOptions: callOptions ?? self.defaultCallOptions)")
+        outdent()
+        println("}")
+
+      case .serverStreaming:
+        println("/// Asynchronous server-streaming call to \(method.name).")
+        println("///")
+        printParameters()
+        printRequestParameter()
+        printCallOptionsParameter()
+        printHandlerParameter()
+        println("/// - Returns: A `ServerStreamingCall` with futures for the metadata and status.")
+        println("\(access) func \(methodFunctionName)(_ request: \(methodInputName), callOptions: CallOptions? = nil, handler: @escaping (\(methodOutputName)) -> Void) -> ServerStreamingCall<\(methodInputName), \(methodOutputName)> {")
+        indent()
+        println("return self.makeServerStreamingCall(path: self.path(forMethod: \"\(method.name)\"),")
+        println("                                    request: request,")
+        println("                                    callOptions: callOptions ?? self.defaultCallOptions,")
+        println("                                    handler: handler)")
+        outdent()
+        println("}")
+
+      case .clientStreaming:
+        println("/// Asynchronous client-streaming call to \(method.name).")
+        println("///")
+        printClientStreamingDetails()
+        println("///")
+        printParameters()
+        printCallOptionsParameter()
+        println("/// - Returns: A `ClientStreamingCall` with futures for the metadata, status and response.")
+        println("\(access) func \(methodFunctionName)(callOptions: CallOptions? = nil) -> ClientStreamingCall<\(methodInputName), \(methodOutputName)> {")
+        indent()
+        println("return self.makeClientStreamingCall(path: self.path(forMethod: \"\(method.name)\"),")
+        println("                                    callOptions: callOptions ?? self.defaultCallOptions)")
+        outdent()
+        println("}")
+
+      case .bidirectionalStreaming:
+        println("/// Asynchronous bidirectional-streaming call to \(method.name).")
+        println("///")
+        printClientStreamingDetails()
+        println("///")
+        printParameters()
+        printCallOptionsParameter()
+        printHandlerParameter()
+        println("/// - Returns: A `ClientStreamingCall` with futures for the metadata and status.")
+        println("\(access) func \(methodFunctionName)(callOptions: CallOptions? = nil, handler: @escaping (\(methodOutputName)) -> Void) -> BidirectionalStreamingCall<\(methodInputName), \(methodOutputName)> {")
+        indent()
+        println("return self.makeBidirectionalStreamingCall(path: self.path(forMethod: \"\(method.name)\"),")
+        println("                                           callOptions: callOptions ?? self.defaultCallOptions,")
+        println("                                           handler: handler)")
+        outdent()
+        println("}")
+      }
+      println()
+    }
+    outdent()
+    println("}")
+  }
+
+  private func printClientStreamingDetails() {
+    println("/// Callers should use the `send` method on the returned object to send messages")
+    println("/// to the server. The caller should send an `.end` after the final message has been sent.")
+  }
+
+  private func printParameters() {
+    println("/// - Parameters:")
+  }
+
+  private func printRequestParameter() {
+    println("///   - request: Request to send to \(method.name).")
+  }
+
+  private func printCallOptionsParameter() {
+    println("///   - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.")
+  }
+
+  private func printHandlerParameter() {
+    println("///   - handler: A closure called when each response is received from the server.")
+  }
+}

+ 77 - 0
Sources/protoc-gen-grpc-swift/Generator-Names.swift

@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+import Foundation
+import SwiftProtobuf
+import SwiftProtobufPluginLibrary
+
+internal func nameForPackageService(_ file: FileDescriptor,
+                                    _ service: ServiceDescriptor) -> String {
+  if !file.package.isEmpty {
+    return SwiftProtobufNamer().typePrefix(forFile:file) + service.name
+  } else {
+    return service.name
+  }
+}
+
+internal func nameForPackageServiceMethod(_ file: FileDescriptor,
+                                          _ service: ServiceDescriptor,
+                                          _ method: MethodDescriptor) -> String {
+  return nameForPackageService(file, service) + method.name
+}
+
+extension Generator {
+
+  internal var access : String {
+    return options.visibility.sourceSnippet
+  }
+
+  internal var serviceClassName: String {
+    return nameForPackageService(file, service) + "Service"
+  }
+
+  internal var providerName: String {
+    return nameForPackageService(file, service) + "Provider"
+  }
+
+  internal var callName: String {
+    return nameForPackageServiceMethod(file, service, method) + "Call"
+  }
+
+  internal var methodFunctionName: String {
+    let name = method.name
+    return name.prefix(1).lowercased() + name.dropFirst()
+  }
+
+  internal var methodInputName: String {
+    return protobufNamer.fullName(message: method.inputType)
+  }
+
+  internal var methodOutputName: String {
+    return protobufNamer.fullName(message: method.outputType)
+  }
+  
+  internal var servicePath: String {
+    if !file.package.isEmpty {
+      return file.package + "." + service.name
+    } else {
+      return service.name
+    }
+  }
+
+  internal var methodPath: String {
+    return "\"/" + servicePath + "/" + method.name + "\""
+  }
+}

+ 92 - 0
Sources/protoc-gen-grpc-swift/Generator-Server.swift

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+import Foundation
+import SwiftProtobuf
+import SwiftProtobufPluginLibrary
+
+extension Generator {
+  internal func printServer() {
+    printServerProtocol()
+  }
+
+  private func printServerProtocol() {
+    println("/// To build a server, implement a class that conforms to this protocol.")
+    println("\(access) protocol \(providerName): CallHandlerProvider {")
+    indent()
+    for method in service.methods {
+      self.method = method
+      
+      switch streamingType(method) {
+      case .unary:
+        println("func \(methodFunctionName)(request: \(methodInputName), context: StatusOnlyCallContext) -> EventLoopFuture<\(methodOutputName)>")
+      case .serverStreaming:
+        println("func \(methodFunctionName)(request: \(methodInputName), context: StreamingResponseCallContext<\(methodOutputName)>) -> EventLoopFuture<GRPCStatus>")
+      case .clientStreaming:
+        println("func \(methodFunctionName)(context: UnaryResponseCallContext<\(methodOutputName)>) -> EventLoopFuture<(StreamEvent<\(methodInputName)>) -> Void>")
+      case .bidirectionalStreaming:
+        println("func \(methodFunctionName)(context: StreamingResponseCallContext<\(methodOutputName)>) -> EventLoopFuture<(StreamEvent<\(methodInputName)>) -> Void>")
+      }
+    }
+    outdent()
+    println("}")
+    println()
+    println("extension \(providerName) {")
+    indent()
+    println("\(access) var serviceName: String { return \"\(servicePath)\" }")
+    println()
+    println("/// Determines, calls and returns the appropriate request handler, depending on the request's method.")
+    println("/// Returns nil for methods not handled by this service.")
+    println("\(access) func handleMethod(_ methodName: String, callHandlerContext: CallHandlerContext) -> GRPCCallHandler? {")
+    indent()
+    println("switch methodName {")
+    for method in service.methods {
+      self.method = method
+      println("case \"\(method.name)\":")
+      indent()
+      let callHandlerType: String
+      switch streamingType(method) {
+        case .unary: callHandlerType = "UnaryCallHandler"
+        case .serverStreaming: callHandlerType = "ServerStreamingCallHandler"
+        case .clientStreaming: callHandlerType = "ClientStreamingCallHandler"
+        case .bidirectionalStreaming: callHandlerType = "BidirectionalStreamingCallHandler"
+      }
+      println("return \(callHandlerType)(callHandlerContext: callHandlerContext) { context in")
+      indent()
+      switch streamingType(method) {
+      case .unary, .serverStreaming:
+        println("return { request in")
+        indent()
+        println("self.\(methodFunctionName)(request: request, context: context)")
+        outdent()
+        println("}")
+      case .clientStreaming, .bidirectionalStreaming:
+        println("return self.\(methodFunctionName)(context: context)")
+      }
+      outdent()
+      println("}")
+      outdent()
+      println()
+    }
+    println("default: return nil")
+    println("}")
+    outdent()
+    println("}")
+
+    outdent()
+    println("}")
+    println()
+  }
+}

+ 121 - 0
Sources/protoc-gen-grpc-swift/Generator.swift

@@ -0,0 +1,121 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+import SwiftProtobufPluginLibrary
+
+class Generator {
+  internal var options: GeneratorOptions
+  private var printer: CodePrinter
+
+  internal var file: FileDescriptor
+  internal var service: ServiceDescriptor! // context during generation
+  internal var method: MethodDescriptor!   // context during generation
+
+  internal let protobufNamer: SwiftProtobufNamer
+
+  init(_ file: FileDescriptor, options: GeneratorOptions) {
+    self.file = file
+    self.options = options
+    self.printer = CodePrinter()
+    self.protobufNamer = SwiftProtobufNamer(
+      currentFile: file,
+      protoFileToModuleMappings: options.protoToModuleMappings)
+    printMain()
+  }
+
+  public var code: String {
+    return printer.content
+  }
+
+  internal func println(_ text: String = "") {
+    printer.print(text)
+    printer.print("\n")
+  }
+
+  internal func indent() {
+    printer.indent()
+  }
+
+  internal func outdent() {
+    printer.outdent()
+  }
+
+  private func printMain() {
+    printer.print("""
+      //
+      // DO NOT EDIT.
+      //
+      // Generated by the protocol buffer compiler.
+      // Source: \(file.name)
+      //
+
+      //
+      // Copyright 2018, gRPC Authors All rights reserved.
+      //
+      // 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.
+      //\n
+      """)
+
+    let moduleNames = [
+        "Foundation",
+        "NIO",
+        "NIOHTTP1",
+        "GRPC",
+        "SwiftProtobuf"
+    ]
+
+    for moduleName in (moduleNames + options.extraModuleImports).sorted() {
+      println("import \(moduleName)")
+    }
+    // Build systems like Bazel will generate the Swift service definitions in a different module
+    // than the rest of the protos defined in the same file (because they are generated by separate
+    // build rules that invoke the separate generator plugins). So, we need to specify module
+    // mappings to import the service protos as well as and any other proto modules that the file
+    // imports.
+    let moduleMappings = options.protoToModuleMappings
+    if let serviceProtoModuleName = moduleMappings.moduleName(forFile: file) {
+      println("import \(serviceProtoModuleName)")
+    }
+    for importedProtoModuleName in moduleMappings.neededModules(forFile: file) ?? [] {
+      println("import \(importedProtoModuleName)")
+    }
+    println()
+
+    if options.generateClient {
+      for service in file.services {
+        self.service = service
+        printClient()
+      }
+    }
+    println()
+
+    if options.generateServer {
+      for service in file.services {
+        self.service = service
+        printServer()
+      }
+    }
+  }
+}

+ 17 - 0
Sources/protoc-gen-grpc-swift/README.md

@@ -0,0 +1,17 @@
+# Swift gRPC Plugin
+
+This directory contains the Swift gRPC plugin for `protoc`,
+the Protocol Buffer Compiler.
+
+It is built with the Swift Package Manager and the included
+Makefile. The resulting binary is named `protoc-gen-grpc-swift`
+and can be called from `protoc` by adding the `--grpc-swift_out`
+command-line option and `--plugin` option. For example, here's an
+invocation from the Makefile:
+
+		protoc ../Examples/Echo/echo.proto --proto_path=../Examples/Echo --plugin=./protoc-gen-grpc-swift --grpc-swift_out=.
+
+The Swift gRPC plugin can be installed by placing the
+`protoc-gen-grpc-swift` binary into one of the directories in your
+path.  Specifying `--grpc-swift_out` to `protoc` will automatically
+search the `PATH` environment variable for this binary.

+ 39 - 0
Sources/protoc-gen-grpc-swift/StreamingType.swift

@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+import SwiftProtobufPluginLibrary
+
+internal enum StreamingType {
+  case unary
+  case clientStreaming
+  case serverStreaming
+  case bidirectionalStreaming
+}
+
+internal func streamingType(_ method: MethodDescriptor) -> StreamingType {
+  if method.proto.clientStreaming {
+    if method.proto.serverStreaming {
+      return .bidirectionalStreaming
+    } else {
+      return .clientStreaming
+    }
+  } else {
+    if method.proto.serverStreaming {
+      return .serverStreaming
+    } else {
+      return .unary
+    }
+  }
+}

+ 64 - 0
Sources/protoc-gen-grpc-swift/io.swift

@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+import Foundation
+
+// The I/O code below is derived from Apple's swift-protobuf project.
+// https://github.com/apple/swift-protobuf
+// BEGIN swift-protobuf derivation
+
+#if os(Linux)
+  import Glibc
+#else
+  import Darwin.C
+#endif
+
+enum PluginError: Error {
+  /// Raised for any errors reading the input
+  case readFailure
+}
+
+// Alias clib's write() so Stdout.write(bytes:) can call it.
+private let _write = write
+
+class Stdin {
+  static func readall() throws -> Data {
+    let fd: Int32 = 0
+    let buffSize = 32
+    var buff = [UInt8]()
+    while true {
+      var fragment = [UInt8](repeating: 0, count: buffSize)
+      let count = read(fd, &fragment, buffSize)
+      if count < 0 {
+        throw PluginError.readFailure
+      }
+      if count < buffSize {
+        buff += fragment[0..<count]
+        return Data(buff)
+      }
+      buff += fragment
+    }
+  }
+}
+
+class Stdout {
+  static func write(bytes: Data) {
+    bytes.withUnsafeBytes { (p: UnsafeRawBufferPointer) -> Void in
+      _ = _write(1, p.baseAddress, p.count)
+    }
+  }
+}
+
+// END swift-protobuf derivation

+ 127 - 0
Sources/protoc-gen-grpc-swift/main.swift

@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+import Foundation
+import SwiftProtobuf
+import SwiftProtobufPluginLibrary
+
+func Log(_ message: String) {
+  FileHandle.standardError.write((message + "\n").data(using: .utf8)!)
+}
+
+// from apple/swift-protobuf/Sources/protoc-gen-swift/StringUtils.swift
+func splitPath(pathname: String) -> (dir: String, base: String, suffix: String) {
+  var dir = ""
+  var base = ""
+  var suffix = ""
+  #if swift(>=3.2)
+    let pathnameChars = pathname
+  #else
+    let pathnameChars = pathname.characters
+  #endif
+  for c in pathnameChars {
+    if c == "/" {
+      dir += base + suffix + String(c)
+      base = ""
+      suffix = ""
+    } else if c == "." {
+      base += suffix
+      suffix = String(c)
+    } else {
+      suffix += String(c)
+    }
+  }
+  #if swift(>=3.2)
+    let validSuffix = suffix.isEmpty || suffix.first == "."
+  #else
+    let validSuffix = suffix.isEmpty || suffix.characters.first == "."
+  #endif
+  if !validSuffix {
+    base += suffix
+    suffix = ""
+  }
+  return (dir: dir, base: base, suffix: suffix)
+}
+
+enum FileNaming: String {
+  case FullPath
+  case PathToUnderscores
+  case DropPath
+}
+
+func outputFileName(component: String, fileDescriptor: FileDescriptor, fileNamingOption: FileNaming) -> String {
+  let ext = "." + component + ".swift"
+  let pathParts = splitPath(pathname: fileDescriptor.name)
+  switch fileNamingOption {
+  case .FullPath:
+    return pathParts.dir + pathParts.base + ext
+  case .PathToUnderscores:
+    let dirWithUnderscores =
+      pathParts.dir.replacingOccurrences(of: "/", with: "_")
+    return dirWithUnderscores + pathParts.base + ext
+  case .DropPath:
+    return pathParts.base + ext
+  }
+}
+
+var generatedFiles: [String: Int] = [:]
+
+func uniqueOutputFileName(component: String, fileDescriptor: FileDescriptor, fileNamingOption: FileNaming) -> String {
+  let defaultName = outputFileName(component: component, fileDescriptor: fileDescriptor, fileNamingOption: fileNamingOption)
+  if let count = generatedFiles[defaultName] {
+    generatedFiles[defaultName] = count + 1
+    return outputFileName(component: "\(count)." + component, fileDescriptor: fileDescriptor, fileNamingOption: fileNamingOption)
+  } else {
+    generatedFiles[defaultName] = 1
+    return defaultName
+  }
+}
+
+func main() throws {
+
+  // initialize responses
+  var response = Google_Protobuf_Compiler_CodeGeneratorResponse()
+
+  // read plugin input
+  let rawRequest = try Stdin.readall()
+  let request = try Google_Protobuf_Compiler_CodeGeneratorRequest(serializedData: rawRequest)
+
+  let options = try GeneratorOptions(parameter: request.parameter)
+
+  // Build the SwiftProtobufPluginLibrary model of the plugin input
+  let descriptorSet = DescriptorSet(protos: request.protoFile)
+
+  // process each .proto file separately
+  for fileDescriptor in descriptorSet.files {
+    if fileDescriptor.services.count > 0 {
+      let grpcFileName = uniqueOutputFileName(component: "grpc", fileDescriptor: fileDescriptor, fileNamingOption: options.fileNaming)
+      let grpcGenerator = Generator(fileDescriptor, options: options)
+      var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
+      grpcFile.name = grpcFileName
+      grpcFile.content = grpcGenerator.code
+      response.file.append(grpcFile)
+    }
+  }
+
+  // return everything to the caller
+  let serializedResponse = try response.serializedData()
+  Stdout.write(bytes: serializedResponse)
+}
+
+do {
+  try main()
+} catch {
+  Log("ERROR: \(error)")
+}

+ 140 - 0
Sources/protoc-gen-grpc-swift/options.swift

@@ -0,0 +1,140 @@
+/*
+ * Copyright 2017, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+import Foundation
+import SwiftProtobufPluginLibrary
+
+enum GenerationError: Error {
+  /// Raised when parsing the parameter string and found an unknown key
+  case unknownParameter(name: String)
+  /// Raised when a parameter was giving an invalid value
+  case invalidParameterValue(name: String, value: String)
+  /// Raised to wrap another error but provide a context message.
+  case wrappedError(message: String, error: Error)
+
+  var localizedDescription: String {
+    switch self {
+    case .unknownParameter(let name):
+      return "Unknown generation parameter '\(name)'"
+    case .invalidParameterValue(let name, let value):
+      return "Unknown value for generation parameter '\(name)': '\(value)'"
+    case .wrappedError(let message, let error):
+      return "\(message): \(error.localizedDescription)"
+    }
+  }
+}
+
+final class GeneratorOptions {
+  enum Visibility: String {
+    case `internal` = "Internal"
+    case `public` = "Public"
+
+    var sourceSnippet: String {
+      switch self {
+      case .internal:
+        return "internal"
+      case .public:
+        return "public"
+      }
+    }
+  }
+
+  private(set) var visibility = Visibility.internal
+  private(set) var generateServer = true
+  private(set) var generateClient = true
+  private(set) var protoToModuleMappings = ProtoFileToModuleMappings()
+  private(set) var fileNaming = FileNaming.FullPath
+  private(set) var extraModuleImports: [String] = []
+
+  init(parameter: String?) throws {
+    for pair in GeneratorOptions.parseParameter(string: parameter) {
+      switch pair.key {
+      case "Visibility":
+        if let value = Visibility(rawValue: pair.value) {
+          visibility = value
+        } else {
+          throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+        }
+
+      case "Server":
+        if let value = Bool(pair.value) {
+          generateServer = value
+        } else {
+          throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+        }
+
+      case "Client":
+        if let value = Bool(pair.value) {
+          generateClient = value
+        } else {
+          throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+        }
+
+      case "ProtoPathModuleMappings":
+        if !pair.value.isEmpty {
+          do {
+            protoToModuleMappings = try ProtoFileToModuleMappings(path: pair.value)
+          } catch let e {
+            throw GenerationError.wrappedError(
+              message: "Parameter 'ProtoPathModuleMappings=\(pair.value)'",
+              error: e)
+          }
+        }
+
+      case "FileNaming":
+        if let value = FileNaming(rawValue: pair.value) {
+          fileNaming = value
+        } else {
+          throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+        }
+
+      case "ExtraModuleImports":
+        if !pair.value.isEmpty {
+          extraModuleImports.append(pair.value)
+        } else {
+          throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+        }
+
+      default:
+        throw GenerationError.unknownParameter(name: pair.key)
+      }
+    }
+  }
+
+  static func parseParameter(string: String?) -> [(key: String, value: String)] {
+    guard let string = string, !string.isEmpty else {
+      return []
+    }
+    let parts = string.components(separatedBy: ",")
+
+    // Partitions the string into the section before the = and after the =
+    let result = parts.map { string -> (key: String, value: String) in
+
+      // Finds the equal sign and exits early if none
+      guard let index = string.range(of: "=")?.lowerBound else {
+        return (string, "")
+      }
+
+      // Creates key/value pair and trims whitespace
+      let key = string[..<index]
+        .trimmingCharacters(in: .whitespacesAndNewlines)
+      let value = string[string.index(after: index)...]
+        .trimmingCharacters(in: .whitespacesAndNewlines)
+
+      return (key: key, value: value)
+    }
+    return result
+  }
+}