PluginUtils.swift 8.2 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 PackagePlugin
  18. /// Derive the path to the instance of `protoc` to be used.
  19. /// - Parameters:
  20. /// - config: The supplied configuration. If no path is supplied then one is discovered using the `PROTOC_PATH` environment variable or the `findTool`.
  21. /// - findTool: The context-supplied tool which is used to attempt to discover the path to a `protoc` binary.
  22. /// - Returns: The path to the instance of `protoc` to be used.
  23. func deriveProtocPath(
  24. using config: CommonConfiguration,
  25. tool findTool: (String) throws -> PackagePlugin.PluginContext.Tool
  26. ) throws -> URL {
  27. if let configuredProtocPath = config.protocPath {
  28. return URL(fileURLWithPath: configuredProtocPath)
  29. } else if let environmentPath = ProcessInfo.processInfo.environment["PROTOC_PATH"] {
  30. // The user set the env variable, so let's take that
  31. return URL(fileURLWithPath: environmentPath)
  32. } else {
  33. // The user didn't set anything so let's try see if SPM can find a binary for us
  34. return try findTool("protoc").url
  35. }
  36. }
  37. /// Construct the arguments to be passed to `protoc` when invoking the `proto-gen-swift` `protoc` plugin.
  38. /// - Parameters:
  39. /// - config: The configuration for this operation.
  40. /// - fileNaming: The file naming scheme to be used.
  41. /// - inputFiles: The input `.proto` files.
  42. /// - protoDirectoryPaths: The directories in which `protoc` will search for imports.
  43. /// - protocGenSwiftPath: The path to the `proto-gen-swift` `protoc` plugin.
  44. /// - outputDirectory: The directory in which generated source files are created.
  45. /// - Returns: The constructed arguments to be passed to `protoc` when invoking the `proto-gen-swift` `protoc` plugin.
  46. func constructProtocGenSwiftArguments(
  47. config: CommonConfiguration,
  48. using fileNaming: CommonConfiguration.FileNaming?,
  49. inputFiles: [URL],
  50. protoDirectoryPaths: [URL],
  51. protocGenSwiftPath: URL,
  52. outputDirectory: URL
  53. ) -> [String] {
  54. // Construct the `protoc` arguments.
  55. var protocArgs = [
  56. "--plugin=protoc-gen-swift=\(protocGenSwiftPath.relativePath)",
  57. "--swift_out=\(outputDirectory.relativePath)",
  58. ]
  59. // Add the visibility if it was set
  60. if let visibility = config.visibility {
  61. protocArgs.append("--swift_opt=Visibility=\(visibility.rawValue)")
  62. }
  63. // Add the file naming
  64. if let fileNaming = fileNaming {
  65. protocArgs.append("--swift_opt=FileNaming=\(fileNaming.rawValue)")
  66. }
  67. // TODO: Don't currently support implementation only imports
  68. // // Add the implementation only imports flag if it was set
  69. // if let implementationOnlyImports = config.implementationOnlyImports {
  70. // protocArgs.append("--swift_opt=ImplementationOnlyImports=\(implementationOnlyImports)")
  71. // }
  72. // Add the useAccessLevelOnImports only imports flag if it was set
  73. if let useAccessLevelOnImports = config.useAccessLevelOnImports {
  74. protocArgs.append("--swift_opt=UseAccessLevelOnImports=\(useAccessLevelOnImports)")
  75. }
  76. protocArgs.append(contentsOf: protoDirectoryPaths.map { "--proto_path=\($0.relativePath)" })
  77. protocArgs.append(contentsOf: inputFiles.map { $0.relativePath })
  78. return protocArgs
  79. }
  80. /// Construct the arguments to be passed to `protoc` when invoking the `proto-gen-grpc-swift` `protoc` plugin.
  81. /// - Parameters:
  82. /// - config: The configuration for this operation.
  83. /// - fileNaming: The file naming scheme to be used.
  84. /// - inputFiles: The input `.proto` files.
  85. /// - protoDirectoryPaths: The directories in which `protoc` will search for imports.
  86. /// - protocGenGRPCSwiftPath: The path to the `proto-gen-grpc-swift` `protoc` plugin.
  87. /// - outputDirectory: The directory in which generated source files are created.
  88. /// - Returns: The constructed arguments to be passed to `protoc` when invoking the `proto-gen-grpc-swift` `protoc` plugin.
  89. func constructProtocGenGRPCSwiftArguments(
  90. config: CommonConfiguration,
  91. using fileNaming: CommonConfiguration.FileNaming?,
  92. inputFiles: [URL],
  93. protoDirectoryPaths: [URL],
  94. protocGenGRPCSwiftPath: URL,
  95. outputDirectory: URL
  96. ) -> [String] {
  97. // Construct the `protoc` arguments.
  98. var protocArgs = [
  99. "--plugin=protoc-gen-grpc-swift=\(protocGenGRPCSwiftPath.relativePath)",
  100. "--grpc-swift_out=\(outputDirectory.relativePath)",
  101. ]
  102. if let importPaths = config.importPaths {
  103. for path in importPaths {
  104. protocArgs.append("-I")
  105. protocArgs.append("\(path)")
  106. }
  107. }
  108. if let visibility = config.visibility {
  109. protocArgs.append("--grpc-swift_opt=Visibility=\(visibility.rawValue.capitalized)")
  110. }
  111. if let generateServerCode = config.server {
  112. protocArgs.append("--grpc-swift_opt=Server=\(generateServerCode)")
  113. }
  114. if let generateClientCode = config.client {
  115. protocArgs.append("--grpc-swift_opt=Client=\(generateClientCode)")
  116. }
  117. // TODO: Don't currently support reflection data
  118. // if let generateReflectionData = config.reflectionData {
  119. // protocArgs.append("--grpc-swift_opt=ReflectionData=\(generateReflectionData)")
  120. // }
  121. if let fileNaming = fileNaming {
  122. protocArgs.append("--grpc-swift_opt=FileNaming=\(fileNaming.rawValue)")
  123. }
  124. if let protoPathModuleMappings = config.protoPathModuleMappings {
  125. protocArgs.append("--grpc-swift_opt=ProtoPathModuleMappings=\(protoPathModuleMappings)")
  126. }
  127. if let useAccessLevelOnImports = config.useAccessLevelOnImports {
  128. protocArgs.append("--grpc-swift_opt=UseAccessLevelOnImports=\(useAccessLevelOnImports)")
  129. }
  130. protocArgs.append(contentsOf: protoDirectoryPaths.map { "--proto_path=\($0.relativePath)" })
  131. protocArgs.append(contentsOf: inputFiles.map { $0.relativePath })
  132. return protocArgs
  133. }
  134. /// Derive the expected output file path to match the behavior of the `proto-gen-swift` and `proto-gen-grpc-swift` `protoc` plugins.
  135. /// - Parameters:
  136. /// - inputFile: The input `.proto` file.
  137. /// - fileNaming: The file naming scheme.
  138. /// - protoDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes.
  139. /// - outputDirectory: The directory in which generated source files are created.
  140. /// - outputExtension: The file extension to be appended to generated files in-place of `.proto`.
  141. /// - Returns: The expected output file path.
  142. func deriveOutputFilePath(
  143. for inputFile: URL,
  144. using fileNaming: CommonConfiguration.FileNaming,
  145. protoDirectoryPath: URL,
  146. outputDirectory: URL,
  147. outputExtension: String
  148. ) -> URL {
  149. // The name of the output file is based on the name of the input file.
  150. // We validated in the beginning that every file has the suffix of .proto
  151. // This means we can just drop the last 5 elements and append the new suffix
  152. let lastPathComponentRoot = inputFile.lastPathComponent.dropLast(5)
  153. let lastPathComponent = String(lastPathComponentRoot + outputExtension)
  154. // find the inputFile path relative to the proto directory
  155. var relativePathComponents = inputFile.deletingLastPathComponent().pathComponents
  156. for protoDirectoryPathComponent in protoDirectoryPath.pathComponents {
  157. if relativePathComponents.first == protoDirectoryPathComponent {
  158. relativePathComponents.removeFirst()
  159. } else {
  160. break
  161. }
  162. }
  163. switch fileNaming {
  164. case .dropPath:
  165. let outputFileName = lastPathComponent
  166. return outputDirectory.appendingPathComponent(outputFileName)
  167. case .fullPath:
  168. let outputFileComponents = relativePathComponents + [lastPathComponent]
  169. var outputFilePath = outputDirectory
  170. for outputFileComponent in outputFileComponents {
  171. outputFilePath.append(component: outputFileComponent)
  172. }
  173. return outputFilePath
  174. case .pathToUnderscores:
  175. let outputFileComponents = relativePathComponents + [lastPathComponent]
  176. let outputFileName = outputFileComponents.joined(separator: "_")
  177. return outputDirectory.appendingPathComponent(outputFileName)
  178. }
  179. }