GenerateGRPC.swift 5.7 KB

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