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 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. try self.generateV2Stubs(descriptor, options: options, outputs: outputs)
  57. }
  58. }
  59. private func generateReflectionData(
  60. _ descriptor: FileDescriptor,
  61. options: GeneratorOptions,
  62. outputs: any GeneratorOutputs
  63. ) throws {
  64. let fileName = self.uniqueOutputFileName(
  65. fileDescriptor: descriptor,
  66. fileNamingOption: options.fileNaming,
  67. extension: "reflection"
  68. )
  69. var options = ExtractProtoOptions()
  70. options.includeSourceCodeInfo = true
  71. let proto = descriptor.extractProto(options: options)
  72. let serializedProto = try proto.serializedData()
  73. let reflectionData = serializedProto.base64EncodedString()
  74. try outputs.add(fileName: fileName, contents: reflectionData)
  75. }
  76. private func generateV2Stubs(
  77. _ descriptor: FileDescriptor,
  78. options: GeneratorOptions,
  79. outputs: any GeneratorOutputs
  80. ) throws {
  81. let fileName = self.uniqueOutputFileName(
  82. fileDescriptor: descriptor,
  83. fileNamingOption: options.fileNaming
  84. )
  85. let config = SourceGenerator.Config(options: options)
  86. let fileGenerator = ProtobufCodeGenerator(config: config)
  87. let contents = try fileGenerator.generateCode(
  88. fileDescriptor: descriptor,
  89. protoFileModuleMappings: options.protoToModuleMappings,
  90. extraModuleImports: options.extraModuleImports
  91. )
  92. try outputs.add(fileName: fileName, contents: contents)
  93. }
  94. }
  95. extension GenerateGRPC {
  96. private func uniqueOutputFileName(
  97. fileDescriptor: FileDescriptor,
  98. fileNamingOption: FileNaming,
  99. component: String = "grpc",
  100. extension: String = "swift"
  101. ) -> String {
  102. let defaultName = outputFileName(
  103. component: component,
  104. fileDescriptor: fileDescriptor,
  105. fileNamingOption: fileNamingOption,
  106. extension: `extension`
  107. )
  108. if let count = self.generatedFileNames[defaultName] {
  109. self.generatedFileNames[defaultName] = count + 1
  110. return outputFileName(
  111. component: "\(count)." + component,
  112. fileDescriptor: fileDescriptor,
  113. fileNamingOption: fileNamingOption,
  114. extension: `extension`
  115. )
  116. } else {
  117. self.generatedFileNames[defaultName] = 1
  118. return defaultName
  119. }
  120. }
  121. private func outputFileName(
  122. component: String,
  123. fileDescriptor: FileDescriptor,
  124. fileNamingOption: FileNaming,
  125. extension: String
  126. ) -> String {
  127. let ext = "." + component + "." + `extension`
  128. let pathParts = splitPath(pathname: fileDescriptor.name)
  129. switch fileNamingOption {
  130. case .fullPath:
  131. return pathParts.dir + pathParts.base + ext
  132. case .pathToUnderscores:
  133. let dirWithUnderscores = pathParts.dir.replacing("/", 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. }