ProtoFileToModuleMappings.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // Sources/SwiftProtobufPluginLibrary/ProtoPathModuleMappings.swift - Helpers for module mappings option
  2. //
  3. // Copyright (c) 2014 - 2017 Apple Inc. and the project authors
  4. // Licensed under Apache License v2.0 with Runtime Library Exception
  5. //
  6. // See LICENSE.txt for license information:
  7. // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
  8. //
  9. // -----------------------------------------------------------------------------
  10. ///
  11. /// Helper handling proto file to module mappings.
  12. ///
  13. // -----------------------------------------------------------------------------
  14. import Foundation
  15. private let defaultSwiftProtobufModuleName = "SwiftProtobuf"
  16. /// Handles the mapping of proto files to the modules they will be compiled into.
  17. public struct ProtoFileToModuleMappings {
  18. /// Errors raised from parsing mappings
  19. public enum LoadError: Error, Equatable {
  20. /// Raised if the path wasn't found.
  21. case failToOpen(path: String)
  22. /// Raised if an mapping entry in the protobuf doesn't have a module name.
  23. /// mappingIndex is the index (0-N) of the mapping.
  24. case entryMissingModuleName(mappingIndex: Int)
  25. /// Raised if an mapping entry in the protobuf doesn't have any proto files listed.
  26. /// mappingIndex is the index (0-N) of the mapping.
  27. case entryHasNoProtoPaths(mappingIndex: Int)
  28. /// The given proto path was listed for both modules.
  29. case duplicateProtoPathMapping(path: String, firstModule: String, secondModule: String)
  30. }
  31. /// Proto file name to module name.
  32. /// This is really `private` to this type, it is just `internal` so the tests can
  33. /// access it to verify things.
  34. package let mappings: [String: String]
  35. /// A Boolean value that indicates that there were developer provided
  36. /// mappings.
  37. ///
  38. /// Since `mappings` will have the bundled proto files also, this is used
  39. /// to track whether there are any provided mappings.
  40. public let hasMappings: Bool
  41. /// The name of the runtime module for SwiftProtobuf (usually "SwiftProtobuf").
  42. /// We expect to find the WKTs in the module named here.
  43. public let swiftProtobufModuleName: String
  44. /// Loads and parses the given module mapping from disk. Raises LoadError
  45. /// or TextFormatDecodingError.
  46. public init(path: String) throws {
  47. try self.init(path: path, swiftProtobufModuleName: nil)
  48. }
  49. /// Loads and parses the given module mapping from disk. Raises LoadError
  50. /// or TextFormatDecodingError.
  51. public init(path: String, swiftProtobufModuleName: String?) throws {
  52. let content: String
  53. do {
  54. content = try String(contentsOfFile: path, encoding: String.Encoding.utf8)
  55. } catch {
  56. throw LoadError.failToOpen(path: path)
  57. }
  58. let mappingsProto = try SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: content)
  59. try self.init(moduleMappingsProto: mappingsProto, swiftProtobufModuleName: swiftProtobufModuleName)
  60. }
  61. /// Parses the given module mapping. Raises LoadError.
  62. public init(moduleMappingsProto mappings: SwiftProtobuf_GenSwift_ModuleMappings) throws {
  63. try self.init(moduleMappingsProto: mappings, swiftProtobufModuleName: nil)
  64. }
  65. /// Parses the given module mapping. Raises LoadError.
  66. public init(
  67. moduleMappingsProto mappings: SwiftProtobuf_GenSwift_ModuleMappings,
  68. swiftProtobufModuleName: String?
  69. ) throws {
  70. self.swiftProtobufModuleName = swiftProtobufModuleName ?? defaultSwiftProtobufModuleName
  71. var builder = wktMappings(swiftProtobufModuleName: self.swiftProtobufModuleName)
  72. let initialCount = builder.count
  73. for (idx, mapping) in mappings.mapping.lazy.enumerated() {
  74. if mapping.moduleName.isEmpty {
  75. throw LoadError.entryMissingModuleName(mappingIndex: idx)
  76. }
  77. if mapping.protoFilePath.isEmpty {
  78. throw LoadError.entryHasNoProtoPaths(mappingIndex: idx)
  79. }
  80. for path in mapping.protoFilePath {
  81. if let existing = builder[path] {
  82. if existing != mapping.moduleName {
  83. throw LoadError.duplicateProtoPathMapping(
  84. path: path,
  85. firstModule: existing,
  86. secondModule: mapping.moduleName
  87. )
  88. }
  89. // Was a repeat, just allow it.
  90. } else {
  91. builder[path] = mapping.moduleName
  92. }
  93. }
  94. }
  95. self.mappings = builder
  96. self.hasMappings = initialCount != builder.count
  97. }
  98. public init() {
  99. try! self.init(moduleMappingsProto: SwiftProtobuf_GenSwift_ModuleMappings(), swiftProtobufModuleName: nil)
  100. }
  101. public init(swiftProtobufModuleName: String?) {
  102. try! self.init(
  103. moduleMappingsProto: SwiftProtobuf_GenSwift_ModuleMappings(),
  104. swiftProtobufModuleName: swiftProtobufModuleName
  105. )
  106. }
  107. /// Looks up the module a given file is in.
  108. public func moduleName(forFile file: FileDescriptor) -> String? {
  109. mappings[file.name]
  110. }
  111. /// Returns the list of modules that need to be imported for a given file based on
  112. /// the dependencies it has.
  113. public func neededModules(forFile file: FileDescriptor) -> [String]? {
  114. guard hasMappings else { return nil }
  115. if file.dependencies.isEmpty {
  116. return nil
  117. }
  118. var collector = Set<String>()
  119. for dependency in file.dependencies {
  120. if let depModule = mappings[dependency.name] {
  121. collector.insert(depModule)
  122. }
  123. }
  124. // NOTE: This api is only used by gRPC (or things like it), with
  125. // `import public` now re-exporting things, this likely can go away or just
  126. // be reduced just the above loop, without the need for special casing the
  127. // `import public` cases. It will come down to what should expectations
  128. // be for protobuf messages, enums, and extensions with repsect to something
  129. // that generates on top if it. i.e. - should they re-export things or
  130. // should only the generated proto code do it?
  131. // Protocol Buffers has the concept of "public imports", these are imports
  132. // into a file that expose everything from within the file to the new
  133. // context. From the docs -
  134. // https://protobuf.dev/programming-guides/proto/#importing
  135. // `import public` dependencies can be transitively relied upon by anyone
  136. // importing the proto containing the import public statement.
  137. // To properly expose the types for use, it means in each file, the public imports
  138. // from the dependencies have to be hoisted and also imported.
  139. var visited = Set<String>()
  140. var toScan = file.dependencies
  141. while let dep = toScan.popLast() {
  142. for pubDep in dep.publicDependencies {
  143. let pubDepName = pubDep.name
  144. if visited.contains(pubDepName) { continue }
  145. visited.insert(pubDepName)
  146. toScan.append(pubDep)
  147. if let pubDepModule = mappings[pubDepName] {
  148. collector.insert(pubDepModule)
  149. }
  150. }
  151. }
  152. if let moduleForThisFile = mappings[file.name] {
  153. collector.remove(moduleForThisFile)
  154. }
  155. // The library itself (happens if the import one of the WKTs).
  156. collector.remove(self.swiftProtobufModuleName)
  157. if collector.isEmpty {
  158. return nil
  159. }
  160. return collector.sorted()
  161. }
  162. }
  163. // Used to seed the mappings, the wkt are all part of the main library.
  164. private func wktMappings(swiftProtobufModuleName: String) -> [String: String] {
  165. SwiftProtobufInfo.bundledProtoFiles.reduce(into: [:]) { $0[$1] = swiftProtobufModuleName }
  166. }