Browse Source

Avoid generating multiple conformances to GRPCProtobufPayload (#740)

Motivation:

- Generated services must also add conformance to the request/response
  types that they require
- If different services rely on the same message type then this
  conformance is generated multiple times; leading to a build error

Modifications:

- Pass the observed message types between each run of the generator

Result:

- Code generated for services defined in different files with the same
  message types does not cause a build error
George Barnett 5 years ago
parent
commit
5e507e3736

+ 40 - 0
Sources/protoc-gen-grpc-swift/Generator-Conformance.swift

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020, 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 SwiftProtobuf
+import SwiftProtobufPluginLibrary
+
+extension Generator {
+  internal func printProtobufExtensions() {
+    println("// Provides conformance to `GRPCPayload` for request and response messages")
+    for service in self.file.services {
+      self.service = service
+      for method in self.service.methods {
+        self.method = method
+        self.printExtension(for: self.methodInputName)
+        self.printExtension(for: self.methodOutputName)
+      }
+      println()
+    }
+  }
+
+  private func printExtension(for messageType: String) {
+    guard !self.observedMessages.contains(messageType) else {
+      return
+    }
+    self.println("extension \(messageType): GRPCProtobufPayload {}")
+    self.observedMessages.insert(messageType)
+  }
+}

+ 5 - 22
Sources/protoc-gen-grpc-swift/Generator.swift

@@ -23,11 +23,13 @@ class Generator {
   internal var service: ServiceDescriptor! // context during generation
   internal var method: MethodDescriptor!   // context during generation
 
+  internal var observedMessages: Set<String>
   internal let protobufNamer: SwiftProtobufNamer
 
-  init(_ file: FileDescriptor, options: GeneratorOptions) {
+  init(_ file: FileDescriptor, options: GeneratorOptions, observedMessages: Set<String>) {
     self.file = file
     self.options = options
+    self.observedMessages = observedMessages
     self.printer = CodePrinter()
     self.protobufNamer = SwiftProtobufNamer(
       currentFile: file,
@@ -117,27 +119,8 @@ class Generator {
         printServer()
       }
     }
-    println()
-    printProtoBufExtensions()
-  }
-    
-  internal func printProtoBufExtensions() {
-    var writtenValues = Set<String>()
-    println("/// Provides conformance to `GRPCPayload` for the request and response messages")
-    for service in file.services {
-      self.service = service
-      for method in service.methods {
-        self.method = method
-        printExtension(for: methodInputName, typesSeen: &writtenValues)
-        printExtension(for: methodOutputName, typesSeen: &writtenValues)
-      }
-      println()
-    }
+    self.println()
+    self.printProtobufExtensions()
   }
 
-  private func printExtension(for messageType: String, typesSeen: inout Set<String>) {
-    guard !typesSeen.contains(messageType) else { return }
-    println("extension \(messageType): GRPCProtobufPayload {}")
-    typesSeen.insert(messageType)
-  }
 }

+ 9 - 4
Sources/protoc-gen-grpc-swift/main.swift

@@ -90,7 +90,6 @@ func uniqueOutputFileName(component: String, fileDescriptor: FileDescriptor, fil
 }
 
 func main() throws {
-
   // initialize responses
   var response = Google_Protobuf_Compiler_CodeGeneratorResponse()
 
@@ -103,15 +102,21 @@ func main() throws {
   // Build the SwiftProtobufPluginLibrary model of the plugin input
   let descriptorSet = DescriptorSet(protos: request.protoFile)
 
-  // process each .proto file separately
-  for fileDescriptor in descriptorSet.files {
+  // We need to generate conformance to `GRPCPayload` for request/response types. Track which
+  // types we've seen to avoid generating the conformance multiple times.
+  var observedMessages = Set<String>()
+
+  // process each .proto file in filename order in an attempt to stabilise the output (i.e. where
+  // conformance to `GRPCPayload` is generated)
+  for fileDescriptor in descriptorSet.files.sorted(by: { $0.name < $1.name }) {
     if fileDescriptor.services.count > 0 {
       let grpcFileName = uniqueOutputFileName(component: "grpc", fileDescriptor: fileDescriptor, fileNamingOption: options.fileNaming)
-      let grpcGenerator = Generator(fileDescriptor, options: options)
+      let grpcGenerator = Generator(fileDescriptor, options: options, observedMessages: observedMessages)
       var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
       grpcFile.name = grpcFileName
       grpcFile.content = grpcGenerator.code
       response.file.append(grpcFile)
+      observedMessages.formUnion(grpcGenerator.observedMessages)
     }
   }