| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851 |
- /*
- * Copyright 2019 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import Foundation
- import FirebaseManifest
- /// Misc. constants used in the build tool.
- struct Constants {
- /// Constants related to the Xcode project template.
- struct ProjectPath {
- // Required for building.
- static let infoPlist = "Info.plist"
- static let projectFile = "FrameworkMaker.xcodeproj"
- /// All required files for building the Zip file.
- static let requiredFilesForBuilding: [String] = [projectFile, infoPlist]
- // Required for distribution.
- static let readmeName = "README.md"
- // Required from the Firebase pod.
- static let firebaseHeader = "Firebase.h"
- static let modulemap = "module.modulemap"
- /// The dummy Firebase library for Carthage distribution.
- static let dummyFirebaseLib = "dummy_Firebase_lib"
- // Make the struct un-initializable.
- @available(*, unavailable)
- init() { fatalError() }
- }
- /// The text added to the README for a product if it contains Resources. The empty line at the end
- /// is intentional.
- static let resourcesRequiredText = """
- You'll also need to add the resources in the Resources
- directory into your target's main bundle.
- """
- // Make the struct un-initializable.
- @available(*, unavailable)
- init() { fatalError() }
- }
- /// A zip file builder. The zip file can be built with the `buildAndAssembleReleaseDir()` function.
- struct ZipBuilder {
- /// Artifacts from building and assembling the release directory.
- struct ReleaseArtifacts {
- /// The Firebase version.
- let firebaseVersion: String
- /// The directory that contains the properly assembled release artifacts.
- let zipDir: URL
- /// The directory that contains the properly assembled release artifacts for Carthage if building it.
- let carthageDir: URL?
- }
- /// Relevant paths in the filesystem to build the release directory.
- struct FilesystemPaths {
- // MARK: - Required Paths
- /// The root of the `firebase-ios-sdk` git repo.
- let repoDir: URL
- /// The path to the directory containing the blank xcodeproj and Info.plist for building source
- /// based frameworks. Generated based on the `repoDir`.
- var templateDir: URL {
- return type(of: self).templateDir(fromRepoDir: repoDir)
- }
- // MARK: - Optional Paths
- /// The root directory for build artifacts. If `nil`, a temporary directory will be used.
- let buildRoot: URL?
- /// The output directory for any artifacts generated during the build. If `nil`, a temporary
- /// directory will be used.
- let outputDir: URL?
- /// The path to where local podspecs are stored.
- let localPodspecPath: URL?
- /// The path to a directory to move all build logs to. If `nil`, a temporary directory will be
- /// used.
- var logsOutputDir: URL?
- /// Creates the struct containing all properties needed for a build.
- /// - Parameter repoDir: The root of the `firebase-ios-sdk` git repo.
- /// - Parameter buildRoot: The root directory for build artifacts. If `nil`, a temporary
- /// directory will be used.
- /// - Parameter outputDir: The output directory for any artifacts generated. If `nil`, a
- /// temporary directory will be used.
- /// - Parameter localPodspecPath: A path to where local podspecs are stored.
- /// - Parameter logsOutputDir: The output directory for any logs. If `nil`, a temporary
- /// directory will be used.
- init(repoDir: URL,
- buildRoot: URL?,
- outputDir: URL?,
- localPodspecPath: URL?,
- logsOutputDir: URL?) {
- self.repoDir = repoDir
- self.buildRoot = buildRoot
- self.outputDir = outputDir
- self.localPodspecPath = localPodspecPath
- self.logsOutputDir = logsOutputDir
- }
- /// Returns the expected template directory given the repo directory provided.
- static func templateDir(fromRepoDir repoDir: URL) -> URL {
- return repoDir.appendingPathComponents(["ReleaseTooling", "Template"])
- }
- }
- /// Paths needed throughout the process of packaging the Zip file.
- public let paths: FilesystemPaths
- /// The platforms to target for the builds.
- public let platforms: [Platform]
- /// Specifies if the builder is building dynamic frameworks instead of static frameworks.
- private let dynamicFrameworks: Bool
- /// Custom CocoaPods spec repos to be used. If not provided, the tool will only use the CocoaPods
- /// master repo.
- private let customSpecRepos: [URL]?
- /// Creates a ZipBuilder struct to build and assemble zip files and Carthage builds.
- ///
- /// - Parameters:
- /// - paths: Paths that are needed throughout the process of packaging the Zip file.
- /// - platforms: The platforms to target for the builds.
- /// - dynamicFrameworks: Specifies if dynamic frameworks should be built, otherwise static
- /// frameworks are built.
- /// - customSpecRepo: A custom spec repo to be used for fetching CocoaPods from.
- init(paths: FilesystemPaths,
- platforms: [Platform],
- dynamicFrameworks: Bool,
- customSpecRepos: [URL]? = nil) {
- self.paths = paths
- self.platforms = platforms
- self.customSpecRepos = customSpecRepos
- self.dynamicFrameworks = dynamicFrameworks
- }
- /// Builds and assembles the contents for the zip build.
- ///
- /// - Parameter podsToInstall: All pods to install.
- /// - Parameter includeCarthage: Build Carthage distribution as well.
- /// - Parameter includeDependencies: Include dependencies of requested pod in distribution.
- /// - Returns: Arrays of pod install info and the frameworks installed.
- func buildAndAssembleZip(podsToInstall: [CocoaPodUtils.VersionedPod],
- includeCarthage: Bool,
- includeDependencies: Bool) ->
- ([String: CocoaPodUtils.PodInfo], [String: [URL]], URL?) {
- // Remove CocoaPods cache so the build gets updates after a version is rebuilt during the
- // release process. Always do this, since it can be the source of subtle failures on rebuilds.
- CocoaPodUtils.cleanPodCache()
- // We need to install all the pods in order to get every single framework that we'll need
- // for the zip file. We can't install each one individually since some pods depend on different
- // subspecs from the same pod (ex: GoogleUtilities, GoogleToolboxForMac, etc). All of the code
- // wouldn't be included so we need to install all of the subspecs to catch the superset of all
- // required frameworks, then use that as the source of frameworks to pull from when including
- // the folders in each product directory.
- let linkage: CocoaPodUtils.LinkageType = dynamicFrameworks ? .dynamic : .standardStatic
- var groupedFrameworks: [String: [URL]] = [:]
- var carthageCoreDiagnosticsFrameworks: [URL] = []
- var podsBuilt: [String: CocoaPodUtils.PodInfo] = [:]
- var xcframeworks: [String: [URL]] = [:]
- var resources: [String: URL] = [:]
- for platform in platforms {
- let projectDir = FileManager.default.temporaryDirectory(withName: "project-" + platform.name)
- CocoaPodUtils.podInstallPrepare(inProjectDir: projectDir, templateDir: paths.templateDir)
- let platformPods = podsToInstall.filter { $0.platforms.contains(platform.name) }
- CocoaPodUtils.installPods(platformPods,
- inDir: projectDir,
- platform: platform,
- customSpecRepos: customSpecRepos,
- localPodspecPath: paths.localPodspecPath,
- linkage: linkage)
- // Find out what pods were installed with the above commands.
- let installedPods = CocoaPodUtils.installedPodsInfo(inProjectDir: projectDir,
- localPodspecPath: paths.localPodspecPath)
- // If module maps are needed for static frameworks, build them here to be available to copy
- // into the generated frameworks.
- if !dynamicFrameworks {
- ModuleMapBuilder(customSpecRepos: customSpecRepos,
- selectedPods: installedPods,
- platform: platform,
- paths: paths).build()
- }
- let podsToBuild = includeDependencies ? installedPods : installedPods.filter {
- platformPods.map { $0.name.components(separatedBy: "/").first }.contains($0.key)
- }
- // Build in a sorted order to make the build deterministic and to avoid exposing random
- // build order bugs.
- // Also AppCheck must be built after other pods so that its restricted architecture
- // selection does not restrict any of its dependencies.
- var sortedPods = podsToBuild.keys.sorted()
- // Interop pods are protocols only and should not be built.
- sortedPods.removeAll(where: { value in
- value.hasSuffix("Interop")
- })
- sortedPods.removeAll(where: { value in
- value == "FirebaseAppCheck"
- })
- sortedPods.append("FirebaseAppCheck")
- for podName in sortedPods {
- guard let podInfo = podsToBuild[podName] else {
- continue
- }
- if podName == "Firebase" {
- // Don't build the Firebase pod.
- } else if podInfo.isSourcePod {
- let builder = FrameworkBuilder(projectDir: projectDir,
- targetPlatforms: platform.platformTargets,
- dynamicFrameworks: dynamicFrameworks)
- let (frameworks, resourceContents) =
- builder.compileFrameworkAndResources(withName: podName,
- logsOutputDir: paths.logsOutputDir,
- setCarthage: false,
- podInfo: podInfo)
- groupedFrameworks[podName] = (groupedFrameworks[podName] ?? []) + frameworks
- if includeCarthage, podName == "FirebaseCoreDiagnostics" {
- let (cdFrameworks, _) = builder.compileFrameworkAndResources(withName: podName,
- logsOutputDir: paths
- .logsOutputDir,
- setCarthage: true,
- podInfo: podInfo)
- carthageCoreDiagnosticsFrameworks += cdFrameworks
- }
- if resourceContents != nil {
- resources[podName] = resourceContents
- }
- } else if podsBuilt[podName] == nil {
- // Binary pods need to be collected once, since the platforms should already be merged.
- let binaryFrameworks = collectBinaryFrameworks(fromPod: podName, podInfo: podInfo)
- xcframeworks[podName] = binaryFrameworks
- }
- // Union all pods built across platforms.
- // Be conservative and favor iOS if it exists - and workaround
- // bug where Firebase.h doesn't get installed for tvOS and macOS.
- // Fixed in #7284.
- if podsBuilt[podName] == nil {
- podsBuilt[podName] = podInfo
- }
- }
- }
- // Now consolidate the built frameworks for all platforms into a single xcframework.
- let xcframeworksDir = FileManager.default.temporaryDirectory(withName: "xcframeworks")
- do {
- try FileManager.default.createDirectory(at: xcframeworksDir,
- withIntermediateDirectories: false)
- } catch {
- fatalError("Could not create XCFrameworks directory: \(error)")
- }
- for groupedFramework in groupedFrameworks {
- let name = groupedFramework.key
- let xcframework = FrameworkBuilder.makeXCFramework(withName: name,
- frameworks: groupedFramework.value,
- xcframeworksDir: xcframeworksDir,
- resourceContents: resources[name])
- xcframeworks[name] = [xcframework]
- }
- for (framework, paths) in xcframeworks {
- print("Frameworks for pod: \(framework) were compiled at \(paths)")
- }
- guard includeCarthage else {
- // No Carthage build necessary, return now.
- return (podsBuilt, xcframeworks, nil)
- }
- let xcframeworksCarthageDir = FileManager.default.temporaryDirectory(withName: "xcf-carthage")
- do {
- try FileManager.default.createDirectory(at: xcframeworksCarthageDir,
- withIntermediateDirectories: false)
- } catch {
- fatalError("Could not create XCFrameworks Carthage directory: \(error)")
- }
- let carthageCoreDiagnosticsXcframework = FrameworkBuilder.makeXCFramework(
- withName: "FirebaseCoreDiagnostics",
- frameworks: carthageCoreDiagnosticsFrameworks,
- xcframeworksDir: xcframeworksCarthageDir,
- resourceContents: nil
- )
- return (podsBuilt, xcframeworks, carthageCoreDiagnosticsXcframework)
- }
- /// Try to build and package the contents of the Zip file. This will throw an error as soon as it
- /// encounters an error, or will quit due to a fatal error with the appropriate log.
- ///
- /// - Parameter templateDir: The template project for pod install.
- /// - Throws: One of many errors that could have happened during the build phase.
- func buildAndAssembleFirebaseRelease(templateDir: URL) throws -> ReleaseArtifacts {
- let manifest = FirebaseManifest.shared
- var podsToInstall = manifest.pods.map {
- CocoaPodUtils.VersionedPod(name: $0.name,
- version: manifest.versionString($0),
- platforms: $0.platforms)
- }
- guard !podsToInstall.isEmpty else {
- fatalError("Failed to find versions for Firebase release")
- }
- // We don't release Google-Mobile-Ads-SDK and GoogleSignIn, but we include their latest
- // version for convenience in the Zip and Carthage builds.
- podsToInstall.append(CocoaPodUtils.VersionedPod(name: "Google-Mobile-Ads-SDK",
- version: nil,
- platforms: ["ios"]))
- podsToInstall.append(CocoaPodUtils.VersionedPod(name: "GoogleSignIn",
- version: nil,
- platforms: ["ios"]))
- print("Final expected versions for the Zip file: \(podsToInstall)")
- let (installedPods, frameworks, carthageCoreDiagnosticsXcframeworkFirebase) =
- buildAndAssembleZip(podsToInstall: podsToInstall,
- includeCarthage: true,
- // Always include dependencies for Firebase zips.
- includeDependencies: true)
- // We need the Firebase pod to get the version for Carthage and to copy the `Firebase.h` and
- // `module.modulemap` file from it.
- guard let firebasePod = installedPods["Firebase"] else {
- fatalError("Could not get the Firebase pod from list of installed pods. All pods " +
- "installed: \(installedPods)")
- }
- guard let carthageCoreDiagnosticsXcframework = carthageCoreDiagnosticsXcframeworkFirebase else {
- fatalError("CoreDiagnosticsXcframework is missing")
- }
- let zipDir = try assembleDistributions(withPackageKind: "Firebase",
- podsToInstall: podsToInstall,
- installedPods: installedPods,
- frameworksToAssemble: frameworks,
- firebasePod: firebasePod)
- // Replace Core Diagnostics
- var carthageFrameworks = frameworks
- carthageFrameworks["FirebaseCoreDiagnostics"] = [carthageCoreDiagnosticsXcframework]
- let carthageDir = try assembleDistributions(withPackageKind: "CarthageFirebase",
- podsToInstall: podsToInstall,
- installedPods: installedPods,
- frameworksToAssemble: carthageFrameworks,
- firebasePod: firebasePod)
- return ReleaseArtifacts(firebaseVersion: firebasePod.version,
- zipDir: zipDir, carthageDir: carthageDir)
- }
- // MARK: - Private
- /// Assemble the folder structure of the Zip file. In order to get the frameworks
- /// required, we will `pod install` only those subspecs and then fetch the information for all
- /// the frameworks that were installed, copying the frameworks from our list of compiled
- /// frameworks. The whole process is:
- /// 1. Copy any required files (headers, modulemap, etc) over beforehand to fail fast if anything
- /// is misconfigured.
- /// 2. Get the frameworks required for Analytics, copy them to the Analytics folder.
- /// 3. Go through the rest of the subspecs (excluding those included in Analytics) and copy them
- /// to a folder with the name of the subspec.
- /// 4. Assemble the `README` file based off the template and copy it to the directory.
- /// 5. Return the URL of the folder containing the contents of the Zip file.
- ///
- /// - Returns: Return the URL of the folder containing the contents of the Zip or Carthage distribution.
- /// - Throws: One of many errors that could have happened during the build phase.
- private func assembleDistributions(withPackageKind packageKind: String,
- podsToInstall: [CocoaPodUtils.VersionedPod],
- installedPods: [String: CocoaPodUtils.PodInfo],
- frameworksToAssemble: [String: [URL]],
- firebasePod: CocoaPodUtils.PodInfo) throws -> URL {
- // Create the directory that will hold all the contents of the Zip file.
- let fileManager = FileManager.default
- let zipDir = fileManager.temporaryDirectory(withName: packageKind)
- do {
- if fileManager.directoryExists(at: zipDir) {
- try fileManager.removeItem(at: zipDir)
- }
- try fileManager.createDirectory(at: zipDir,
- withIntermediateDirectories: true,
- attributes: nil)
- }
- // Copy all required files from the Firebase pod. This will cause a fatalError if anything
- // fails.
- copyFirebasePodFiles(fromDir: firebasePod.installedLocation, to: zipDir)
- // Start with installing Analytics, since we'll need to exclude those frameworks from the rest
- // of the folders.
- let analyticsFrameworks: [String]
- let analyticsDir: URL
- do {
- // This returns the Analytics directory and a list of framework names that Analytics requires.
- /// Example: ["FirebaseInstallations, "GoogleAppMeasurement", "nanopb", <...>]
- let (dir, frameworks) = try installAndCopyFrameworks(forPod: "FirebaseAnalyticsSwift",
- inFolder: "FirebaseAnalytics",
- withInstalledPods: installedPods,
- rootZipDir: zipDir,
- builtFrameworks: frameworksToAssemble)
- analyticsFrameworks = frameworks
- analyticsDir = dir
- } catch {
- fatalError("Could not copy frameworks from Analytics into the zip file: \(error)")
- }
- // Start the README dependencies string with the frameworks built in Analytics.
- var readmeDeps = dependencyString(for: "FirebaseAnalyticsSwift",
- in: analyticsDir,
- frameworks: analyticsFrameworks)
- // Loop through all the other subspecs that aren't Core and Analytics and write them to their
- // final destination, including resources.
- let analyticsPods = analyticsFrameworks.map {
- $0.replacingOccurrences(of: ".framework", with: "")
- }
- let manifest = FirebaseManifest.shared
- let firebaseZipPods = manifest.pods.filter { $0.zip }.map { $0.name }
- // Skip Analytics and the pods bundled with it.
- let remainingPods = installedPods.filter {
- $0.key == "Google-Mobile-Ads-SDK" ||
- $0.key == "GoogleSignIn" ||
- (firebaseZipPods.contains($0.key) &&
- $0.key != "FirebaseAnalyticsSwift" &&
- $0.key != "Firebase" &&
- podsToInstall.map { $0.name }.contains($0.key))
- }.sorted { $0.key < $1.key }
- for pod in remainingPods {
- let folder = pod.key.replacingOccurrences(of: "Swift", with: "")
- do {
- if frameworksToAssemble[pod.key] == nil {
- // Continue if the pod wasn't built.
- continue
- }
- let (productDir, podFrameworks) =
- try installAndCopyFrameworks(forPod: pod.key,
- inFolder: folder,
- withInstalledPods: installedPods,
- rootZipDir: zipDir,
- builtFrameworks: frameworksToAssemble,
- podsToIgnore: analyticsPods)
- // Update the README.
- readmeDeps += dependencyString(for: folder, in: productDir, frameworks: podFrameworks)
- } catch {
- fatalError("Could not copy frameworks from \(pod) into the zip file: \(error)")
- }
- do {
- // Update Resources: For the zip distribution, they get pulled from the xcframework to the
- // top-level product directory. For the Carthage distribution, they propagate to each
- // individual framework.
- // TODO: Investigate changing the zip distro to also have Resources in the .frameworks to
- // enable different platform Resources.
- let productPath = zipDir.appendingPathComponent(folder)
- let contents = try fileManager.contentsOfDirectory(atPath: productPath.path)
- for fileOrFolder in contents {
- let xcPath = productPath.appendingPathComponent(fileOrFolder)
- let xcResourceDir = xcPath.appendingPathComponent("Resources")
- // Ignore anything that not an xcframework with Resources
- guard fileManager.isDirectory(at: xcPath),
- xcPath.lastPathComponent.hasSuffix("xcframework"),
- fileManager.directoryExists(at: xcResourceDir) else { continue }
- if packageKind == "Firebase" {
- // Move all the bundles in the frameworks out to a common "Resources" directory to
- // match the existing Zip structure.
- let resourcesDir = productPath.appendingPathComponent("Resources")
- try fileManager.moveItem(at: xcResourceDir, to: resourcesDir)
- } else {
- let xcContents = try fileManager.contentsOfDirectory(atPath: xcPath.path)
- for fileOrFolder in xcContents {
- let platformPath = xcPath.appendingPathComponent(fileOrFolder)
- guard fileManager.isDirectory(at: platformPath) else { continue }
- let platformContents = try fileManager.contentsOfDirectory(atPath: platformPath.path)
- for fileOrFolder in platformContents {
- let frameworkPath = platformPath.appendingPathComponent(fileOrFolder)
- // Ignore anything that not a framework.
- guard fileManager.isDirectory(at: frameworkPath),
- frameworkPath.lastPathComponent.hasSuffix("framework") else { continue }
- let resourcesDir = frameworkPath.appendingPathComponent("Resources")
- try fileManager.copyItem(at: xcResourceDir, to: resourcesDir)
- }
- }
- try fileManager.removeItem(at: xcResourceDir)
- }
- }
- } catch {
- fatalError("Could not setup Resources for \(pod) for \(packageKind) \(error)")
- }
- // Special case for Crashlytics:
- // Copy additional tools to avoid users from downloading another artifact to upload symbols.
- let crashlyticsPodName = "FirebaseCrashlytics"
- if pod.key == crashlyticsPodName {
- for file in ["upload-symbols", "run"] {
- let source = pod.value.installedLocation.appendingPathComponent(file)
- let target = zipDir.appendingPathComponent(crashlyticsPodName)
- .appendingPathComponent(file)
- do {
- try fileManager.copyItem(at: source, to: target)
- } catch {
- fatalError("Error copying Crashlytics tools from \(source) to \(target): \(error)")
- }
- }
- }
- }
- // Assemble the README. Start with the version text, then use the template to inject the
- // versions and the list of frameworks to include for each pod.
- let readmePath = paths.templateDir.appendingPathComponent(Constants.ProjectPath.readmeName)
- let readmeTemplate: String
- do {
- readmeTemplate = try String(contentsOf: readmePath)
- } catch {
- fatalError("Could not get contents of the README template: \(error)")
- }
- let versionsText = versionsString(for: installedPods)
- let readmeText = readmeTemplate.replacingOccurrences(of: "__INTEGRATION__", with: readmeDeps)
- .replacingOccurrences(of: "__VERSIONS__", with: versionsText)
- do {
- try readmeText.write(to: zipDir.appendingPathComponent(Constants.ProjectPath.readmeName),
- atomically: true,
- encoding: .utf8)
- } catch {
- fatalError("Could not write README to Zip directory: \(error)")
- }
- print("Contents of the packaged release were assembled at: \(zipDir)")
- return zipDir
- }
- /// Copies all frameworks from the `InstalledPod` (pulling from the `frameworkLocations`) and copy
- /// them to the destination directory.
- ///
- /// - Parameters:
- /// - installedPods: Names of all the pods installed, which will be used as a
- /// list to find out what frameworks to copy to the destination.
- /// - dir: Destination directory for all the frameworks.
- /// - frameworkLocations: A dictionary containing the pod name as the key and a location to
- /// the compiled frameworks.
- /// - ignoreFrameworks: A list of Pod
- /// - Returns: The filenames of the frameworks that were copied.
- /// - Throws: Various FileManager errors in case the copying fails, or an error if the framework
- /// doesn't exist in `frameworkLocations`.
- @discardableResult
- func copyFrameworks(fromPods installedPods: [String],
- toDirectory dir: URL,
- frameworkLocations: [String: [URL]],
- podsToIgnore: [String] = []) throws -> [String] {
- let fileManager = FileManager.default
- if !fileManager.directoryExists(at: dir) {
- try fileManager.createDirectory(at: dir, withIntermediateDirectories: false, attributes: nil)
- }
- // Keep track of the names of the frameworks copied over.
- var copiedFrameworkNames: [String] = []
- // Loop through each installedPod item and get the name so we can fetch the framework and copy
- // it to the destination directory.
- for podName in installedPods {
- // Skip the Firebase pod and specifically ignored frameworks.
- guard podName != "Firebase",
- !podName.hasSuffix("Interop"),
- !podsToIgnore.contains(podName) else {
- continue
- }
- guard let xcframeworks = frameworkLocations[podName] else {
- let reason = "Unable to find frameworks for \(podName) in cache of frameworks built to " +
- "include in the Zip file for that framework's folder."
- let error = NSError(domain: "com.firebase.zipbuilder",
- code: 1,
- userInfo: [NSLocalizedDescriptionKey: reason])
- throw error
- }
- // Copy each of the frameworks over, unless it's explicitly ignored.
- for xcframework in xcframeworks {
- let xcframeworkName = xcframework.lastPathComponent
- let destination = dir.appendingPathComponent(xcframeworkName)
- try fileManager.copyItem(at: xcframework, to: destination)
- copiedFrameworkNames
- .append(xcframeworkName.replacingOccurrences(of: ".xcframework", with: ""))
- }
- }
- return copiedFrameworkNames
- }
- /// Copies required files from the Firebase pod (`Firebase.h`, `module.modulemap`, and `NOTICES`) into
- /// the given `zipDir`. Will cause a fatalError if anything fails since the zip file can't exist
- /// without these files.
- private func copyFirebasePodFiles(fromDir firebasePodDir: URL, to zipDir: URL) {
- let firebasePodFiles = ["NOTICES", "Sources/" + Constants.ProjectPath.firebaseHeader,
- "Sources/" + Constants.ProjectPath.modulemap]
- let firebaseFiles = firebasePodDir.appendingPathComponent("CoreOnly")
- let firebaseFilesToCopy = firebasePodFiles.map {
- firebaseFiles.appendingPathComponent($0)
- }
- // Copy each Firebase file.
- for file in firebaseFilesToCopy {
- // Each file should be copied to the destination project directory with the same name.
- let destination = zipDir.appendingPathComponent(file.lastPathComponent)
- do {
- if !FileManager.default.fileExists(atPath: destination.path) {
- print("Copying final distribution file \(file) to \(destination)...")
- try FileManager.default.copyItem(at: file, to: destination)
- }
- } catch {
- fatalError("Could not copy final distribution files to temporary directory before " +
- "building. Failed while attempting to copy \(file) to \(destination). \(error)")
- }
- }
- }
- /// Creates the String required for this pod to be added to the README. Creates a header and
- /// lists each framework in alphabetical order with the appropriate indentation, as well as a
- /// message about resources if they exist.
- ///
- /// - Parameters:
- /// - subspec: The subspec that requires documentation.
- /// - frameworks: All the frameworks required by the subspec.
- /// - includesResources: A flag to include or exclude the text for adding Resources.
- /// - Returns: A string with a header for the subspec name, and a list of frameworks required to
- /// integrate for the product to work. Formatted and ready for insertion into the
- /// README.
- private func dependencyString(for podName: String, in dir: URL, frameworks: [String]) -> String {
- var result = readmeHeader(podName: podName)
- for framework in frameworks.sorted() {
- // The .xcframework suffix has been stripped. The .framework suffix has not been.
- if framework.hasSuffix(".framework") {
- result += "- \(framework)\n"
- } else {
- result += "- \(framework).xcframework\n"
- }
- }
- result += "\n" // Necessary for Resource message to print properly in markdown.
- // Check if there is a Resources directory, and if so, add the disclaimer to the dependency
- // string.
- do {
- let fileManager = FileManager.default
- let resourceDirs = try fileManager.recursivelySearch(for: .directories(name: "Resources"),
- in: dir)
- if !resourceDirs.isEmpty {
- result += Constants.resourcesRequiredText
- result += "\n" // Separate from next pod in listing for text version.
- }
- } catch {
- fatalError("""
- Tried to find Resources directory for \(podName) in order to build the README, but an error
- occurred: \(error).
- """)
- }
- return result
- }
- /// Describes the dependency on other frameworks for the README file.
- func readmeHeader(podName: String) -> String {
- var header = "## \(podName)"
- if !(podName == "FirebaseAnalytics" || podName == "GoogleSignIn") {
- header += " (~> FirebaseAnalytics)"
- }
- header += "\n"
- return header
- }
- /// Installs a subspec and attempts to copy all the frameworks required for it from
- /// `buildFramework` and puts them into a new directory in the `rootZipDir` matching the
- /// subspec's name.
- ///
- /// - Parameters:
- /// - subspec: The subspec to install and get the dependencies list.
- /// - projectDir: Root of the project containing the Podfile.
- /// - rootZipDir: The root directory to be turned into the Zip file.
- /// - builtFrameworks: All frameworks that have been built, with the framework name as the key
- /// and the framework's location as the value.
- /// - podsToIgnore: Pods to avoid copying, if any.
- /// - Throws: Throws various errors from copying frameworks.
- /// - Returns: The product directory containing all frameworks and the names of the frameworks
- /// that were copied for this subspec.
- @discardableResult
- func installAndCopyFrameworks(forPod podName: String,
- inFolder folder: String,
- withInstalledPods installedPods: [String: CocoaPodUtils.PodInfo],
- rootZipDir: URL,
- builtFrameworks: [String: [URL]],
- podsToIgnore: [String] = []) throws -> (productDir: URL,
- frameworks: [String]) {
- let podsToCopy = [podName] +
- CocoaPodUtils.transitiveMasterPodDependencies(for: podName, in: installedPods)
- // Remove any duplicates from the `podsToCopy` array. The easiest way to do this is to wrap it
- // in a set then back to an array.
- let dedupedPods = Array(Set(podsToCopy))
- // Copy the frameworks into the proper product directory.
- let productDir = rootZipDir.appendingPathComponent(folder)
- let namedFrameworks = try copyFrameworks(fromPods: dedupedPods,
- toDirectory: productDir,
- frameworkLocations: builtFrameworks,
- podsToIgnore: podsToIgnore)
- let copiedFrameworks = namedFrameworks.filter {
- // Skip frameworks that aren't contained in the "podsToIgnore" array and the Firebase pod.
- !(podsToIgnore.contains($0) || $0 == "Firebase")
- }
- return (productDir, copiedFrameworks)
- }
- /// Creates the String that displays all the versions of each pod, in alphabetical order.
- ///
- /// - Parameter pods: All pods that were installed, with their versions.
- /// - Returns: A String to be added to the README.
- private func versionsString(for pods: [String: CocoaPodUtils.PodInfo]) -> String {
- // Get the longest name in order to generate padding with spaces so it looks nicer.
- let maxLength: Int = {
- guard let pod = pods.keys.max(by: { $0.count < $1.count }) else {
- // The longest pod as of this writing is 29 characters, if for whatever reason this fails
- // just assume 30 characters long.
- return 30
- }
- // Return room for a space afterwards.
- return pod.count + 1
- }()
- let header: String = {
- // Center the CocoaPods title within the spaces given. If there's an odd number of spaces, add
- // the extra space after the CocoaPods title.
- let cocoaPods = "CocoaPod"
- let spacesToPad = maxLength - cocoaPods.count
- let halfPadding = String(repeating: " ", count: spacesToPad / 2)
- // Start with the spaces padding, then add the CocoaPods title.
- var result = halfPadding + cocoaPods + halfPadding
- if spacesToPad % 2 != 0 {
- // Add an extra space since the padding isn't even
- result += " "
- }
- // Add the versioning text and return.
- result += "| Version\n"
- // Add a line underneath each.
- result += String(repeating: "-", count: maxLength) + "|" + String(repeating: "-", count: 9)
- result += "\n"
- return result
- }()
- // Sort the pods by name for a cleaner display.
- let sortedPods = pods.sorted { $0.key < $1.key }
- // Get the name and version of each pod, padding it along the way.
- var podVersions = ""
- for pod in sortedPods {
- // Insert the name and enough spaces to reach the end of the column.
- let podName = pod.key
- podVersions += podName + String(repeating: " ", count: maxLength - podName.count)
- // Add a pipe and the version.
- podVersions += "| " + pod.value.version + "\n"
- }
- return header + podVersions
- }
- // MARK: - Framework Generation
- /// Collects the .framework and .xcframeworks files from the binary pods. This will go through
- /// the contents of the directory and copy the .frameworks to a temporary directory. Returns a
- /// dictionary with the framework name for the key and all information for frameworks to install
- /// EXCLUDING resources, as they are handled later (if not included in the .framework file
- /// already).
- private func collectBinaryFrameworks(fromPod podName: String,
- podInfo: CocoaPodUtils.PodInfo) -> [URL] {
- // Verify the Pods folder exists and we can get the contents of it.
- let fileManager = FileManager.default
- // Create the temporary directory we'll be storing the build/assembled frameworks in, and remove
- // the Resources directory if it already exists.
- let binaryZipDir = fileManager.temporaryDirectory(withName: "binary_zip")
- do {
- try fileManager.createDirectory(at: binaryZipDir,
- withIntermediateDirectories: true,
- attributes: nil)
- } catch {
- fatalError("Cannot create temporary directory to store binary frameworks: \(error)")
- }
- var frameworks: [URL] = []
- // TODO: packageAllResources is disabled for binary frameworks since it's not needed for Firebase
- // and it does not yet support xcframeworks.
- // Package all resources into the frameworks since that's how Carthage needs it packaged.
- // do {
- // // TODO: Figure out if we need to exclude bundles here or not.
- // try ResourcesManager.packageAllResources(containedIn: podInfo.installedLocation)
- // } catch {
- // fatalError("Tried to package resources for \(podName) but it failed: \(error)")
- // }
- // Copy each of the frameworks to a known temporary directory and store the location.
- for framework in podInfo.binaryFrameworks {
- // Copy it to the temporary directory and save it to our list of frameworks.
- let zipLocation = binaryZipDir.appendingPathComponent(framework.lastPathComponent)
- // Remove the framework if it exists since it could be out of date.
- fileManager.removeIfExists(at: zipLocation)
- do {
- try fileManager.copyItem(at: framework, to: zipLocation)
- } catch {
- fatalError("Cannot copy framework at \(framework) while " +
- "attempting to generate frameworks. \(error)")
- }
- frameworks.append(zipLocation)
- }
- return frameworks
- }
- }
|