Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,26 +133,35 @@ protoc \

#### Generator options

| Name | Possible Values | Default | Description |
|---------------------------|---------------------------------------------|------------|----------------------------------------------------------|
| `Visibility` | `Public`, `Package`, `Internal` | `Internal` | Access level for generated stubs |
| `Server` | `True`, `False` | `True` | Generate server stubs |
| `Client` | `True`, `False` | `True` | Generate client stubs |
| `FileNaming` | `FullPath`, `PathToUnderscores`, `DropPath` | `FullPath` | How generated source files should be named. (See below.) |
| `ProtoPathModuleMappings` | | | Path to module map `.asciipb` file. (See below.) |
| `UseAccessLevelOnImports` | `True`, `False` | `False` | Whether imports should have explicit access levels. |

The `FileNaming` option has three possible values, for an input of `foo/bar/baz.proto` the following
| Name | Possible Values | Default | Description |
|---------------------------|---------------------------------------------|-----------------|----------------------------------------------------------|
| `Visibility` | `Public`, `Package`, `Internal` | `Internal` | Access level for generated stubs |
| `Server` | `True`, `False` | `True` | Generate server stubs |
| `Client` | `True`, `False` | `True` | Generate client stubs |
| `FileNaming` | `FullPath`, `PathToUnderscores`, `DropPath` | `FullPath` | How generated source files should be named. † |
| `ProtoPathModuleMappings` | | | Path to module map `.asciipb` file. ‡ |
| `UseAccessLevelOnImports` | `True`, `False` | `False` | Whether imports should have explicit access levels. |
| `GRPCModuleName` | | `GRPCCore` | The name of the `GRPCCore` module. |
| `GRPCProtobufModuleName` | | `GRPCProtobuf` | The name of the `GRPCProtobuf` module. |
| `SwiftProtobufModuleName` | | `SwiftProtobuf` | The name of the `SwiftProtobuf` module. |
| `Availability` | String, in the form `OS Version` | | Platform availability to use in generated code. § |

† The `FileNaming` option has three possible values, for an input of `foo/bar/baz.proto` the following
output file will be generated:
- `FullPath`: `foo/bar/baz.grpc.swift`.
- `PathToUnderscores`: `foo_bar_baz.grpc.swift`
- `DropPath`: `baz.grpc.swift`

The code generator assumes all inputs are generated into the same module, `ProtoPathModuleMappings`
The code generator assumes all inputs are generated into the same module, `ProtoPathModuleMappings`
allows you to specify a mapping from `.proto` files to the Swift module they are generated in. This
allows the code generator to add appropriate imports to your generated stubs. This is described in
more detail in the [SwiftProtobuf documentation](https://github.com/apple/swift-protobuf/blob/main/Documentation/PLUGIN.md).

§ If unspecified the following availability is used: macOS 15, iOS 18, tvOS 18,
watchOS 11, visionOS 2. The `Availability` option may be specified multiple
times, where each value is a space delimited pair of platform and version, e.g.
`Availability=macOS 15.0`.

#### Building the protoc plugin

> The version of `protoc-gen-grpc-swift` you use mustn't be newer than the version of
Expand Down
14 changes: 13 additions & 1 deletion Sources/GRPCProtobufCodeGen/ProtobufCodeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ package struct ProtobufCodeGenerator {
package func generateCode(
fileDescriptor: FileDescriptor,
protoFileModuleMappings: ProtoFileToModuleMappings,
extraModuleImports: [String]
extraModuleImports: [String],
availabilityOverrides: [(os: String, version: String)] = []
) throws -> String {
let parser = ProtobufCodeGenParser(
protoFileModuleMappings: protoFileModuleMappings,
Expand All @@ -46,6 +47,17 @@ package struct ProtobufCodeGenerator {
indentation: self.config.indentation
)
codeGeneratorConfig.grpcCoreModuleName = self.config.moduleNames.grpcCore

if availabilityOverrides.isEmpty {
codeGeneratorConfig.availability = .default
} else {
codeGeneratorConfig.availability = .custom(
availabilityOverrides.map { (os, version) in
.init(os: os, version: version)
}
)
}

let codeGenerator = GRPCCodeGen.CodeGenerator(config: codeGeneratorConfig)

let codeGenerationRequest = try parser.parse(descriptor: fileDescriptor)
Expand Down
3 changes: 2 additions & 1 deletion Sources/protoc-gen-grpc-swift/GenerateGRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ final class GenerateGRPC: SwiftProtobufPluginLibrary.CodeGenerator {
let contents = try fileGenerator.generateCode(
fileDescriptor: descriptor,
protoFileModuleMappings: options.protoToModuleMappings,
extraModuleImports: options.extraModuleImports
extraModuleImports: options.extraModuleImports,
availabilityOverrides: options.availabilityOverrides
)

try outputs.add(fileName: fileName, contents: contents)
Expand Down
13 changes: 13 additions & 0 deletions Sources/protoc-gen-grpc-swift/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct GeneratorOptions {
private(set) var protoToModuleMappings = ProtoFileToModuleMappings()
private(set) var fileNaming = FileNaming.fullPath
private(set) var extraModuleImports: [String] = []
private(set) var availabilityOverrides: [(os: String, version: String)] = []

private(set) var config: ProtobufCodeGenerator.Config = .defaults

Expand Down Expand Up @@ -130,6 +131,18 @@ struct GeneratorOptions {
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
}

case "Availability":
if !pair.value.isEmpty {
let parts = pair.value.split(separator: " ", maxSplits: 1)
if parts.count == 2 {
self.availabilityOverrides.append((os: String(parts[0]), version: String(parts[1])))
} else {
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
}
} else {
throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
}

case "ReflectionData":
throw GenerationError.unsupportedParameter(
name: pair.key,
Expand Down
57 changes: 45 additions & 12 deletions Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,38 @@ struct ProtobufCodeGeneratorTests {
static let descriptorSetName = "test-service"
static let fileDescriptorName = "test-service"

@Test("Generate", arguments: [CodeGenerator.Config.AccessLevel.internal])
func generate(accessLevel: GRPCCodeGen.CodeGenerator.Config.AccessLevel) throws {
enum Availability {
case `default`
case fooOS

var override: [(String, String)] {
switch self {
case .default:
return []
case .fooOS:
return [("fooOS", "42.0")]
}
}

var expected: String {
switch self {
case .default:
return "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"
case .fooOS:
return "fooOS 42.0"
}
}
}

@Test(
"Generate",
arguments: [CodeGenerator.Config.AccessLevel.internal],
[Availability.default, Availability.fooOS]
)
func generate(
accessLevel: GRPCCodeGen.CodeGenerator.Config.AccessLevel,
availability: Availability
) throws {
var config = ProtobufCodeGenerator.Config.defaults
config.accessLevel = accessLevel
config.indentation = 2
Expand All @@ -44,10 +74,13 @@ struct ProtobufCodeGeneratorTests {
fatalError()
}

let expectedAvailability = availability.expected

let generated = try generator.generateCode(
fileDescriptor: Self.fileDescriptor,
protoFileModuleMappings: ProtoFileToModuleMappings(),
extraModuleImports: []
extraModuleImports: [],
availabilityOverrides: availability.override
)

let expected = """
Expand All @@ -70,7 +103,7 @@ struct ProtobufCodeGeneratorTests {
// MARK: - test.TestService

/// Namespace containing generated types for the "test.TestService" service.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
\(access) enum Test_TestService {
/// Service descriptor for the "test.TestService" service.
\(access) static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "test.TestService")
Expand Down Expand Up @@ -134,15 +167,15 @@ struct ProtobufCodeGeneratorTests {
}
}

@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
extension GRPCCore.ServiceDescriptor {
/// Service descriptor for the "test.TestService" service.
\(access) static let test_TestService = GRPCCore.ServiceDescriptor(fullyQualifiedService: "test.TestService")
}

// MARK: test.TestService (server)

@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
extension Test_TestService {
/// Streaming variant of the service protocol for the "test.TestService" service.
///
Expand Down Expand Up @@ -404,7 +437,7 @@ struct ProtobufCodeGeneratorTests {
}

// Default implementation of 'registerMethods(with:)'.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
extension Test_TestService.StreamingServiceProtocol {
\(access) func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
router.registerHandler(
Expand Down Expand Up @@ -455,7 +488,7 @@ struct ProtobufCodeGeneratorTests {
}

// Default implementation of streaming methods from 'StreamingServiceProtocol'.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
extension Test_TestService.ServiceProtocol {
\(access) func unary(
request: GRPCCore.StreamingServerRequest<Test_TestInput>,
Expand Down Expand Up @@ -492,7 +525,7 @@ struct ProtobufCodeGeneratorTests {
}

// Default implementation of methods from 'ServiceProtocol'.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
extension Test_TestService.SimpleServiceProtocol {
\(access) func unary(
request: GRPCCore.ServerRequest<Test_TestInput>,
Expand Down Expand Up @@ -557,7 +590,7 @@ struct ProtobufCodeGeneratorTests {

// MARK: test.TestService (client)

@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
extension Test_TestService {
/// Generated client protocol for the "test.TestService" service.
///
Expand Down Expand Up @@ -816,7 +849,7 @@ struct ProtobufCodeGeneratorTests {
}

// Helpers providing default arguments to 'ClientProtocol' methods.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
extension Test_TestService.ClientProtocol {
/// Call the "Unary" method.
///
Expand Down Expand Up @@ -932,7 +965,7 @@ struct ProtobufCodeGeneratorTests {
}

// Helpers providing sugared APIs for 'ClientProtocol' methods.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@available(\(expectedAvailability), *)
extension Test_TestService.ClientProtocol {
/// Call the "Unary" method.
///
Expand Down
Loading