GenerateGRPC.swift 6.6 KB

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