Skip to content

Commit 09c46b0

Browse files
Tool that generates serialised file descriptor protos (#1654)
Motivation: In order to implement the reflection service we need to be able to generate serialised file descriptor protos of the services and messages that a server offers. Modifications: Added an option for protoc-gen-grpc-swift that enables generating a binary file containing a base64 encoded representation of the serialized file descriptor proto of the specified proto file. Result: This change enables the generation of serialised file descriptor protos that will be useful in the implementation of the reflection service.
1 parent d576a74 commit 09c46b0

File tree

6 files changed

+150
-18
lines changed

6 files changed

+150
-18
lines changed

Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,20 @@ ${NORMALIZATION_GRPC}: ${NORMALIZATION_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
117117
.PHONY:
118118
generate-normalization: ${NORMALIZATION_PB} ${NORMALIZATION_GRPC}
119119

120+
SERIALIZATION_GRPC_REFLECTION=Tests/GRPCTests/Codegen/Serialization/echo.grpc.reflection.txt
121+
122+
# For serialization we'll set the ReflectionData option to true.
123+
${SERIALIZATION_GRPC_REFLECTION}: ${ECHO_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
124+
protoc $< \
125+
--proto_path=$(dir $<) \
126+
--plugin=${PROTOC_GEN_GRPC_SWIFT} \
127+
--grpc-swift_opt=Client=false,Server=false,ReflectionData=true \
128+
--grpc-swift_out=$(dir ${SERIALIZATION_GRPC_REFLECTION})
129+
130+
# Generates binary file containing the serialized file descriptor proto for the Serialization test
131+
.PHONY:
132+
generate-reflection-data: ${SERIALIZATION_GRPC_REFLECTION}
133+
120134
### Testing ####################################################################
121135

122136
# Normal test suite.

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ extension Target {
205205
),
206206
exclude: [
207207
"Codegen/Normalization/normalization.proto",
208+
"Codegen/Serialization/echo.grpc.reflection.txt",
208209
]
209210
)
210211

Sources/protoc-gen-grpc-swift/main.swift

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ enum FileNaming: String {
6464
func outputFileName(
6565
component: String,
6666
fileDescriptor: FileDescriptor,
67-
fileNamingOption: FileNaming
67+
fileNamingOption: FileNaming,
68+
extension: String
6869
) -> String {
69-
let ext = "." + component + ".swift"
70+
let ext = "." + component + "." + `extension`
7071
let pathParts = splitPath(pathname: fileDescriptor.name)
7172
switch fileNamingOption {
7273
case .FullPath:
@@ -84,19 +85,22 @@ func uniqueOutputFileName(
8485
component: String,
8586
fileDescriptor: FileDescriptor,
8687
fileNamingOption: FileNaming,
87-
generatedFiles: inout [String: Int]
88+
generatedFiles: inout [String: Int],
89+
extension: String = "swift"
8890
) -> String {
8991
let defaultName = outputFileName(
9092
component: component,
9193
fileDescriptor: fileDescriptor,
92-
fileNamingOption: fileNamingOption
94+
fileNamingOption: fileNamingOption,
95+
extension: `extension`
9396
)
9497
if let count = generatedFiles[defaultName] {
9598
generatedFiles[defaultName] = count + 1
9699
return outputFileName(
97100
component: "\(count)." + component,
98101
fileDescriptor: fileDescriptor,
99-
fileNamingOption: fileNamingOption
102+
fileNamingOption: fileNamingOption,
103+
extension: `extension`
100104
)
101105
} else {
102106
generatedFiles[defaultName] = 1
@@ -136,19 +140,38 @@ func main(args: [String]) throws {
136140

137141
// Only generate output for services.
138142
for name in request.fileToGenerate {
139-
if let fileDescriptor = descriptorSet.fileDescriptor(named: name),
140-
!fileDescriptor.services.isEmpty {
141-
let grpcFileName = uniqueOutputFileName(
142-
component: "grpc",
143-
fileDescriptor: fileDescriptor,
144-
fileNamingOption: options.fileNaming,
145-
generatedFiles: &generatedFiles
146-
)
147-
let grpcGenerator = Generator(fileDescriptor, options: options)
148-
var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
149-
grpcFile.name = grpcFileName
150-
grpcFile.content = grpcGenerator.code
151-
response.file.append(grpcFile)
143+
if let fileDescriptor = descriptorSet.fileDescriptor(named: name) {
144+
if (options.generateReflectionData) {
145+
var binaryFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
146+
let binaryFileName = uniqueOutputFileName(
147+
component: "grpc.reflection",
148+
fileDescriptor: fileDescriptor,
149+
fileNamingOption: options.fileNaming,
150+
generatedFiles: &generatedFiles,
151+
extension: "txt"
152+
)
153+
let serializedFileDescriptorProto = try fileDescriptor.proto.serializedData()
154+
.base64EncodedString()
155+
binaryFile.name = binaryFileName
156+
binaryFile.content = serializedFileDescriptorProto
157+
response.file.append(binaryFile)
158+
}
159+
if (
160+
!fileDescriptor.services
161+
.isEmpty && (options.generateClient || options.generateServer)
162+
) {
163+
var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
164+
let grpcFileName = uniqueOutputFileName(
165+
component: "grpc",
166+
fileDescriptor: fileDescriptor,
167+
fileNamingOption: options.fileNaming,
168+
generatedFiles: &generatedFiles
169+
)
170+
let grpcGenerator = Generator(fileDescriptor, options: options)
171+
grpcFile.name = grpcFileName
172+
grpcFile.content = grpcGenerator.code
173+
response.file.append(grpcFile)
174+
}
152175
}
153176
}
154177

Sources/protoc-gen-grpc-swift/options.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ final class GeneratorOptions {
6464
private(set) var extraModuleImports: [String] = []
6565
private(set) var gRPCModuleName = "GRPC"
6666
private(set) var swiftProtobufModuleName = "SwiftProtobuf"
67+
private(set) var generateReflectionData = false
6768

6869
init(parameter: String?) throws {
6970
for pair in GeneratorOptions.parseParameter(string: parameter) {
@@ -143,6 +144,13 @@ final class GeneratorOptions {
143144
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
144145
}
145146

147+
case "ReflectionData":
148+
if let value = Bool(pair.value) {
149+
self.generateReflectionData = value
150+
} else {
151+
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
152+
}
153+
146154
default:
147155
throw GenerationError.unknownParameter(name: pair.key)
148156
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2023, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
import GRPC
19+
import SwiftProtobuf
20+
import XCTest
21+
22+
final class SerializationTests: GRPCTestCase {
23+
var fileDescriptorProto: Google_Protobuf_FileDescriptorProto!
24+
25+
override func setUp() {
26+
super.setUp()
27+
let binaryFileURL = URL(fileURLWithPath: #filePath)
28+
.deletingLastPathComponent().appendingPathComponent("echo.grpc.reflection.txt")
29+
let base64EncodedData = try! Data(contentsOf: binaryFileURL)
30+
let binaryData = Data(base64Encoded: base64EncodedData)!
31+
self
32+
.fileDescriptorProto =
33+
try! Google_Protobuf_FileDescriptorProto(serializedData: binaryData)
34+
}
35+
36+
func testFileDescriptorMetadata() throws {
37+
let name = self.fileDescriptorProto.name
38+
XCTAssertEqual(name, "echo.proto")
39+
40+
let syntax = self.fileDescriptorProto.syntax
41+
XCTAssertEqual(syntax, "proto3")
42+
43+
let package = self.fileDescriptorProto.package
44+
XCTAssertEqual(package, "echo")
45+
}
46+
47+
func testFileDescriptorMessages() {
48+
let messages = self.fileDescriptorProto.messageType
49+
XCTAssertEqual(messages.count, 2)
50+
for message in messages {
51+
XCTAssert((message.name == "EchoRequest") || (message.name == "EchoResponse"))
52+
XCTAssertEqual(message.field.count, 1)
53+
XCTAssertEqual(message.field.first!.name, "text")
54+
XCTAssert(message.field.first!.hasNumber)
55+
}
56+
}
57+
58+
func testFileDescriptorServices() {
59+
let services = self.fileDescriptorProto.service
60+
XCTAssertEqual(services.count, 1)
61+
XCTAssertEqual(self.fileDescriptorProto.service.first!.method.count, 4)
62+
for method in self.fileDescriptorProto.service.first!.method {
63+
switch method.name {
64+
case "Get":
65+
XCTAssertEqual(method.inputType, ".echo.EchoRequest")
66+
XCTAssertEqual(method.outputType, ".echo.EchoResponse")
67+
case "Expand":
68+
XCTAssertEqual(method.inputType, ".echo.EchoRequest")
69+
XCTAssertEqual(method.outputType, ".echo.EchoResponse")
70+
XCTAssert(method.serverStreaming)
71+
case "Collect":
72+
XCTAssertEqual(method.inputType, ".echo.EchoRequest")
73+
XCTAssertEqual(method.outputType, ".echo.EchoResponse")
74+
XCTAssert(method.clientStreaming)
75+
case "Update":
76+
XCTAssertEqual(method.inputType, ".echo.EchoRequest")
77+
XCTAssertEqual(method.outputType, ".echo.EchoResponse")
78+
XCTAssert(method.clientStreaming)
79+
XCTAssert(method.serverStreaming)
80+
default:
81+
XCTFail("The method name is incorrect.")
82+
}
83+
}
84+
}
85+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CgplY2hvLnByb3RvEgRlY2hvIiEKC0VjaG9SZXF1ZXN0EhIKBHRleHQYASABKAlSBHRleHQiIgoMRWNob1Jlc3BvbnNlEhIKBHRleHQYASABKAlSBHRleHQy2AEKBEVjaG8SLgoDR2V0EhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgASMwoGRXhwYW5kEhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgAwARI0CgdDb2xsZWN0EhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgAoARI1CgZVcGRhdGUSES5lY2hvLkVjaG9SZXF1ZXN0GhIuZWNoby5FY2hvUmVzcG9uc2UiACgBMAFK/QoKBhIEDgAoAQrCBAoBDBIDDgASMrcEIENvcHlyaWdodCAoYykgMjAxNSwgR29vZ2xlIEluYy4KCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCggKAQISAxAADQoKCgIGABIEEgAeAQoKCgMGAAESAxIIDAo4CgQGAAIAEgMUAjAaKyBJbW1lZGlhdGVseSByZXR1cm5zIGFuIGVjaG8gb2YgYSByZXF1ZXN0LgoKDAoFBgACAAESAxQGCQoMCgUGAAIAAhIDFAoVCgwKBQYAAgADEgMUICwKWQoEBgACARIDFwI6GkwgU3BsaXRzIGEgcmVxdWVzdCBpbnRvIHdvcmRzIGFuZCByZXR1cm5zIGVhY2ggd29yZCBpbiBhIHN0cmVhbSBvZiBtZXNzYWdlcy4KCgwKBQYAAgEBEgMXBgwKDAoFBgACAQISAxcNGAoMCgUGAAIBBhIDFyMpCgwKBQYAAgEDEgMXKjYKYgoEBgACAhIDGgI7GlUgQ29sbGVjdHMgYSBzdHJlYW0gb2YgbWVzc2FnZXMgYW5kIHJldHVybnMgdGhlbSBjb25jYXRlbmF0ZWQgd2hlbiB0aGUgY2FsbGVyIGNsb3Nlcy4KCgwKBQYAAgIBEgMaBg0KDAoFBgACAgUSAxoOFAoMCgUGAAICAhIDGhUgCgwKBQYAAgIDEgMaKzcKTQoEBgACAxIDHQJBGkAgU3RyZWFtcyBiYWNrIG1lc3NhZ2VzIGFzIHRoZXkgYXJlIHJlY2VpdmVkIGluIGFuIGlucHV0IHN0cmVhbS4KCgwKBQYAAgMBEgMdBgwKDAoFBgACAwUSAx0NEwoMCgUGAAIDAhIDHRQfCgwKBQYAAgMGEgMdKjAKDAoFBgACAwMSAx0xPQoKCgIEABIEIAAjAQoKCgMEAAESAyAIEwoyCgQEAAIAEgMiAhIaJSBUaGUgdGV4dCBvZiBhIG1lc3NhZ2UgdG8gYmUgZWNob2VkLgoKDAoFBAACAAUSAyICCAoMCgUEAAIAARIDIgkNCgwKBQQAAgADEgMiEBEKCgoCBAESBCUAKAEKCgoDBAEBEgMlCBQKLAoEBAECABIDJwISGh8gVGhlIHRleHQgb2YgYW4gZWNobyByZXNwb25zZS4KCgwKBQQBAgAFEgMnAggKDAoFBAECAAESAycJDQoMCgUEAQIAAxIDJxARYgZwcm90bzM=

0 commit comments

Comments
 (0)