Skip to content

Commit 27fd02a

Browse files
committed
Merge branch 'xcframework-master'
2 parents 73771a0 + bbbcb49 commit 27fd02a

15 files changed

+965
-434
lines changed

ZipBuilder/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ you can fix issues or dig in without having to dig too deep into the code.
88

99
## Zip Builder
1010

11-
This is a small Swift Package Manager project that allows users to package an iOS Zip file of binary
11+
This is a Swift Package Manager project that allows users to package an iOS Zip file of binary
1212
packages.
1313

1414
### Requirements
1515

1616
In order to build the Zip file, you will need:
1717

18-
- Xcode 10.1
18+
- Xcode 11.0
1919
- CocoaPods
2020
- An internet connection to fetch CocoaPods
2121

ZipBuilder/Sources/ZipBuilder/CarthageUtils.swift

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,56 @@ import Foundation
2222
enum CarthageUtils {}
2323

2424
extension CarthageUtils {
25+
/// Package all required files for a Carthage release.
26+
///
27+
/// - Parameters:
28+
/// - templateDir: The template project directory, contains the dummy Firebase library.
29+
/// - carthageJSONDir: Location of directory containing all JSON Carthage manifests.
30+
/// - artifacts: Release Artifacts from build.
31+
/// - rcNumber: The RC number.
32+
/// - Returns: The path to the root of the Carthage installation.
33+
static func packageCarthageRelease(templateDir: URL,
34+
carthageJSONDir: URL,
35+
artifacts: ZipBuilder.ReleaseArtifacts,
36+
rcNumber: Int?) -> URL? {
37+
guard let zipLocation = artifacts.carthageDir else { return nil }
38+
39+
do {
40+
print("Creating Carthage release...")
41+
let carthagePath =
42+
zipLocation.deletingLastPathComponent().appendingPathComponent("carthage_build")
43+
// Create a copy of the release directory since we'll be modifying it.
44+
let fileManager = FileManager.default
45+
fileManager.removeIfExists(at: carthagePath)
46+
try fileManager.copyItem(at: zipLocation, to: carthagePath)
47+
48+
// Package the Carthage distribution with the current directory structure.
49+
let carthageDir = zipLocation.deletingLastPathComponent().appendingPathComponent("carthage")
50+
fileManager.removeIfExists(at: carthageDir)
51+
var output = carthageDir.appendingPathComponent(artifacts.firebaseVersion)
52+
if let rcNumber = args.rcNumber {
53+
output.appendPathComponent("rc\(rcNumber)")
54+
} else {
55+
output.appendPathComponent("latest-non-rc")
56+
}
57+
try fileManager.createDirectory(at: output, withIntermediateDirectories: true)
58+
generateCarthageRelease(fromPackagedDir: carthagePath,
59+
templateDir: templateDir,
60+
jsonDir: carthageJSONDir,
61+
artifacts: artifacts,
62+
outputDir: output)
63+
64+
// Remove the duplicated Carthage build directory.
65+
fileManager.removeIfExists(at: carthagePath)
66+
print("Done creating Carthage release! Files written to \(output)")
67+
68+
// Save the directory for later copying.
69+
return carthageDir
70+
} catch {
71+
fatalError("Could not copy output directory for Carthage build: \(error)")
72+
}
73+
}
74+
2575
/// Generates all required files for a Carthage release.
2676
///
2777
/// - Parameters:
@@ -31,18 +81,19 @@ extension CarthageUtils {
3181
/// - firebaseVersion: The version of the Firebase pod.
3282
/// - coreDiagnosticsPath: The path to the Core Diagnostics framework built for Carthage.
3383
/// - outputDir: The directory where all artifacts should be created.
34-
static func generateCarthageRelease(fromPackagedDir packagedDir: URL,
35-
templateDir: URL,
36-
jsonDir: URL,
37-
firebaseVersion: String,
38-
coreDiagnosticsPath: URL,
39-
outputDir: URL) {
84+
85+
private static func generateCarthageRelease(fromPackagedDir packagedDir: URL,
86+
templateDir: URL,
87+
jsonDir: URL,
88+
artifacts: ZipBuilder.ReleaseArtifacts,
89+
outputDir: URL) {
4090
let directories: [String]
4191
do {
4292
directories = try FileManager.default.contentsOfDirectory(atPath: packagedDir.path)
4393
} catch {
4494
fatalError("Could not get contents of Firebase directory to package Carthage build. \(error)")
4595
}
96+
let firebaseVersion = artifacts.firebaseVersion
4697

4798
// Loop through each directory available and package it as a separate Zip file.
4899
for product in directories {
@@ -51,31 +102,10 @@ extension CarthageUtils {
51102

52103
// Parse the JSON file, ensure that we're not trying to overwrite a release.
53104
var jsonManifest = parseJSONFile(fromDir: jsonDir, product: product)
54-
guard jsonManifest[firebaseVersion] == nil else {
55-
print("Carthage release for \(product) \(firebaseVersion) already exists - skipping.")
56-
continue
57-
}
58-
59-
// Find all the .frameworks in this directory.
60-
let allContents: [String]
61-
do {
62-
allContents = try FileManager.default.contentsOfDirectory(atPath: fullPath.path)
63-
} catch {
64-
fatalError("Could not get contents of \(product) for Carthage build in order to add " +
65-
"an Info.plist in each framework. \(error)")
66-
}
67-
68-
// Carthage will fail to install a framework if it doesn't have an Info.plist, even though
69-
// they're not used for static frameworks. Generate one and write it to each framework.
70-
let frameworks = allContents.filter { $0.hasSuffix(".framework") }
71-
for framework in frameworks {
72-
let plistPath = fullPath.appendingPathComponents([framework, "Info.plist"])
73-
// Drop the extension of the framework name.
74-
let plist = generatePlistContents(forName: framework.components(separatedBy: ".").first!)
75-
do {
76-
try plist.write(to: plistPath)
77-
} catch {
78-
fatalError("Could not copy plist for \(framework) for Carthage release. \(error)")
105+
if !args.carthageSkipVersionCheck {
106+
guard jsonManifest[firebaseVersion] == nil else {
107+
print("Carthage release for \(product) \(firebaseVersion) already exists - skipping.")
108+
continue
79109
}
80110
}
81111

@@ -93,17 +123,6 @@ extension CarthageUtils {
93123
} catch {
94124
fatalError("Could not copy \(noticesName) to FirebaseCore for Carthage build. \(error)")
95125
}
96-
97-
// Override the Core Diagnostics framework with one that includes the proper bit flipped.
98-
let coreDiagnosticsFramework = Constants.coreDiagnosticsName + ".framework"
99-
let destination = fullPath.appendingPathComponent(coreDiagnosticsFramework)
100-
do {
101-
// Remove the existing framework and replace it with the newly compiled one.
102-
try FileManager.default.removeItem(at: destination)
103-
try FileManager.default.copyItem(at: coreDiagnosticsPath, to: destination)
104-
} catch {
105-
fatalError("Could not replace \(coreDiagnosticsFramework) during Carthage build. \(error)")
106-
}
107126
}
108127

109128
// Hash the contents of the directory to get a unique name for Carthage.
@@ -229,7 +248,7 @@ extension CarthageUtils {
229248
}
230249
}
231250

232-
private static func generatePlistContents(forName name: String) -> Data {
251+
static func generatePlistContents(forName name: String) -> Data {
233252
let plist: [String: String] = ["CFBundleIdentifier": "com.firebase.Firebase",
234253
"CFBundleInfoDictionaryVersion": "6.0",
235254
"CFBundlePackageType": "FMWK",

ZipBuilder/Sources/ZipBuilder/CocoaPodUtils.swift

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ enum CocoaPodUtils {
4040
}
4141

4242
/// Information associated with an installed pod.
43-
struct PodInfo {
43+
/// This is a class so that moduleMapContents can be updated via reference.
44+
class PodInfo {
4445
/// The version of the generated pod.
4546
let version: String
4647

@@ -49,6 +50,39 @@ enum CocoaPodUtils {
4950

5051
/// The location of the pod on disk.
5152
let installedLocation: URL
53+
54+
/// Source pod flag.
55+
let isSourcePod: Bool
56+
57+
/// Binary frameworks in this pod.
58+
let binaryFrameworks: [URL]
59+
60+
/// Subspecs installed for this pod.
61+
let subspecs: Set<String>
62+
63+
/// The contents of the module map for all frameworks associated with the pod.
64+
var moduleMapContents: ModuleMapBuilder.ModuleMapContents?
65+
66+
init(version: String, dependencies: [String], installedLocation: URL, subspecs: Set<String>) {
67+
self.version = version
68+
self.dependencies = dependencies
69+
self.installedLocation = installedLocation
70+
self.subspecs = subspecs
71+
72+
// Get all the frameworks contained in this directory.
73+
var binaryFrameworks: [URL] = []
74+
if installedLocation != LaunchArgs.shared.localPodspecPath {
75+
do {
76+
binaryFrameworks = try FileManager.default.recursivelySearch(for: .frameworks,
77+
in: installedLocation)
78+
} catch {
79+
fatalError("Cannot search for .framework files in Pods directory " +
80+
"\(installedLocation): \(error)")
81+
}
82+
}
83+
self.binaryFrameworks = binaryFrameworks
84+
isSourcePod = binaryFrameworks == []
85+
}
5286
}
5387

5488
/// Executes the `pod cache clean --all` command to remove any cached CocoaPods.
@@ -86,11 +120,14 @@ enum CocoaPodUtils {
86120
/// - pods: List of VersionedPods to install
87121
/// - directory: Destination directory for the pods.
88122
/// - customSpecRepos: Additional spec repos to check for installation.
123+
/// - forceStaticLibs: Force a static library pod install. For the module map construction, we want pod names not module
124+
/// names in the generated OTHER_LD_FLAGS options.
89125
/// - Returns: A dictionary of PodInfo's keyed by the pod name.
90126
@discardableResult
91127
static func installPods(_ pods: [VersionedPod],
92128
inDir directory: URL,
93-
customSpecRepos: [URL]? = nil) -> [String: PodInfo] {
129+
customSpecRepos: [URL]?,
130+
forceStaticLibs: Bool = false) -> [String: PodInfo] {
94131
let fileManager = FileManager.default
95132
// Ensure the directory exists, otherwise we can't install all subspecs.
96133
guard fileManager.directoryExists(at: directory) else {
@@ -105,7 +142,10 @@ enum CocoaPodUtils {
105142

106143
// Attempt to write the Podfile to disk.
107144
do {
108-
try writePodfile(for: pods, toDirectory: directory, customSpecRepos: customSpecRepos)
145+
try writePodfile(for: pods,
146+
toDirectory: directory,
147+
customSpecRepos: customSpecRepos,
148+
forceStaticLibs: forceStaticLibs)
109149
} catch let FileManager.FileError.directoryNotFound(path) {
110150
fatalError("Failed to write Podfile with pods \(pods) at path \(path)")
111151
} catch let FileManager.FileError.writeToFileFailed(path, error) {
@@ -156,30 +196,49 @@ enum CocoaPodUtils {
156196
break
157197
}
158198
if let (pod, version) = detectVersion(fromLine: line) {
159-
let corePod = pod.components(separatedBy: "/")[0]
160-
currentPod = corePod.trimmingCharacters(in: quotes)
199+
currentPod = pod.trimmingCharacters(in: quotes)
161200
pods[currentPod!] = version
162201
} else if let currentPod = currentPod {
163202
let matches = depRegex.matches(in: line, range: NSRange(location: 0, length: line.utf8.count))
164203
// Match something like - GTMSessionFetcher/Full (= 1.3.0)
165204
if let match = matches.first {
166205
let depLine = (line as NSString).substring(with: match.range(at: 0)) as String
167206
// Split spaces and subspecs.
168-
let dep = depLine.components(separatedBy: [" ", "/"])[2].trimmingCharacters(in: quotes)
207+
let dep = depLine.components(separatedBy: [" "])[2].trimmingCharacters(in: quotes)
169208
if dep != currentPod {
170-
if deps[currentPod] == nil {
171-
deps[currentPod] = Set()
172-
}
173-
deps[currentPod]?.insert(dep)
209+
deps[currentPod, default: Set()].insert(dep)
174210
}
175211
}
176212
}
177213
}
214+
// Organize the subspecs
215+
var versions: [String: String] = [:]
216+
var subspecs: [String: Set<String>] = [:]
217+
218+
for (podName, version) in pods {
219+
let subspecArray = podName.components(separatedBy: "/")
220+
if subspecArray.count == 1 || subspecArray[0] == "abseil" {
221+
// Special case for abseil since it has two layers and no external deps.
222+
versions[subspecArray[0]] = version
223+
} else if subspecArray.count > 2 {
224+
fatalError("Multi-layered subspecs are not supported - \(podName)")
225+
} else {
226+
if let previousVersion = versions[podName], version != previousVersion {
227+
fatalError("Different installed versions for \(podName)." +
228+
"\(version) versus \(previousVersion)")
229+
} else {
230+
let basePodName = subspecArray[0]
231+
versions[basePodName] = version
232+
subspecs[basePodName, default: Set()].insert(subspecArray[1])
233+
deps[basePodName] = deps[basePodName, default: Set()].union(deps[podName] ?? Set())
234+
}
235+
}
236+
}
178237

179238
// Generate an InstalledPod for each Pod found.
180239
let podsDir = projectDir.appendingPathComponent("Pods")
181240
var installedPods: [String: PodInfo] = [:]
182-
for (podName, version) in pods {
241+
for (podName, version) in versions {
183242
var podDir = podsDir.appendingPathComponent(podName)
184243
// Make sure that pod got installed if it's not coming from a local podspec.
185244
if !FileManager.default.directoryExists(at: podDir) {
@@ -190,7 +249,8 @@ enum CocoaPodUtils {
190249
podDir = repoDir
191250
}
192251
let dependencies = [String](deps[podName] ?? [])
193-
let podInfo = PodInfo(version: version, dependencies: dependencies, installedLocation: podDir)
252+
let podInfo = PodInfo(version: version, dependencies: dependencies, installedLocation: podDir,
253+
subspecs: subspecs[podName] ?? Set())
194254
installedPods[podName] = podInfo
195255
}
196256
return installedPods
@@ -251,6 +311,15 @@ enum CocoaPodUtils {
251311
return Array(returnDeps)
252312
}
253313

314+
/// Get all transitive pod dependencies for a pod with subspecs merged.
315+
/// - Returns: An array of Strings of pod names.
316+
static func transitiveMasterPodDependencies(for podName: String,
317+
in installedPods: [String: PodInfo]) -> [String] {
318+
return Array(Set(transitivePodDependencies(for: podName, in: installedPods).map {
319+
$0.components(separatedBy: "/")[0]
320+
}))
321+
}
322+
254323
/// Get all transitive pod dependencies for a pod.
255324
/// - Returns: An array of dependencies with versions for a given pod.
256325
static func transitiveVersionedPodDependencies(for podName: String,
@@ -300,7 +369,8 @@ enum CocoaPodUtils {
300369
/// Create the contents of a Podfile for an array of subspecs. This assumes the array of subspecs
301370
/// is not empty.
302371
private static func generatePodfile(for pods: [VersionedPod],
303-
customSpecsRepos: [URL]? = nil) -> String {
372+
customSpecsRepos: [URL]?,
373+
forceStaticLibs: Bool) -> String {
304374
// Start assembling the Podfile.
305375
var podfile: String = ""
306376

@@ -315,6 +385,13 @@ enum CocoaPodUtils {
315385
""" // Explicit newline above to ensure it's included in the String.
316386
}
317387

388+
if forceStaticLibs {
389+
podfile += " use_modular_headers!\n"
390+
} else if LaunchArgs.shared.dynamic {
391+
podfile += " use_frameworks!\n"
392+
} else {
393+
podfile += " use_frameworks! :linkage => :static\n"
394+
}
318395
// Include the minimum iOS version.
319396
podfile += """
320397
platform :ios, '\(LaunchArgs.shared.minimumIOSVersion)'
@@ -367,15 +444,16 @@ enum CocoaPodUtils {
367444
/// "Podfile".
368445
private static func writePodfile(for pods: [VersionedPod],
369446
toDirectory directory: URL,
370-
customSpecRepos: [URL]?) throws {
447+
customSpecRepos: [URL]?,
448+
forceStaticLibs: Bool) throws {
371449
guard FileManager.default.directoryExists(at: directory) else {
372450
// Throw an error so the caller can provide a better error message.
373451
throw FileManager.FileError.directoryNotFound(path: directory.path)
374452
}
375453

376454
// Generate the full path of the Podfile and attempt to write it to disk.
377455
let path = directory.appendingPathComponent("Podfile")
378-
let podfile = generatePodfile(for: pods, customSpecsRepos: customSpecRepos)
456+
let podfile = generatePodfile(for: pods, customSpecsRepos: customSpecRepos, forceStaticLibs: forceStaticLibs)
379457
do {
380458
try podfile.write(toFile: path.path, atomically: true, encoding: .utf8)
381459
} catch {

ZipBuilder/Sources/ZipBuilder/FileManager+Utils.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,14 @@ extension FileManager {
101101
}
102102
}
103103

104-
// Enable a single unique temporary workspace per execution.
105-
static let unique: String = UUID().uuidString
104+
/// Enable a single unique temporary workspace per execution with a sortable and readable timestamp.
105+
private static func timeStamp() -> String {
106+
let formatter = DateFormatter()
107+
formatter.dateFormat = "YYYY-MM-dd'T'HH-mm-ss"
108+
return formatter.string(from: Date())
109+
}
110+
111+
static let unique: String = timeStamp()
106112

107113
/// Returns a deterministic path of a temporary directory for the given name. Note: This does
108114
/// *not* create the directory if it doesn't exist, merely generates the name for creation.

0 commit comments

Comments
 (0)