Skip to content

Commit b18eb73

Browse files
glbrnttgjcairo
andauthored
Generate availability annotations (#2232)
Motivation: gRPC is moving from specifying the platforms in the package manifest to annotating code with availability annotations. In order to do this the generateed code must also be annotated. Modifications: - Generate appropriate annotations Result: Generated code has availability annotations --------- Co-authored-by: Gus Cairo <[email protected]>
1 parent 0d850d6 commit b18eb73

12 files changed

+170
-40
lines changed

Sources/GRPCCodeGen/CodeGenerator.swift

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public struct CodeGenerator: Sendable {
4242
public var server: Bool
4343
/// The name of the core gRPC module.
4444
public var grpcCoreModuleName: String
45+
/// The availability annotations to use on the generated code.
46+
public var availability: AvailabilityAnnotations = .default
4547

4648
/// Creates a new configuration.
4749
///
@@ -84,6 +86,50 @@ public struct CodeGenerator: Sendable {
8486
/// The generated code will have `package` access level.
8587
public static var `package`: Self { Self(level: .`package`) }
8688
}
89+
90+
// The availability that generated code is annotated with.
91+
public struct AvailabilityAnnotations: Sendable, Hashable {
92+
public struct Platform: Sendable, Hashable {
93+
/// The name of the OS, e.g. 'macOS'.
94+
public var os: String
95+
/// The version of the OS, e.g. '15.0'.
96+
public var version: String
97+
98+
public init(os: String, version: String) {
99+
self.os = os
100+
self.version = version
101+
}
102+
}
103+
104+
fileprivate enum Wrapped: Sendable, Hashable {
105+
case macOS15Aligned
106+
case custom([Platform])
107+
}
108+
109+
fileprivate var wrapped: Wrapped
110+
111+
private init(_ wrapped: Wrapped) {
112+
self.wrapped = wrapped
113+
}
114+
115+
/// Use the default availability.
116+
///
117+
/// The default platform availability is:
118+
/// - macOS 15.0
119+
/// - iOS 18.0
120+
/// - tvOS 18.0
121+
/// - watchOS 11.0
122+
/// - visionOS 2.0
123+
public static var `default`: Self {
124+
Self(.macOS15Aligned)
125+
}
126+
127+
/// Use a custom set of availability attributes.
128+
/// - Parameter platforms: Availability requirements.
129+
public static func custom(_ platforms: [Platform]) -> Self {
130+
Self(.custom(platforms))
131+
}
132+
}
87133
}
88134

89135
/// Transforms a ``CodeGenerationRequest`` object into a ``SourceFile`` object containing
@@ -100,11 +146,25 @@ public struct CodeGenerator: Sendable {
100146
accessLevelOnImports: self.config.accessLevelOnImports,
101147
client: self.config.client,
102148
server: self.config.server,
103-
grpcCoreModuleName: self.config.grpcCoreModuleName
149+
grpcCoreModuleName: self.config.grpcCoreModuleName,
150+
availability: AvailabilityDescription(self.config.availability)
104151
)
105152

106153
let sourceFile = try textRenderer.render(structured: structuredSwiftRepresentation)
107154

108155
return sourceFile
109156
}
110157
}
158+
159+
extension AvailabilityDescription {
160+
init(_ availability: CodeGenerator.Config.AvailabilityAnnotations) throws {
161+
switch availability.wrapped {
162+
case .macOS15Aligned:
163+
self = .macOS15Aligned
164+
case .custom(let platforms):
165+
self.osVersions = platforms.map {
166+
.init(os: .init(name: $0.os), version: $0.version)
167+
}
168+
}
169+
}
170+
}

Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,8 @@ struct TextBasedRenderer: RendererProtocol {
877877
renderCommentableDeclaration(comment: comment, declaration: nestedDeclaration)
878878
case let .deprecated(deprecation, nestedDeclaration):
879879
renderDeprecatedDeclaration(deprecation: deprecation, declaration: nestedDeclaration)
880+
case let .guarded(availability, nestedDeclaration):
881+
renderGuardedDeclaration(availability: availability, declaration: nestedDeclaration)
880882
case .variable(let variableDescription): renderVariable(variableDescription)
881883
case .extension(let extensionDescription): renderExtension(extensionDescription)
882884
case .struct(let structDescription): renderStruct(structDescription)

Sources/GRPCCodeGen/Internal/StructuredSwift+ServiceMetadata.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@
1414
* limitations under the License.
1515
*/
1616

17+
extension AvailabilityDescription {
18+
package static let macOS15Aligned = AvailabilityDescription(
19+
osVersions: [
20+
OSVersion(os: .macOS, version: "15.0"),
21+
OSVersion(os: .iOS, version: "18.0"),
22+
OSVersion(os: .watchOS, version: "11.0"),
23+
OSVersion(os: .tvOS, version: "18.0"),
24+
OSVersion(os: .visionOS, version: "2.0"),
25+
]
26+
)
27+
}
28+
1729
extension TypealiasDescription {
1830
/// `typealias Input = <name>`
1931
package static func methodInput(
@@ -341,6 +353,7 @@ extension [CodeBlock] {
341353
package static func serviceMetadata(
342354
accessModifier: AccessModifier? = nil,
343355
service: ServiceDescriptor,
356+
availability: AvailabilityDescription,
344357
namer: Namer = Namer()
345358
) -> Self {
346359
var blocks: [CodeBlock] = []
@@ -357,7 +370,7 @@ extension [CodeBlock] {
357370
comment: .doc(
358371
"Namespace containing generated types for the \"\(service.name.identifyingName)\" service."
359372
),
360-
item: .declaration(.enum(serviceNamespace))
373+
item: .declaration(.guarded(availability, .enum(serviceNamespace)))
361374
)
362375
)
363376

@@ -367,7 +380,9 @@ extension [CodeBlock] {
367380
literalFullyQualifiedService: service.name.identifyingName,
368381
namer: namer
369382
)
370-
blocks.append(CodeBlock(item: .declaration(.extension(descriptorExtension))))
383+
blocks.append(
384+
CodeBlock(item: .declaration(.guarded(availability, .extension(descriptorExtension))))
385+
)
371386

372387
return blocks
373388
}

Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,9 @@ indirect enum Declaration: Equatable, Codable, Sendable {
797797
/// A declaration that adds a comment on top of the provided declaration.
798798
case deprecated(DeprecationDescription, Declaration)
799799

800+
/// A declaration that adds an availability guard on top of the provided declaration.
801+
case guarded(AvailabilityDescription, Declaration)
802+
800803
/// A variable declaration.
801804
case variable(VariableDescription)
802805

@@ -837,37 +840,37 @@ struct DeprecationDescription: Equatable, Codable, Sendable {
837840
/// A description of an availability guard.
838841
///
839842
/// For example: `@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)`
840-
struct AvailabilityDescription: Equatable, Codable, Sendable {
843+
package struct AvailabilityDescription: Equatable, Codable, Sendable {
841844
/// The array of OSes and versions which are specified in the availability guard.
842-
var osVersions: [OSVersion]
843-
init(osVersions: [OSVersion]) {
845+
package var osVersions: [OSVersion]
846+
package init(osVersions: [OSVersion]) {
844847
self.osVersions = osVersions
845848
}
846849

847850
/// An OS and its version.
848-
struct OSVersion: Equatable, Codable, Sendable {
849-
var os: OS
850-
var version: String
851-
init(os: OS, version: String) {
851+
package struct OSVersion: Equatable, Codable, Sendable {
852+
package var os: OS
853+
package var version: String
854+
package init(os: OS, version: String) {
852855
self.os = os
853856
self.version = version
854857
}
855858
}
856859

857860
/// One of the possible OSes.
858861
// swift-format-ignore: DontRepeatTypeInStaticProperties
859-
struct OS: Equatable, Codable, Sendable {
860-
var name: String
862+
package struct OS: Equatable, Codable, Sendable {
863+
package var name: String
861864

862-
init(name: String) {
865+
package init(name: String) {
863866
self.name = name
864867
}
865868

866-
static let macOS = Self(name: "macOS")
867-
static let iOS = Self(name: "iOS")
868-
static let watchOS = Self(name: "watchOS")
869-
static let tvOS = Self(name: "tvOS")
870-
static let visionOS = Self(name: "visionOS")
869+
package static let macOS = Self(name: "macOS")
870+
package static let iOS = Self(name: "iOS")
871+
package static let watchOS = Self(name: "watchOS")
872+
package static let tvOS = Self(name: "tvOS")
873+
package static let visionOS = Self(name: "visionOS")
871874
}
872875
}
873876

@@ -1873,6 +1876,7 @@ extension Declaration {
18731876
switch self {
18741877
case .commentable(_, let declaration): return declaration.accessModifier
18751878
case .deprecated(_, let declaration): return declaration.accessModifier
1879+
case .guarded(_, let declaration): return declaration.accessModifier
18761880
case .variable(let variableDescription): return variableDescription.accessModifier
18771881
case .extension(let extensionDescription): return extensionDescription.accessModifier
18781882
case .struct(let structDescription): return structDescription.accessModifier
@@ -1891,6 +1895,9 @@ extension Declaration {
18911895
case .deprecated(let deprecationDescription, var declaration):
18921896
declaration.accessModifier = newValue
18931897
self = .deprecated(deprecationDescription, declaration)
1898+
case .guarded(let availability, var declaration):
1899+
declaration.accessModifier = newValue
1900+
self = .guarded(availability, declaration)
18941901
case .variable(var variableDescription):
18951902
variableDescription.accessModifier = newValue
18961903
self = .variable(variableDescription)

Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ struct ClientCodeTranslator {
8181
func translate(
8282
accessModifier: AccessModifier,
8383
service: ServiceDescriptor,
84+
availability: AvailabilityDescription,
8485
namer: Namer = Namer(),
8586
serializer: (String) -> String,
8687
deserializer: (String) -> String
@@ -132,7 +133,7 @@ struct ClientCodeTranslator {
132133
),
133134
]
134135
)
135-
blocks.append(.declaration(.extension(`extension`)))
136+
blocks.append(.declaration(.guarded(availability, .extension(`extension`))))
136137

137138
let extensionWithDefaults: ExtensionDescription = .clientMethodSignatureWithDefaults(
138139
accessLevel: accessModifier,
@@ -145,7 +146,7 @@ struct ClientCodeTranslator {
145146
blocks.append(
146147
CodeBlock(
147148
comment: .inline("Helpers providing default arguments to 'ClientProtocol' methods."),
148-
item: .declaration(.extension(extensionWithDefaults))
149+
item: .declaration(.guarded(availability, .extension(extensionWithDefaults)))
149150
)
150151
)
151152

@@ -158,7 +159,7 @@ struct ClientCodeTranslator {
158159
blocks.append(
159160
CodeBlock(
160161
comment: .inline("Helpers providing sugared APIs for 'ClientProtocol' methods."),
161-
item: .declaration(.extension(extensionWithExplodedAPI))
162+
item: .declaration(.guarded(availability, .extension(extensionWithExplodedAPI)))
162163
)
163164
)
164165

Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ package struct IDLToStructuredSwiftTranslator {
2626
accessLevelOnImports: Bool,
2727
client: Bool,
2828
server: Bool,
29-
grpcCoreModuleName: String
29+
grpcCoreModuleName: String,
30+
availability: AvailabilityDescription
3031
) throws -> StructuredSwiftRepresentation {
3132
try self.validateInput(codeGenerationRequest)
3233
let accessModifier = AccessModifier(accessLevel)
@@ -46,6 +47,7 @@ package struct IDLToStructuredSwiftTranslator {
4647
let metadata = metadataTranslator.translate(
4748
accessModifier: accessModifier,
4849
service: service,
50+
availability: availability,
4951
namer: namer
5052
)
5153
codeBlocks.append(contentsOf: metadata)
@@ -58,6 +60,7 @@ package struct IDLToStructuredSwiftTranslator {
5860
let blocks = serverTranslator.translate(
5961
accessModifier: accessModifier,
6062
service: service,
63+
availability: availability,
6164
namer: namer,
6265
serializer: codeGenerationRequest.makeSerializerCodeSnippet,
6366
deserializer: codeGenerationRequest.makeDeserializerCodeSnippet
@@ -72,6 +75,7 @@ package struct IDLToStructuredSwiftTranslator {
7275
let blocks = clientTranslator.translate(
7376
accessModifier: accessModifier,
7477
service: service,
78+
availability: availability,
7579
namer: namer,
7680
serializer: codeGenerationRequest.makeSerializerCodeSnippet,
7781
deserializer: codeGenerationRequest.makeDeserializerCodeSnippet

Sources/GRPCCodeGen/Internal/Translator/MetadataTranslator.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,14 @@ struct MetadataTranslator {
2020
func translate(
2121
accessModifier: AccessModifier,
2222
service: ServiceDescriptor,
23+
availability: AvailabilityDescription,
2324
namer: Namer = Namer()
2425
) -> [CodeBlock] {
25-
.serviceMetadata(accessModifier: accessModifier, service: service, namer: namer)
26+
.serviceMetadata(
27+
accessModifier: accessModifier,
28+
service: service,
29+
availability: availability,
30+
namer: namer
31+
)
2632
}
2733
}

Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ struct ServerCodeTranslator {
6363
func translate(
6464
accessModifier: AccessModifier,
6565
service: ServiceDescriptor,
66+
availability: AvailabilityDescription,
6667
namer: Namer = Namer(),
6768
serializer: (String) -> String,
6869
deserializer: (String) -> String
@@ -129,7 +130,7 @@ struct ServerCodeTranslator {
129130
),
130131
]
131132
)
132-
blocks.append(.declaration(.extension(`extension`)))
133+
blocks.append(.declaration(.guarded(availability, .extension(`extension`))))
133134

134135
// extension <Service>.StreamingServiceProtocol> { ... }
135136
let registerExtension: ExtensionDescription = .registrableRPCServiceDefaultImplementation(
@@ -144,7 +145,7 @@ struct ServerCodeTranslator {
144145
blocks.append(
145146
CodeBlock(
146147
comment: .inline("Default implementation of 'registerMethods(with:)'."),
147-
item: .declaration(.extension(registerExtension))
148+
item: .declaration(.guarded(availability, .extension(registerExtension)))
148149
)
149150
)
150151

@@ -161,7 +162,7 @@ struct ServerCodeTranslator {
161162
comment: .inline(
162163
"Default implementation of streaming methods from 'StreamingServiceProtocol'."
163164
),
164-
item: .declaration(.extension(streamingServiceDefaultImplExtension))
165+
item: .declaration(.guarded(availability, .extension(streamingServiceDefaultImplExtension)))
165166
)
166167
)
167168

@@ -175,7 +176,7 @@ struct ServerCodeTranslator {
175176
blocks.append(
176177
CodeBlock(
177178
comment: .inline("Default implementation of methods from 'ServiceProtocol'."),
178-
item: .declaration(.extension(serviceDefaultImplExtension))
179+
item: .declaration(.guarded(availability, .extension(serviceDefaultImplExtension)))
179180
)
180181
)
181182

Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct ClientCodeTranslatorSnippetBasedTests {
4242
)
4343

4444
let expectedSwift = """
45+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
4546
extension NamespaceA_ServiceA {
4647
/// Generated client protocol for the "namespaceA.ServiceA" service.
4748
///
@@ -132,6 +133,7 @@ struct ClientCodeTranslatorSnippetBasedTests {
132133
}
133134
}
134135
// Helpers providing default arguments to 'ClientProtocol' methods.
136+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
135137
extension NamespaceA_ServiceA.ClientProtocol {
136138
/// Call the "MethodA" method.
137139
///
@@ -163,6 +165,7 @@ struct ClientCodeTranslatorSnippetBasedTests {
163165
}
164166
}
165167
// Helpers providing sugared APIs for 'ClientProtocol' methods.
168+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
166169
extension NamespaceA_ServiceA.ClientProtocol {
167170
/// Call the "MethodA" method.
168171
///
@@ -208,7 +211,11 @@ struct ClientCodeTranslatorSnippetBasedTests {
208211
service: ServiceDescriptor
209212
) -> String {
210213
let translator = ClientCodeTranslator()
211-
let codeBlocks = translator.translate(accessModifier: accessLevel, service: service) {
214+
let codeBlocks = translator.translate(
215+
accessModifier: accessLevel,
216+
service: service,
217+
availability: .macOS15Aligned
218+
) {
212219
"GRPCProtobuf.ProtobufSerializer<\($0)>()"
213220
} deserializer: {
214221
"GRPCProtobuf.ProtobufDeserializer<\($0)>()"

0 commit comments

Comments
 (0)