ModuleMapBuilder.swift 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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 framework in installedPods.values
  97. where framework.isSourcePod &&
  98. framework.versionedPod.name != "Firebase" &&
  99. framework.transitiveFrameworks == nil {
  100. generate(framework: framework)
  101. }
  102. }
  103. // MARK: - Internal Functions
  104. /// Build a module map for a single framework. A CocoaPod install is run to extract the required frameworks
  105. /// and libraries from the generated xcconfig. All previously installed dependent pods are put into the Podfile
  106. /// to make sure we install the right version and from the right location.
  107. private func generate(framework: FrameworkInfo) {
  108. let podName = framework.versionedPod.name
  109. let deps = CocoaPodUtils.transitiveVersionedPodDependencies(for: podName, in: allPods)
  110. CocoaPodUtils.installPods(allSubspecList(framework: framework) + deps,
  111. inDir: projectDir,
  112. platform: platform,
  113. customSpecRepos: customSpecRepos,
  114. localPodspecPath: localPodspecPath,
  115. linkage: .forcedStatic)
  116. let xcconfigFile = projectDir.appendingPathComponents(["Pods", "Target Support Files",
  117. "Pods-FrameworkMaker",
  118. "Pods-FrameworkMaker.release.xcconfig"])
  119. allPods[podName]?
  120. .moduleMapContents = makeModuleMap(forFramework: framework, withXcconfigFile: xcconfigFile)
  121. }
  122. /// Convert a list of versioned pods to a list of versioned pods specified with all needed subspecs.
  123. private func allSubspecList(framework: FrameworkInfo) -> [CocoaPodUtils.VersionedPod] {
  124. let name = framework.versionedPod.name
  125. let version = framework.versionedPod.version
  126. guard framework.subspecs.count > 0 else {
  127. return [CocoaPodUtils.VersionedPod(name: name, version: version)]
  128. }
  129. return framework.subspecs.map { subspec in
  130. CocoaPodUtils.VersionedPod(name: "\(name)/\(subspec)", version: version)
  131. }
  132. }
  133. // Extract the framework and library dependencies for a framework from
  134. // the xcconfig file from an app generated from a Podfile only with that
  135. // CocoaPod and removing the frameworks and libraries from its transitive dependencies.
  136. private func getModuleDependencies(withXcconfigFile xcconfigFile: URL) ->
  137. (frameworks: [String], libraries: [String]) {
  138. do {
  139. let text = try String(contentsOf: xcconfigFile)
  140. let lines = text.components(separatedBy: .newlines)
  141. for line in lines {
  142. if line.hasPrefix("OTHER_LDFLAGS =") {
  143. var dependencyFrameworks: [String] = []
  144. var dependencyLibraries: [String] = []
  145. let tokens = line.components(separatedBy: " ")
  146. var addNext = false
  147. for token in tokens {
  148. if addNext {
  149. dependencyFrameworks.append(token)
  150. addNext = false
  151. } else if token == "-framework" {
  152. addNext = true
  153. } else if token.hasPrefix("-l") {
  154. let index = token.index(token.startIndex, offsetBy: 2)
  155. dependencyLibraries.append(String(token[index...]))
  156. }
  157. }
  158. return (dependencyFrameworks, dependencyLibraries)
  159. }
  160. }
  161. } catch {
  162. fatalError("Failed to open \(xcconfigFile): \(error)")
  163. }
  164. return ([], [])
  165. }
  166. private func makeModuleMap(forFramework framework: FrameworkInfo,
  167. withXcconfigFile xcconfigFile: URL) -> ModuleMapContents {
  168. let name = framework.versionedPod.name
  169. let dependencies = getModuleDependencies(withXcconfigFile: xcconfigFile)
  170. let frameworkDeps = Set(dependencies.frameworks)
  171. let libraryDeps = Set(dependencies.libraries)
  172. var transitiveFrameworkDeps = frameworkDeps
  173. var transitiveLibraryDeps = libraryDeps
  174. var myFrameworkDeps = frameworkDeps
  175. var myLibraryDeps = libraryDeps
  176. // Iterate through the deps looking for pods. At time of authoring, TensorFlowLiteObjC is the
  177. // only pod in frameworkDeps instead of libraryDeps.
  178. for library in libraryDeps.union(frameworkDeps) {
  179. let lib = library.replacingOccurrences(of: "\"", with: "")
  180. if let dependencyPod = installedPods[lib] {
  181. myLibraryDeps.remove(library)
  182. myFrameworkDeps.remove(library)
  183. if lib == name {
  184. continue
  185. }
  186. var podFrameworkDeps = dependencyPod.transitiveFrameworks
  187. var podLibraryDeps = dependencyPod.transitiveLibraries
  188. if podFrameworkDeps == nil {
  189. generate(framework: dependencyPod)
  190. podFrameworkDeps = dependencyPod.transitiveFrameworks
  191. podLibraryDeps = dependencyPod.transitiveLibraries
  192. }
  193. if let fdeps = podFrameworkDeps {
  194. transitiveFrameworkDeps.formUnion(fdeps)
  195. }
  196. if let ldeps = podLibraryDeps {
  197. transitiveLibraryDeps.formUnion(ldeps)
  198. }
  199. }
  200. }
  201. installedPods[name]?.transitiveFrameworks = transitiveFrameworkDeps
  202. installedPods[name]?.transitiveLibraries = transitiveLibraryDeps
  203. return ModuleMapContents(module: name, frameworks: myFrameworkDeps, libraries: myLibraryDeps)
  204. }
  205. }