GenerateGRPC.swift 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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. import GRPCCodeGen
  17. import GRPCProtobufCodeGen
  18. import SwiftProtobuf
  19. import SwiftProtobufPluginLibrary
  20. #if canImport(FoundationEssentials)
  21. import FoundationEssentials
  22. #else
  23. import Foundation
  24. #endif
  25. @main
  26. final class GenerateGRPC: CodeGenerator {
  27. var version: String? {
  28. Version.versionString
  29. }
  30. var projectURL: String {
  31. "https://github.com/grpc/grpc-swift"
  32. }
  33. var supportedFeatures: [Google_Protobuf_Compiler_CodeGeneratorResponse.Feature] {
  34. [.proto3Optional, .supportsEditions]
  35. }
  36. var supportedEditionRange: ClosedRange<Google_Protobuf_Edition> {
  37. Google_Protobuf_Edition.proto2 ... Google_Protobuf_Edition.edition2023
  38. }
  39. // A count of generated files by desired name (actual name may differ to avoid collisions).
  40. private var generatedFileNames: [String: Int] = [:]
  41. func generate(
  42. files fileDescriptors: [FileDescriptor],
  43. parameter: any CodeGeneratorParameter,
  44. protoCompilerContext: any ProtoCompilerContext,
  45. generatorOutputs outputs: any GeneratorOutputs
  46. ) throws {
  47. let options = try GeneratorOptions(parameter: parameter)
  48. for descriptor in fileDescriptors {
  49. if options.generateReflectionData {
  50. try self.generateReflectionData(
  51. descriptor,
  52. options: options,
  53. outputs: outputs
  54. )
  55. }
  56. if descriptor.services.isEmpty {
  57. continue
  58. }
  59. try self.generateV2Stubs(descriptor, options: options, outputs: outputs)
  60. }
  61. }
  62. private func generateReflectionData(
  63. _ descriptor: FileDescriptor,
  64. options: GeneratorOptions,
  65. outputs: any GeneratorOutputs
  66. ) throws {
  67. let fileName = self.uniqueOutputFileName(
  68. fileDescriptor: descriptor,
  69. fileNamingOption: options.fileNaming,
  70. extension: "reflection"
  71. )
  72. var options = ExtractProtoOptions()
  73. options.includeSourceCodeInfo = true
  74. let proto = descriptor.extractProto(options: options)
  75. let serializedProto = try proto.serializedData()
  76. let reflectionData = serializedProto.base64EncodedString()
  77. try outputs.add(fileName: fileName, contents: reflectionData)
  78. }
  79. private func generateV2Stubs(
  80. _ descriptor: FileDescriptor,
  81. options: GeneratorOptions,
  82. outputs: any GeneratorOutputs
  83. ) throws {
  84. let fileName = self.uniqueOutputFileName(
  85. fileDescriptor: descriptor,
  86. fileNamingOption: options.fileNaming
  87. )
  88. let config = SourceGenerator.Config(options: options)
  89. let fileGenerator = ProtobufCodeGenerator(config: config)
  90. let contents = try fileGenerator.generateCode(
  91. fileDescriptor: descriptor,
  92. protoFileModuleMappings: options.protoToModuleMappings,
  93. extraModuleImports: options.extraModuleImports
  94. )
  95. try outputs.add(fileName: fileName, contents: contents)
  96. }
  97. }
  98. extension GenerateGRPC {
  99. private func uniqueOutputFileName(
  100. fileDescriptor: FileDescriptor,
  101. fileNamingOption: FileNaming,
  102. component: String = "grpc",
  103. extension: String = "swift"
  104. ) -> String {
  105. let defaultName = outputFileName(
  106. component: component,
  107. fileDescriptor: fileDescriptor,
  108. fileNamingOption: fileNamingOption,
  109. extension: `extension`
  110. )
  111. if let count = self.generatedFileNames[defaultName] {
  112. self.generatedFileNames[defaultName] = count + 1
  113. return outputFileName(
  114. component: "\(count)." + component,
  115. fileDescriptor: fileDescriptor,
  116. fileNamingOption: fileNamingOption,
  117. extension: `extension`
  118. )
  119. } else {
  120. self.generatedFileNames[defaultName] = 1
  121. return defaultName
  122. }
  123. }
  124. private func outputFileName(
  125. component: String,
  126. fileDescriptor: FileDescriptor,
  127. fileNamingOption: FileNaming,
  128. extension: String
  129. ) -> String {
  130. let ext = "." + component + "." + `extension`
  131. let pathParts = splitPath(pathname: fileDescriptor.name)
  132. switch fileNamingOption {
  133. case .fullPath:
  134. return pathParts.dir + pathParts.base + ext
  135. case .pathToUnderscores:
  136. let dirWithUnderscores = pathParts.dir.replacing("/", with: "_")
  137. return dirWithUnderscores + pathParts.base + ext
  138. case .dropPath:
  139. return pathParts.base + ext
  140. }
  141. }
  142. }
  143. // from apple/swift-protobuf/Sources/protoc-gen-swift/StringUtils.swift
  144. private func splitPath(pathname: String) -> (dir: String, base: String, suffix: String) {
  145. var dir = ""
  146. var base = ""
  147. var suffix = ""
  148. for character in pathname {
  149. if character == "/" {
  150. dir += base + suffix + String(character)
  151. base = ""
  152. suffix = ""
  153. } else if character == "." {
  154. base += suffix
  155. suffix = String(character)
  156. } else {
  157. suffix += String(character)
  158. }
  159. }
  160. let validSuffix = suffix.isEmpty || suffix.first == "."
  161. if !validSuffix {
  162. base += suffix
  163. suffix = ""
  164. }
  165. return (dir: dir, base: base, suffix: suffix)
  166. }
  167. extension SourceGenerator.Config {
  168. init(options: GeneratorOptions) {
  169. let accessLevel: SourceGenerator.Config.AccessLevel
  170. switch options.visibility {
  171. case .internal:
  172. accessLevel = .internal
  173. case .package:
  174. accessLevel = .package
  175. case .public:
  176. accessLevel = .public
  177. }
  178. self.init(
  179. accessLevel: accessLevel,
  180. accessLevelOnImports: options.useAccessLevelOnImports,
  181. client: options.generateClient,
  182. server: options.generateServer
  183. )
  184. }
  185. }