ProtoFileToModuleMappings.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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 {
  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. let mappings: [String:String]
  35. /// The name of the runtime module for SwiftProtobuf (usually "SwiftProtobuf").
  36. /// We expect to find the WKTs in the module named here.
  37. public let swiftProtobufModuleName: String
  38. /// Loads and parses the given module mapping from disk. Raises LoadError
  39. /// or TextFormatDecodingError.
  40. public init(path: String) throws {
  41. try self.init(path: path, swiftProtobufModuleName: nil)
  42. }
  43. /// Loads and parses the given module mapping from disk. Raises LoadError
  44. /// or TextFormatDecodingError.
  45. public init(path: String, swiftProtobufModuleName: String?) throws {
  46. let content: String
  47. do {
  48. content = try String(contentsOfFile: path, encoding: String.Encoding.utf8)
  49. } catch {
  50. throw LoadError.failToOpen(path: path)
  51. }
  52. let mappingsProto = try SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: content)
  53. try self.init(moduleMappingsProto: mappingsProto, swiftProtobufModuleName: swiftProtobufModuleName)
  54. }
  55. /// Parses the given module mapping. Raises LoadError.
  56. public init(moduleMappingsProto mappings: SwiftProtobuf_GenSwift_ModuleMappings) throws {
  57. try self.init(moduleMappingsProto: mappings, swiftProtobufModuleName: nil)
  58. }
  59. /// Parses the given module mapping. Raises LoadError.
  60. public init(moduleMappingsProto mappings: SwiftProtobuf_GenSwift_ModuleMappings, swiftProtobufModuleName: String?) throws {
  61. self.swiftProtobufModuleName = swiftProtobufModuleName ?? defaultSwiftProtobufModuleName
  62. var builder = wktMappings(swiftProtobufModuleName: self.swiftProtobufModuleName)
  63. for (idx, mapping) in mappings.mapping.lazy.enumerated() {
  64. if mapping.moduleName.isEmpty {
  65. throw LoadError.entryMissingModuleName(mappingIndex: idx)
  66. }
  67. if mapping.protoFilePath.isEmpty {
  68. throw LoadError.entryHasNoProtoPaths(mappingIndex: idx)
  69. }
  70. for path in mapping.protoFilePath {
  71. if let existing = builder[path] {
  72. if existing != mapping.moduleName {
  73. throw LoadError.duplicateProtoPathMapping(path: path,
  74. firstModule: existing,
  75. secondModule: mapping.moduleName)
  76. }
  77. // Was a repeat, just allow it.
  78. } else {
  79. builder[path] = mapping.moduleName
  80. }
  81. }
  82. }
  83. self.mappings = builder
  84. }
  85. public init() {
  86. try! self.init(moduleMappingsProto: SwiftProtobuf_GenSwift_ModuleMappings(), swiftProtobufModuleName: nil)
  87. }
  88. public init(swiftProtobufModuleName: String?) {
  89. try! self.init(moduleMappingsProto: SwiftProtobuf_GenSwift_ModuleMappings(), swiftProtobufModuleName: swiftProtobufModuleName)
  90. }
  91. /// Looks up the module a given file is in.
  92. public func moduleName(forFile file: FileDescriptor) -> String? {
  93. return mappings[file.name]
  94. }
  95. /// Returns the list of modules that need to be imported for a given file based on
  96. /// the dependencies it has.
  97. public func neededModules(forFile file: FileDescriptor) -> [String]? {
  98. if file.dependencies.isEmpty {
  99. return nil
  100. }
  101. var collector = Set<String>()
  102. for dependency in file.dependencies {
  103. if let depModule = mappings[dependency.name] {
  104. collector.insert(depModule)
  105. }
  106. }
  107. // Protocol Buffers has the concept of "public imports", these are imports
  108. // into a file that expose everything from within the file to the new
  109. // context. From the docs -
  110. // https://developers.google.com/protocol-buffers/docs/proto#importing-definitions
  111. // `import public` dependencies can be transitively relied upon by anyone
  112. // importing the proto containing the import public statement.
  113. // To properly expose the types for use, it means in each file, the public imports
  114. // from the dependencies have to be hoisted and also imported.
  115. var visited = Set<String>()
  116. var toScan = file.dependencies
  117. while let dep = toScan.popLast() {
  118. for pubDep in dep.publicDependencies {
  119. let pubDepName = pubDep.name
  120. if visited.contains(pubDepName) { continue }
  121. visited.insert(pubDepName)
  122. toScan.append(pubDep)
  123. if let pubDepModule = mappings[pubDepName] {
  124. collector.insert(pubDepModule)
  125. }
  126. }
  127. }
  128. if let moduleForThisFile = mappings[file.name] {
  129. collector.remove(moduleForThisFile)
  130. }
  131. // The library itself (happens if the import one of the WKTs).
  132. collector.remove(self.swiftProtobufModuleName)
  133. if collector.isEmpty {
  134. return nil
  135. }
  136. return collector.sorted()
  137. }
  138. }
  139. // Used to seed the mappings, the wkt are all part of the main library.
  140. private func wktMappings(swiftProtobufModuleName: String) -> [String:String] {
  141. return SwiftProtobufInfo.bundledProtoFiles.reduce(into: [:]) { $0[$1] = swiftProtobufModuleName }
  142. }