ModuleMapBuilder.swift 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. if module == "FirebaseFirestoreInternal" {
  45. content += """
  46. link framework "openssl_grpc"
  47. link framework "grpc"
  48. link framework "grpcpp"
  49. """
  50. }
  51. for framework in frameworks.sorted() {
  52. content += " link framework " + framework + "\n"
  53. }
  54. for library in libraries.sorted() {
  55. content += " link " + library + "\n"
  56. }
  57. // The empty line at the end is intentional, do not remove it.
  58. content += "}\n"
  59. contents = content
  60. }
  61. func get(umbrellaHeader: String) -> String {
  62. return contents.replacingOccurrences(of:
  63. ModuleMapBuilder.ModuleMapContents.umbrellaPlaceholder, with: umbrellaHeader)
  64. }
  65. }
  66. /// The directory containing the Xcode project and Pods folder.
  67. private let projectDir: URL
  68. /// Custom CocoaPods spec repos to be used. If not provided, the tool will only use the CocoaPods
  69. /// master repo.
  70. private let customSpecRepos: [URL]?
  71. /// Dictionary of all installed pods. Update moduleMapContents here.
  72. private var allPods: [String: CocoaPodUtils.PodInfo]
  73. /// Dictionary of installed pods required for this module.
  74. private var installedPods: [String: FrameworkInfo]
  75. /// The platform for this build.
  76. private let platform: Platform
  77. /// The path containing local podspec URLs, if specified.
  78. private let localPodspecPath: URL?
  79. /// Default initializer.
  80. init(customSpecRepos: [URL]?,
  81. selectedPods: [String: CocoaPodUtils.PodInfo],
  82. platform: Platform,
  83. paths: ZipBuilder.FilesystemPaths) {
  84. projectDir = FileManager.default.temporaryDirectory(withName: "module")
  85. CocoaPodUtils.podInstallPrepare(inProjectDir: projectDir, templateDir: paths.templateDir)
  86. self.customSpecRepos = customSpecRepos
  87. allPods = selectedPods
  88. var installedPods: [String: FrameworkInfo] = [:]
  89. for pod in selectedPods {
  90. let versionedPod = CocoaPodUtils.VersionedPod(name: pod.key, version: pod.value.version)
  91. installedPods[pod.key] = FrameworkInfo(isSourcePod: pod.value.isSourcePod,
  92. versionedPod: versionedPod,
  93. subspecs: pod.value.subspecs)
  94. }
  95. self.installedPods = installedPods
  96. self.platform = platform
  97. localPodspecPath = paths.localPodspecPath
  98. }
  99. // MARK: - Public Functions
  100. /// Build the module map files for the source frameworks.
  101. ///
  102. func build() {
  103. for framework in installedPods.values
  104. where framework.isSourcePod &&
  105. framework.versionedPod.name != "Firebase" &&
  106. framework.transitiveFrameworks == nil {
  107. generate(framework: framework)
  108. }
  109. }
  110. // MARK: - Internal Functions
  111. /// Build a module map for a single framework. A CocoaPod install is run to extract the required
  112. /// frameworks
  113. /// and libraries from the generated xcconfig. All previously installed dependent pods are put
  114. /// into the Podfile
  115. /// to make sure we install the right version and from the right location.
  116. private func generate(framework: FrameworkInfo) {
  117. let podName = framework.versionedPod.name
  118. let deps = CocoaPodUtils.transitiveVersionedPodDependencies(for: podName, in: allPods).filter {
  119. // Don't include Interop pods in the module map calculation since they shouldn't add anything
  120. // and it uses the platform-independent version of the dependency list, which causes a crash
  121. // for the iOS-only RecaptchaInterop pod when the subsequent code tries to `pod install` it
  122. // for macOS. All this module code should go away when we switch to building dynamic
  123. // frameworks.
  124. !$0.name.hasSuffix("Interop")
  125. }
  126. CocoaPodUtils.installPods(allSubspecList(framework: framework) + deps,
  127. inDir: projectDir,
  128. platform: platform,
  129. customSpecRepos: customSpecRepos,
  130. localPodspecPath: localPodspecPath,
  131. linkage: .forcedStatic)
  132. let xcconfigFile = projectDir.appendingPathComponents(["Pods", "Target Support Files",
  133. "Pods-FrameworkMaker",
  134. "Pods-FrameworkMaker.release.xcconfig"])
  135. allPods[podName]?
  136. .moduleMapContents = makeModuleMap(forFramework: framework, withXcconfigFile: xcconfigFile)
  137. }
  138. /// Convert a list of versioned pods to a list of versioned pods specified with all needed
  139. /// subspecs.
  140. private func allSubspecList(framework: FrameworkInfo) -> [CocoaPodUtils.VersionedPod] {
  141. let name = framework.versionedPod.name
  142. let version = framework.versionedPod.version
  143. guard framework.subspecs.count > 0 else {
  144. return [CocoaPodUtils.VersionedPod(name: name, version: version)]
  145. }
  146. return framework.subspecs.map { subspec in
  147. CocoaPodUtils.VersionedPod(name: "\(name)/\(subspec)", version: version)
  148. }
  149. }
  150. // Extract the framework and library dependencies for a framework from
  151. // the xcconfig file from an app generated from a Podfile only with that
  152. // CocoaPod and removing the frameworks and libraries from its transitive dependencies.
  153. private func getModuleDependencies(withXcconfigFile xcconfigFile: URL) ->
  154. (frameworks: [String], libraries: [String]) {
  155. do {
  156. let text = try String(contentsOf: xcconfigFile)
  157. let lines = text.components(separatedBy: .newlines)
  158. for line in lines {
  159. if line.hasPrefix("OTHER_LDFLAGS =") {
  160. var dependencyFrameworks: [String] = []
  161. var dependencyLibraries: [String] = []
  162. let tokens = line.components(separatedBy: " ")
  163. var addNext = false
  164. for token in tokens {
  165. if addNext {
  166. dependencyFrameworks.append(token)
  167. addNext = false
  168. } else if token == "-framework" {
  169. addNext = true
  170. } else if token.hasPrefix("-l") {
  171. let index = token.index(token.startIndex, offsetBy: 2)
  172. dependencyLibraries.append(String(token[index...]))
  173. }
  174. }
  175. return (dependencyFrameworks, dependencyLibraries)
  176. }
  177. }
  178. } catch {
  179. fatalError("Failed to open \(xcconfigFile): \(error)")
  180. }
  181. return ([], [])
  182. }
  183. private func makeModuleMap(forFramework framework: FrameworkInfo,
  184. withXcconfigFile xcconfigFile: URL) -> ModuleMapContents {
  185. let name = framework.versionedPod.name
  186. let dependencies = getModuleDependencies(withXcconfigFile: xcconfigFile)
  187. let frameworkDeps = Set(dependencies.frameworks)
  188. let libraryDeps = Set(dependencies.libraries)
  189. var transitiveFrameworkDeps = frameworkDeps
  190. var transitiveLibraryDeps = libraryDeps
  191. var myFrameworkDeps = frameworkDeps
  192. var myLibraryDeps = libraryDeps
  193. // Iterate through the deps looking for pods. At time of authoring, TensorFlowLiteObjC is the
  194. // only pod in frameworkDeps instead of libraryDeps.
  195. for library in libraryDeps.union(frameworkDeps) {
  196. let lib = library.replacingOccurrences(of: "\"", with: "")
  197. if let dependencyPod = installedPods[lib] {
  198. myLibraryDeps.remove(library)
  199. myFrameworkDeps.remove(library)
  200. if lib == name {
  201. continue
  202. }
  203. var podFrameworkDeps = dependencyPod.transitiveFrameworks
  204. var podLibraryDeps = dependencyPod.transitiveLibraries
  205. if podFrameworkDeps == nil {
  206. generate(framework: dependencyPod)
  207. podFrameworkDeps = dependencyPod.transitiveFrameworks
  208. podLibraryDeps = dependencyPod.transitiveLibraries
  209. }
  210. if let fdeps = podFrameworkDeps {
  211. transitiveFrameworkDeps.formUnion(fdeps)
  212. }
  213. if let ldeps = podLibraryDeps {
  214. transitiveLibraryDeps.formUnion(ldeps)
  215. }
  216. }
  217. }
  218. installedPods[name]?.transitiveFrameworks = transitiveFrameworkDeps
  219. installedPods[name]?.transitiveLibraries = transitiveLibraryDeps
  220. let moduleName = FrameworkBuilder.frameworkBuildName(name)
  221. return ModuleMapContents(
  222. module: moduleName,
  223. frameworks: myFrameworkDeps,
  224. libraries: myLibraryDeps
  225. )
  226. }
  227. }