ModuleMapBuilder.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. * Copyright 2019 Google
  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. /// A struct to build a .modulemap in a given framework directory.
  18. ///
  19. struct ModuleMapBuilder {
  20. /// Information associated with a framework
  21. private class FrameworkInfo {
  22. let isSourcePod: Bool
  23. let versionedPod: CocoaPodUtils.VersionedPod
  24. let subspecs: Set<String>
  25. var transitiveFrameworks: Set<String>?
  26. var transitiveLibraries: Set<String>?
  27. init(isSourcePod: Bool, versionedPod: CocoaPodUtils.VersionedPod, subspecs: Set<String>) {
  28. self.isSourcePod = isSourcePod
  29. self.versionedPod = versionedPod
  30. self.subspecs = subspecs
  31. }
  32. }
  33. struct ModuleMapContents {
  34. /// Placeholder to fill in umbrella header.
  35. static let umbrellaPlaceholder = "UMBRELLA_PLACEHOLDER"
  36. let contents: String
  37. init(module: String, frameworks: Set<String>, libraries: Set<String>) {
  38. var content = """
  39. framework module \(module) {
  40. umbrella header "\(ModuleMapBuilder.ModuleMapContents.umbrellaPlaceholder)"
  41. export *
  42. module * { export * }
  43. """
  44. for framework in frameworks.sorted() {
  45. content += " link framework " + framework + "\n"
  46. }
  47. for library in libraries.sorted() {
  48. content += " link " + library + "\n"
  49. }
  50. // The empty line at the end is intentional, do not remove it.
  51. content += "}\n"
  52. contents = content
  53. }
  54. func get(umbrellaHeader: String) -> String {
  55. return contents.replacingOccurrences(of:
  56. ModuleMapBuilder.ModuleMapContents.umbrellaPlaceholder, with: umbrellaHeader)
  57. }
  58. }
  59. /// The directory containing the Xcode project and Pods folder.
  60. private let projectDir: URL
  61. /// Custom CocoaPods spec repos to be used. If not provided, the tool will only use the CocoaPods
  62. /// master repo.
  63. private let customSpecRepos: [URL]?
  64. /// Dictionary of all installed pods. Update moduleMapContents here.
  65. private var allPods: [String: CocoaPodUtils.PodInfo]
  66. /// Dictionary of installed pods required for this module.
  67. private var installedPods: [String: FrameworkInfo]
  68. /// The platform for this build.
  69. private let platform: Platform
  70. /// The path containing local podspec URLs, if specified.
  71. private let localPodspecPath: URL?
  72. /// Default initializer.
  73. init(customSpecRepos: [URL]?,
  74. selectedPods: [String: CocoaPodUtils.PodInfo],
  75. platform: Platform,
  76. paths: ZipBuilder.FilesystemPaths) {
  77. projectDir = FileManager.default.temporaryDirectory(withName: "module")
  78. CocoaPodUtils.podInstallPrepare(inProjectDir: projectDir, templateDir: paths.templateDir)
  79. self.customSpecRepos = customSpecRepos
  80. allPods = selectedPods
  81. var installedPods: [String: FrameworkInfo] = [:]
  82. for pod in selectedPods {
  83. let versionedPod = CocoaPodUtils.VersionedPod(name: pod.key, version: pod.value.version)
  84. installedPods[pod.key] = FrameworkInfo(isSourcePod: pod.value.isSourcePod,
  85. versionedPod: versionedPod,
  86. subspecs: pod.value.subspecs)
  87. }
  88. self.installedPods = installedPods
  89. self.platform = platform
  90. localPodspecPath = paths.localPodspecPath
  91. }
  92. // MARK: - Public Functions
  93. /// Build the module map files for the source frameworks.
  94. ///
  95. func build() {
  96. for (_, info) in installedPods {
  97. if info.isSourcePod == false ||
  98. info.transitiveFrameworks != nil ||
  99. info.versionedPod.name == "Firebase" {
  100. continue
  101. }
  102. generate(framework: info)
  103. }
  104. }
  105. // MARK: - Internal Functions
  106. /// Build a module map for a single framework. A CocoaPod install is run to extract the required frameworks
  107. /// and libraries from the generated xcconfig. All previously installed dependent pods are put into the Podfile
  108. /// to make sure we install the right version and from the right location.
  109. private func generate(framework: FrameworkInfo) {
  110. let podName = framework.versionedPod.name
  111. let deps = CocoaPodUtils.transitiveVersionedPodDependencies(for: podName, in: allPods)
  112. _ = CocoaPodUtils.installPods(allSubspecList(framework: framework) + deps,
  113. inDir: projectDir,
  114. platform: platform,
  115. customSpecRepos: customSpecRepos,
  116. localPodspecPath: localPodspecPath,
  117. linkage: .forcedStatic)
  118. let xcconfigFile = projectDir.appendingPathComponents(["Pods", "Target Support Files",
  119. "Pods-FrameworkMaker",
  120. "Pods-FrameworkMaker.release.xcconfig"])
  121. allPods[podName]?
  122. .moduleMapContents = makeModuleMap(forFramework: framework, withXcconfigFile: xcconfigFile)
  123. }
  124. /// Convert a list of versioned pods to a list of versioned pods specified with all needed subspecs.
  125. private func allSubspecList(framework: FrameworkInfo) -> [CocoaPodUtils.VersionedPod] {
  126. let name = framework.versionedPod.name
  127. let version = framework.versionedPod.version
  128. let subspecs = framework.subspecs
  129. if subspecs.count == 0 {
  130. return [CocoaPodUtils.VersionedPod(name: "\(name)", version: version)]
  131. }
  132. var list: [CocoaPodUtils.VersionedPod] = []
  133. for subspec in framework.subspecs {
  134. list.append(CocoaPodUtils.VersionedPod(name: "\(name)/\(subspec)", version: version))
  135. }
  136. return list
  137. }
  138. // Extract the framework and library dependencies for a framework from
  139. // the xcconfig file from an app generated from a Podfile only with that
  140. // CocoaPod and removing the frameworks and libraries from its transitive dependencies.
  141. private func getModuleDependencies(withXcconfigFile xcconfigFile: URL) ->
  142. (frameworks: [String], libraries: [String]) {
  143. do {
  144. let text = try String(contentsOf: xcconfigFile)
  145. let lines = text.components(separatedBy: .newlines)
  146. for line in lines {
  147. if line.hasPrefix("OTHER_LDFLAGS =") {
  148. var dependencyFrameworks: [String] = []
  149. var dependencyLibraries: [String] = []
  150. let tokens = line.components(separatedBy: " ")
  151. var addNext = false
  152. for token in tokens {
  153. if addNext {
  154. dependencyFrameworks.append(token)
  155. addNext = false
  156. } else if token == "-framework" {
  157. addNext = true
  158. } else if token.hasPrefix("-l") {
  159. let index = token.index(token.startIndex, offsetBy: 2)
  160. dependencyLibraries.append(String(token[index...]))
  161. }
  162. }
  163. return (dependencyFrameworks, dependencyLibraries)
  164. }
  165. }
  166. } catch {
  167. fatalError("Failed to open \(xcconfigFile): \(error)")
  168. }
  169. return ([], [])
  170. }
  171. private func makeModuleMap(forFramework framework: FrameworkInfo,
  172. withXcconfigFile xcconfigFile: URL) -> ModuleMapContents {
  173. let name = framework.versionedPod.name
  174. let dependencies = getModuleDependencies(withXcconfigFile: xcconfigFile)
  175. let frameworkDeps = Set(dependencies.frameworks)
  176. let libraryDeps = Set(dependencies.libraries)
  177. var transitiveFrameworkDeps = frameworkDeps
  178. var transitiveLibraryDeps = libraryDeps
  179. var myFrameworkDeps = frameworkDeps
  180. var myLibraryDeps = libraryDeps
  181. // Iterate through the deps looking for pods. At time of authoring, TensorFlowLiteObjC is the
  182. // only pod in frameworkDeps instead of libraryDeps.
  183. for library in libraryDeps.union(frameworkDeps) {
  184. let lib = library.replacingOccurrences(of: "\"", with: "")
  185. if let dependencyPod = installedPods[lib] {
  186. myLibraryDeps.remove(library)
  187. myFrameworkDeps.remove(library)
  188. if lib == name {
  189. continue
  190. }
  191. var podFrameworkDeps = dependencyPod.transitiveFrameworks
  192. var podLibraryDeps = dependencyPod.transitiveLibraries
  193. if podFrameworkDeps == nil {
  194. generate(framework: dependencyPod)
  195. podFrameworkDeps = dependencyPod.transitiveFrameworks
  196. podLibraryDeps = dependencyPod.transitiveLibraries
  197. }
  198. if let fdeps = podFrameworkDeps {
  199. transitiveFrameworkDeps.formUnion(fdeps)
  200. }
  201. if let ldeps = podLibraryDeps {
  202. transitiveLibraryDeps.formUnion(ldeps)
  203. }
  204. }
  205. }
  206. installedPods[name]?.transitiveFrameworks = transitiveFrameworkDeps
  207. installedPods[name]?.transitiveLibraries = transitiveLibraryDeps
  208. return ModuleMapContents(module: name, frameworks: myFrameworkDeps, libraries: myLibraryDeps)
  209. }
  210. }