GenerateGRPC.swift 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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: SwiftProtobufPluginLibrary.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. try self.generateV2Stubs(descriptor, options: options, outputs: outputs)
  50. }
  51. }
  52. private func generateV2Stubs(
  53. _ descriptor: FileDescriptor,
  54. options: GeneratorOptions,
  55. outputs: any GeneratorOutputs
  56. ) throws {
  57. let fileName = self.uniqueOutputFileName(
  58. fileDescriptor: descriptor,
  59. fileNamingOption: options.fileNaming
  60. )
  61. let fileGenerator = ProtobufCodeGenerator(config: options.config)
  62. let contents = try fileGenerator.generateCode(
  63. fileDescriptor: descriptor,
  64. protoFileModuleMappings: options.protoToModuleMappings,
  65. extraModuleImports: options.extraModuleImports
  66. )
  67. try outputs.add(fileName: fileName, contents: contents)
  68. }
  69. }
  70. extension GenerateGRPC {
  71. private func uniqueOutputFileName(
  72. fileDescriptor: FileDescriptor,
  73. fileNamingOption: FileNaming,
  74. component: String = "grpc",
  75. extension: String = "swift"
  76. ) -> String {
  77. let defaultName = outputFileName(
  78. component: component,
  79. fileDescriptor: fileDescriptor,
  80. fileNamingOption: fileNamingOption,
  81. extension: `extension`
  82. )
  83. if let count = self.generatedFileNames[defaultName] {
  84. self.generatedFileNames[defaultName] = count + 1
  85. return outputFileName(
  86. component: "\(count)." + component,
  87. fileDescriptor: fileDescriptor,
  88. fileNamingOption: fileNamingOption,
  89. extension: `extension`
  90. )
  91. } else {
  92. self.generatedFileNames[defaultName] = 1
  93. return defaultName
  94. }
  95. }
  96. private func outputFileName(
  97. component: String,
  98. fileDescriptor: FileDescriptor,
  99. fileNamingOption: FileNaming,
  100. extension: String
  101. ) -> String {
  102. let ext = "." + component + "." + `extension`
  103. let pathParts = splitPath(pathname: fileDescriptor.name)
  104. switch fileNamingOption {
  105. case .fullPath:
  106. return pathParts.dir + pathParts.base + ext
  107. case .pathToUnderscores:
  108. let dirWithUnderscores = pathParts.dir.replacing("/", with: "_")
  109. return dirWithUnderscores + pathParts.base + ext
  110. case .dropPath:
  111. return pathParts.base + ext
  112. }
  113. }
  114. }
  115. // from apple/swift-protobuf/Sources/protoc-gen-swift/StringUtils.swift
  116. private func splitPath(pathname: String) -> (dir: String, base: String, suffix: String) {
  117. var dir = ""
  118. var base = ""
  119. var suffix = ""
  120. for character in pathname {
  121. if character == "/" {
  122. dir += base + suffix + String(character)
  123. base = ""
  124. suffix = ""
  125. } else if character == "." {
  126. base += suffix
  127. suffix = String(character)
  128. } else {
  129. suffix += String(character)
  130. }
  131. }
  132. let validSuffix = suffix.isEmpty || suffix.first == "."
  133. if !validSuffix {
  134. base += suffix
  135. suffix = ""
  136. }
  137. return (dir: dir, base: base, suffix: suffix)
  138. }