GenerateGRPC.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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. @main
  20. final class GenerateGRPC: CodeGenerator {
  21. var version: String? {
  22. Version.versionString
  23. }
  24. var projectURL: String {
  25. "https://github.com/grpc/grpc-swift"
  26. }
  27. var supportedFeatures: [Google_Protobuf_Compiler_CodeGeneratorResponse.Feature] {
  28. [.proto3Optional, .supportsEditions]
  29. }
  30. var supportedEditionRange: ClosedRange<Google_Protobuf_Edition> {
  31. Google_Protobuf_Edition.proto2 ... Google_Protobuf_Edition.edition2023
  32. }
  33. // A count of generated files by desired name (actual name may differ to avoid collisions).
  34. private var generatedFileNames: [String: Int] = [:]
  35. func generate(
  36. files fileDescriptors: [FileDescriptor],
  37. parameter: any CodeGeneratorParameter,
  38. protoCompilerContext: any ProtoCompilerContext,
  39. generatorOutputs outputs: any GeneratorOutputs
  40. ) throws {
  41. let options = try GeneratorOptions(parameter: parameter)
  42. for descriptor in fileDescriptors {
  43. if options.generateReflectionData {
  44. try self.generateReflectionData(
  45. descriptor,
  46. options: options,
  47. outputs: outputs
  48. )
  49. }
  50. if descriptor.services.isEmpty {
  51. continue
  52. }
  53. if options.generateClient || options.generateServer || options.generateTestClient {
  54. try self.generateV1Stubs(descriptor, options: options, outputs: outputs)
  55. }
  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 generateV1Stubs(
  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 fileGenerator = Generator(descriptor, options: options)
  85. try outputs.add(fileName: fileName, contents: fileGenerator.code)
  86. }
  87. }
  88. extension GenerateGRPC {
  89. private func uniqueOutputFileName(
  90. fileDescriptor: FileDescriptor,
  91. fileNamingOption: FileNaming,
  92. component: String = "grpc",
  93. extension: String = "swift"
  94. ) -> String {
  95. let defaultName = outputFileName(
  96. component: component,
  97. fileDescriptor: fileDescriptor,
  98. fileNamingOption: fileNamingOption,
  99. extension: `extension`
  100. )
  101. if let count = self.generatedFileNames[defaultName] {
  102. self.generatedFileNames[defaultName] = count + 1
  103. return outputFileName(
  104. component: "\(count)." + component,
  105. fileDescriptor: fileDescriptor,
  106. fileNamingOption: fileNamingOption,
  107. extension: `extension`
  108. )
  109. } else {
  110. self.generatedFileNames[defaultName] = 1
  111. return defaultName
  112. }
  113. }
  114. private func outputFileName(
  115. component: String,
  116. fileDescriptor: FileDescriptor,
  117. fileNamingOption: FileNaming,
  118. extension: String
  119. ) -> String {
  120. let ext = "." + component + "." + `extension`
  121. let pathParts = splitPath(pathname: fileDescriptor.name)
  122. switch fileNamingOption {
  123. case .fullPath:
  124. return pathParts.dir + pathParts.base + ext
  125. case .pathToUnderscores:
  126. let dirWithUnderscores =
  127. pathParts.dir.replacingOccurrences(of: "/", with: "_")
  128. return dirWithUnderscores + pathParts.base + ext
  129. case .dropPath:
  130. return pathParts.base + ext
  131. }
  132. }
  133. }
  134. // from apple/swift-protobuf/Sources/protoc-gen-swift/StringUtils.swift
  135. private func splitPath(pathname: String) -> (dir: String, base: String, suffix: String) {
  136. var dir = ""
  137. var base = ""
  138. var suffix = ""
  139. for character in pathname {
  140. if character == "/" {
  141. dir += base + suffix + String(character)
  142. base = ""
  143. suffix = ""
  144. } else if character == "." {
  145. base += suffix
  146. suffix = String(character)
  147. } else {
  148. suffix += String(character)
  149. }
  150. }
  151. let validSuffix = suffix.isEmpty || suffix.first == "."
  152. if !validSuffix {
  153. base += suffix
  154. suffix = ""
  155. }
  156. return (dir: dir, base: base, suffix: suffix)
  157. }