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
62 changes: 61 additions & 1 deletion Sources/GRPCCodeGen/CodeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public struct CodeGenerator: Sendable {
public var server: Bool
/// The name of the core gRPC module.
public var grpcCoreModuleName: String
/// The availability annotations to use on the generated code.
public var availability: AvailabilityAnnotations = .default

/// Creates a new configuration.
///
Expand Down Expand Up @@ -84,6 +86,50 @@ public struct CodeGenerator: Sendable {
/// The generated code will have `package` access level.
public static var `package`: Self { Self(level: .`package`) }
}

// The availability that generated code is annotated with.
public struct AvailabilityAnnotations: Sendable, Hashable {
public struct Platform: Sendable, Hashable {
/// The name of the OS, e.g. 'macOS'.
public var os: String
/// The version of the OS, e.g. '15.0'.
public var version: String

public init(os: String, version: String) {
self.os = os
self.version = version
}
}

fileprivate enum Wrapped: Sendable, Hashable {
case macOS15Aligned
case custom([Platform])
}

fileprivate var wrapped: Wrapped

private init(_ wrapped: Wrapped) {
self.wrapped = wrapped
}

/// Use the default availability.
///
/// The default platform availability is:
/// - macOS 15.0
/// - iOS 18.0
/// - tvOS 18.0
/// - watchOS 11.0
/// - visionOS 2.0
Comment on lines +117 to +122
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we include macCatalyst?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macCatalyst is a derivative of iOS so it doesn't need to be included in the availability annotations (unless its availability differs from iOS).

I couldn't find authoritative documentation on this, but:

  • NSHipster has an article on availability which mentions the same here
  • Swift doesn't include macCatalyst in its availability macros (see here)

I also just tested this locally by building code for Mac Catalyst which only had iOS availability annotations on.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL. We should reconsider what we do in other projects then, because we have added it explicitly elsewhere.

public static var `default`: Self {
Self(.macOS15Aligned)
}

/// Use a custom set of availability attributes.
/// - Parameter platforms: Availability requirements.
public static func custom(_ platforms: [Platform]) -> Self {
Self(.custom(platforms))
}
}
}

/// Transforms a ``CodeGenerationRequest`` object into a ``SourceFile`` object containing
Expand All @@ -100,11 +146,25 @@ public struct CodeGenerator: Sendable {
accessLevelOnImports: self.config.accessLevelOnImports,
client: self.config.client,
server: self.config.server,
grpcCoreModuleName: self.config.grpcCoreModuleName
grpcCoreModuleName: self.config.grpcCoreModuleName,
availability: AvailabilityDescription(self.config.availability)
)

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

return sourceFile
}
}

extension AvailabilityDescription {
init(_ availability: CodeGenerator.Config.AvailabilityAnnotations) throws {
switch availability.wrapped {
case .macOS15Aligned:
self = .macOS15Aligned
case .custom(let platforms):
self.osVersions = platforms.map {
.init(os: .init(name: $0.os), version: $0.version)
}
}
}
}
2 changes: 2 additions & 0 deletions Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,8 @@ struct TextBasedRenderer: RendererProtocol {
renderCommentableDeclaration(comment: comment, declaration: nestedDeclaration)
case let .deprecated(deprecation, nestedDeclaration):
renderDeprecatedDeclaration(deprecation: deprecation, declaration: nestedDeclaration)
case let .guarded(availability, nestedDeclaration):
renderGuardedDeclaration(availability: availability, declaration: nestedDeclaration)
case .variable(let variableDescription): renderVariable(variableDescription)
case .extension(let extensionDescription): renderExtension(extensionDescription)
case .struct(let structDescription): renderStruct(structDescription)
Expand Down
19 changes: 17 additions & 2 deletions Sources/GRPCCodeGen/Internal/StructuredSwift+ServiceMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@
* limitations under the License.
*/

extension AvailabilityDescription {
package static let macOS15Aligned = AvailabilityDescription(
osVersions: [
OSVersion(os: .macOS, version: "15.0"),
OSVersion(os: .iOS, version: "18.0"),
OSVersion(os: .watchOS, version: "11.0"),
OSVersion(os: .tvOS, version: "18.0"),
OSVersion(os: .visionOS, version: "2.0"),
]
)
}

extension TypealiasDescription {
/// `typealias Input = <name>`
package static func methodInput(
Expand Down Expand Up @@ -341,6 +353,7 @@ extension [CodeBlock] {
package static func serviceMetadata(
accessModifier: AccessModifier? = nil,
service: ServiceDescriptor,
availability: AvailabilityDescription,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API breakage is detecting a false positive here:

3 breaking changes detected in GRPCCodeGen:
  💔 API breakage: func Array.serviceMetadata(accessModifier:service:namer:) has parameter 2 type change from GRPCCodeGen.Namer to GRPCCodeGen.AvailabilityDescription
  💔 API breakage: func Array.serviceMetadata(accessModifier:service:namer:) has removed default argument from parameter 2
  💔 API breakage: func Array.serviceMetadata(accessModifier:service:namer:) has been renamed to func serviceMetadata(accessModifier:service:availability:namer:)

namer: Namer = Namer()
) -> Self {
var blocks: [CodeBlock] = []
Expand All @@ -357,7 +370,7 @@ extension [CodeBlock] {
comment: .doc(
"Namespace containing generated types for the \"\(service.name.identifyingName)\" service."
),
item: .declaration(.enum(serviceNamespace))
item: .declaration(.guarded(availability, .enum(serviceNamespace)))
)
)

Expand All @@ -367,7 +380,9 @@ extension [CodeBlock] {
literalFullyQualifiedService: service.name.identifyingName,
namer: namer
)
blocks.append(CodeBlock(item: .declaration(.extension(descriptorExtension))))
blocks.append(
CodeBlock(item: .declaration(.guarded(availability, .extension(descriptorExtension))))
)

return blocks
}
Expand Down
37 changes: 22 additions & 15 deletions Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,9 @@ indirect enum Declaration: Equatable, Codable, Sendable {
/// A declaration that adds a comment on top of the provided declaration.
case deprecated(DeprecationDescription, Declaration)

/// A declaration that adds an availability guard on top of the provided declaration.
case guarded(AvailabilityDescription, Declaration)

/// A variable declaration.
case variable(VariableDescription)

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

/// An OS and its version.
struct OSVersion: Equatable, Codable, Sendable {
var os: OS
var version: String
init(os: OS, version: String) {
package struct OSVersion: Equatable, Codable, Sendable {
package var os: OS
package var version: String
package init(os: OS, version: String) {
self.os = os
self.version = version
}
}

/// One of the possible OSes.
// swift-format-ignore: DontRepeatTypeInStaticProperties
struct OS: Equatable, Codable, Sendable {
var name: String
package struct OS: Equatable, Codable, Sendable {
package var name: String

init(name: String) {
package init(name: String) {
self.name = name
}

static let macOS = Self(name: "macOS")
static let iOS = Self(name: "iOS")
static let watchOS = Self(name: "watchOS")
static let tvOS = Self(name: "tvOS")
static let visionOS = Self(name: "visionOS")
package static let macOS = Self(name: "macOS")
package static let iOS = Self(name: "iOS")
package static let watchOS = Self(name: "watchOS")
package static let tvOS = Self(name: "tvOS")
package static let visionOS = Self(name: "visionOS")
}
}

Expand Down Expand Up @@ -1873,6 +1876,7 @@ extension Declaration {
switch self {
case .commentable(_, let declaration): return declaration.accessModifier
case .deprecated(_, let declaration): return declaration.accessModifier
case .guarded(_, let declaration): return declaration.accessModifier
case .variable(let variableDescription): return variableDescription.accessModifier
case .extension(let extensionDescription): return extensionDescription.accessModifier
case .struct(let structDescription): return structDescription.accessModifier
Expand All @@ -1891,6 +1895,9 @@ extension Declaration {
case .deprecated(let deprecationDescription, var declaration):
declaration.accessModifier = newValue
self = .deprecated(deprecationDescription, declaration)
case .guarded(let availability, var declaration):
declaration.accessModifier = newValue
self = .guarded(availability, declaration)
case .variable(var variableDescription):
variableDescription.accessModifier = newValue
self = .variable(variableDescription)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ struct ClientCodeTranslator {
func translate(
accessModifier: AccessModifier,
service: ServiceDescriptor,
availability: AvailabilityDescription,
namer: Namer = Namer(),
serializer: (String) -> String,
deserializer: (String) -> String
Expand Down Expand Up @@ -132,7 +133,7 @@ struct ClientCodeTranslator {
),
]
)
blocks.append(.declaration(.extension(`extension`)))
blocks.append(.declaration(.guarded(availability, .extension(`extension`))))

let extensionWithDefaults: ExtensionDescription = .clientMethodSignatureWithDefaults(
accessLevel: accessModifier,
Expand All @@ -145,7 +146,7 @@ struct ClientCodeTranslator {
blocks.append(
CodeBlock(
comment: .inline("Helpers providing default arguments to 'ClientProtocol' methods."),
item: .declaration(.extension(extensionWithDefaults))
item: .declaration(.guarded(availability, .extension(extensionWithDefaults)))
)
)

Expand All @@ -158,7 +159,7 @@ struct ClientCodeTranslator {
blocks.append(
CodeBlock(
comment: .inline("Helpers providing sugared APIs for 'ClientProtocol' methods."),
item: .declaration(.extension(extensionWithExplodedAPI))
item: .declaration(.guarded(availability, .extension(extensionWithExplodedAPI)))
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ package struct IDLToStructuredSwiftTranslator {
accessLevelOnImports: Bool,
client: Bool,
server: Bool,
grpcCoreModuleName: String
grpcCoreModuleName: String,
availability: AvailabilityDescription
) throws -> StructuredSwiftRepresentation {
try self.validateInput(codeGenerationRequest)
let accessModifier = AccessModifier(accessLevel)
Expand All @@ -46,6 +47,7 @@ package struct IDLToStructuredSwiftTranslator {
let metadata = metadataTranslator.translate(
accessModifier: accessModifier,
service: service,
availability: availability,
namer: namer
)
codeBlocks.append(contentsOf: metadata)
Expand All @@ -58,6 +60,7 @@ package struct IDLToStructuredSwiftTranslator {
let blocks = serverTranslator.translate(
accessModifier: accessModifier,
service: service,
availability: availability,
namer: namer,
serializer: codeGenerationRequest.makeSerializerCodeSnippet,
deserializer: codeGenerationRequest.makeDeserializerCodeSnippet
Expand All @@ -72,6 +75,7 @@ package struct IDLToStructuredSwiftTranslator {
let blocks = clientTranslator.translate(
accessModifier: accessModifier,
service: service,
availability: availability,
namer: namer,
serializer: codeGenerationRequest.makeSerializerCodeSnippet,
deserializer: codeGenerationRequest.makeDeserializerCodeSnippet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@ struct MetadataTranslator {
func translate(
accessModifier: AccessModifier,
service: ServiceDescriptor,
availability: AvailabilityDescription,
namer: Namer = Namer()
) -> [CodeBlock] {
.serviceMetadata(accessModifier: accessModifier, service: service, namer: namer)
.serviceMetadata(
accessModifier: accessModifier,
service: service,
availability: availability,
namer: namer
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ struct ServerCodeTranslator {
func translate(
accessModifier: AccessModifier,
service: ServiceDescriptor,
availability: AvailabilityDescription,
namer: Namer = Namer(),
serializer: (String) -> String,
deserializer: (String) -> String
Expand Down Expand Up @@ -129,7 +130,7 @@ struct ServerCodeTranslator {
),
]
)
blocks.append(.declaration(.extension(`extension`)))
blocks.append(.declaration(.guarded(availability, .extension(`extension`))))

// extension <Service>.StreamingServiceProtocol> { ... }
let registerExtension: ExtensionDescription = .registrableRPCServiceDefaultImplementation(
Expand All @@ -144,7 +145,7 @@ struct ServerCodeTranslator {
blocks.append(
CodeBlock(
comment: .inline("Default implementation of 'registerMethods(with:)'."),
item: .declaration(.extension(registerExtension))
item: .declaration(.guarded(availability, .extension(registerExtension)))
)
)

Expand All @@ -161,7 +162,7 @@ struct ServerCodeTranslator {
comment: .inline(
"Default implementation of streaming methods from 'StreamingServiceProtocol'."
),
item: .declaration(.extension(streamingServiceDefaultImplExtension))
item: .declaration(.guarded(availability, .extension(streamingServiceDefaultImplExtension)))
)
)

Expand All @@ -175,7 +176,7 @@ struct ServerCodeTranslator {
blocks.append(
CodeBlock(
comment: .inline("Default implementation of methods from 'ServiceProtocol'."),
item: .declaration(.extension(serviceDefaultImplExtension))
item: .declaration(.guarded(availability, .extension(serviceDefaultImplExtension)))
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ struct ClientCodeTranslatorSnippetBasedTests {
)

let expectedSwift = """
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
extension NamespaceA_ServiceA {
/// Generated client protocol for the "namespaceA.ServiceA" service.
///
Expand Down Expand Up @@ -132,6 +133,7 @@ struct ClientCodeTranslatorSnippetBasedTests {
}
}
// Helpers providing default arguments to 'ClientProtocol' methods.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
extension NamespaceA_ServiceA.ClientProtocol {
/// Call the "MethodA" method.
///
Expand Down Expand Up @@ -163,6 +165,7 @@ struct ClientCodeTranslatorSnippetBasedTests {
}
}
// Helpers providing sugared APIs for 'ClientProtocol' methods.
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
extension NamespaceA_ServiceA.ClientProtocol {
/// Call the "MethodA" method.
///
Expand Down Expand Up @@ -208,7 +211,11 @@ struct ClientCodeTranslatorSnippetBasedTests {
service: ServiceDescriptor
) -> String {
let translator = ClientCodeTranslator()
let codeBlocks = translator.translate(accessModifier: accessLevel, service: service) {
let codeBlocks = translator.translate(
accessModifier: accessLevel,
service: service,
availability: .macOS15Aligned
) {
"GRPCProtobuf.ProtobufSerializer<\($0)>()"
} deserializer: {
"GRPCProtobuf.ProtobufDeserializer<\($0)>()"
Expand Down
Loading
Loading