Skip to content

Commit 53396af

Browse files
[CodeGen Protobuf support] protoc-gen-grpc-swift v2 (#1778)
Motivation: We want to have a protoc plugin for grpc-swift v2 which builds on top of the code generator pipeline. Modifications: - Created the ProtobufCodeGenerator struct that encapsulates all setps needed to generate code for a given file descriptor - Created tests for the ProtobufCodeGenerator - added a new option for selecting v2 for the plugin - modified main() accordingly Result: The protoc plugin for grpc-swift will support v2 and will use the CodeGen library.
1 parent ff707e0 commit 53396af

File tree

7 files changed

+517
-8
lines changed

7 files changed

+517
-8
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ extension Target {
236236
dependencies: [
237237
.protobuf,
238238
.protobufPluginLibrary,
239-
.grpcCodeGen
239+
.grpcCodeGen,
240+
.grpcProtobufCodeGen
240241
],
241242
exclude: [
242243
"README.md",

Sources/GRPCCodeGen/SourceGenerator.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ public struct SourceGenerator: Sendable {
3535
/// Whether or not server code should be generated.
3636
public var server: Bool
3737

38+
public init(accessLevel: AccessLevel, client: Bool, server: Bool, indentation: Int = 4) {
39+
self.accessLevel = accessLevel
40+
self.indentation = indentation
41+
self.client = client
42+
self.server = server
43+
}
44+
3845
/// The possible access levels for the generated code.
3946
public struct AccessLevel: Sendable, Hashable {
4047
internal var level: Level
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2024, 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 GRPCCodeGen
18+
import SwiftProtobufPluginLibrary
19+
20+
public struct ProtobufCodeGenerator {
21+
internal var configuration: SourceGenerator.Configuration
22+
23+
public init(configuration: SourceGenerator.Configuration) {
24+
self.configuration = configuration
25+
}
26+
27+
public func generateCode(from fileDescriptor: FileDescriptor) throws -> String {
28+
let parser = ProtobufCodeGenParser()
29+
let sourceGenerator = SourceGenerator(configuration: self.configuration)
30+
31+
let codeGenerationRequest = try parser.parse(input: fileDescriptor)
32+
let sourceFile = try sourceGenerator.generate(codeGenerationRequest)
33+
return sourceFile.contents
34+
}
35+
}

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616
import Foundation
17+
import GRPCCodeGen
18+
import GRPCProtobufCodeGen
1719
import SwiftProtobuf
1820
import SwiftProtobufPluginLibrary
1921

@@ -166,9 +168,16 @@ func main(args: [String]) throws {
166168
fileNamingOption: options.fileNaming,
167169
generatedFiles: &generatedFiles
168170
)
169-
let grpcGenerator = Generator(fileDescriptor, options: options)
171+
if options.v2 {
172+
let grpcGenerator = ProtobufCodeGenerator(
173+
configuration: SourceGenerator.Configuration(options: options)
174+
)
175+
grpcFile.content = try grpcGenerator.generateCode(from: fileDescriptor)
176+
} else {
177+
let grpcGenerator = Generator(fileDescriptor, options: options)
178+
grpcFile.content = grpcGenerator.code
179+
}
170180
grpcFile.name = grpcFileName
171-
grpcFile.content = grpcGenerator.code
172181
response.file.append(grpcFile)
173182
}
174183
}
@@ -184,3 +193,22 @@ do {
184193
} catch {
185194
Log("ERROR: \(error)")
186195
}
196+
197+
extension SourceGenerator.Configuration {
198+
init(options: GeneratorOptions) {
199+
let accessLevel: SourceGenerator.Configuration.AccessLevel
200+
switch options.visibility {
201+
case .internal:
202+
accessLevel = .internal
203+
case .package:
204+
accessLevel = .package
205+
case .public:
206+
accessLevel = .public
207+
}
208+
self.init(
209+
accessLevel: accessLevel,
210+
client: options.generateClient,
211+
server: options.generateServer
212+
)
213+
}
214+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ final class GeneratorOptions {
6868
private(set) var gRPCModuleName = "GRPC"
6969
private(set) var swiftProtobufModuleName = "SwiftProtobuf"
7070
private(set) var generateReflectionData = false
71+
private(set) var v2 = false
7172

7273
init(parameter: String?) throws {
7374
for pair in GeneratorOptions.parseParameter(string: parameter) {
@@ -154,6 +155,13 @@ final class GeneratorOptions {
154155
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
155156
}
156157

158+
case "_V2":
159+
if let value = Bool(pair.value) {
160+
self.v2 = value
161+
} else {
162+
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
163+
}
164+
157165
default:
158166
throw GenerationError.unknownParameter(name: pair.key)
159167
}

Tests/GRPCProtobufCodeGenTests/ProtobufCodeGenParserTests.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,16 @@ import XCTest
2323

2424
final class ProtobufCodeGenParserTests: XCTestCase {
2525
func testParser() throws {
26+
let descriptorSet = DescriptorSet(protos: [Google_Protobuf_FileDescriptorProto.helloWorld])
27+
guard let fileDescriptor = descriptorSet.fileDescriptor(named: "helloworld.proto") else {
28+
return XCTFail(
29+
"""
30+
Could not find the file descriptor of "helloworld.proto".
31+
"""
32+
)
33+
}
2634
let parsedCodeGenRequest = try ProtobufCodeGenParser().parse(
27-
input: self.helloWorldFileDescriptor
35+
input: fileDescriptor
2836
)
2937
XCTAssertEqual(parsedCodeGenRequest.fileName, "helloworld.proto")
3038
XCTAssertEqual(
@@ -105,8 +113,10 @@ final class ProtobufCodeGenParserTests: XCTestCase {
105113
CodeGenerationRequest.Dependency(module: "GRPCProtobuf")
106114
)
107115
}
116+
}
108117

109-
var helloWorldFileDescriptor: FileDescriptor {
118+
extension Google_Protobuf_FileDescriptorProto {
119+
static var helloWorld: Google_Protobuf_FileDescriptorProto {
110120
let requestType = Google_Protobuf_DescriptorProto.with {
111121
$0.name = "HelloRequest"
112122
$0.field = [
@@ -144,7 +154,7 @@ final class ProtobufCodeGenParserTests: XCTestCase {
144154
}
145155
]
146156
}
147-
let protoDescriptor = Google_Protobuf_FileDescriptorProto.with {
157+
return Google_Protobuf_FileDescriptorProto.with {
148158
$0.name = "helloworld.proto"
149159
$0.package = "helloworld"
150160
$0.messageType = [requestType, responseType]
@@ -187,7 +197,5 @@ final class ProtobufCodeGenParserTests: XCTestCase {
187197
}
188198
$0.syntax = "proto3"
189199
}
190-
let descriptorSet = DescriptorSet(protos: [protoDescriptor])
191-
return descriptorSet.fileDescriptor(named: "helloworld.proto")!
192200
}
193201
}

0 commit comments

Comments
 (0)