Skip to content

Commit

Permalink
Avoid generating multiple conformances to GRPCProtobufPayload (#740)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
glbrntt authored Mar 10, 2020
1 parent 2048948 commit c4dc6a3
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Sources/Examples/Echo/Model/echo.grpc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ extension Echo_EchoProvider {
}


/// Provides conformance to `GRPCPayload` for the request and response messages
// Provides conformance to `GRPCPayload` for request and response messages
extension Echo_EchoRequest: GRPCProtobufPayload {}
extension Echo_EchoResponse: GRPCProtobufPayload {}

2 changes: 1 addition & 1 deletion Sources/Examples/HelloWorld/Model/helloworld.grpc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ extension Helloworld_GreeterProvider {
}


/// Provides conformance to `GRPCPayload` for the request and response messages
// Provides conformance to `GRPCPayload` for request and response messages
extension Helloworld_HelloRequest: GRPCProtobufPayload {}
extension Helloworld_HelloReply: GRPCProtobufPayload {}

2 changes: 1 addition & 1 deletion Sources/Examples/RouteGuide/Model/route_guide.grpc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ extension Routeguide_RouteGuideProvider {
}


/// Provides conformance to `GRPCPayload` for the request and response messages
// Provides conformance to `GRPCPayload` for request and response messages
extension Routeguide_Point: GRPCProtobufPayload {}
extension Routeguide_Feature: GRPCProtobufPayload {}
extension Routeguide_Rectangle: GRPCProtobufPayload {}
Expand Down
40 changes: 40 additions & 0 deletions Sources/protoc-gen-grpc-swift/Generator-Conformance.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
27 changes: 5 additions & 22 deletions Sources/protoc-gen-grpc-swift/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
}
13 changes: 9 additions & 4 deletions Sources/protoc-gen-grpc-swift/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ func uniqueOutputFileName(component: String, fileDescriptor: FileDescriptor, fil
}

func main() throws {

// initialize responses
var response = Google_Protobuf_Compiler_CodeGeneratorResponse()

Expand All @@ -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)
}
}

Expand Down

0 comments on commit c4dc6a3

Please sign in to comment.