Skip to content

Commit da77ba7

Browse files
authored
ZipBuilder: Get dependencies from the Podfile.lock (#4452)
1 parent a5fbc16 commit da77ba7

File tree

4 files changed

+175
-172
lines changed

4 files changed

+175
-172
lines changed

ZipBuilder/Sources/ZipBuilder/CocoaPodUtils.swift

Lines changed: 92 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,20 @@ public enum CocoaPodUtils {
2525
/// Public name of the pod.
2626
let name: String
2727

28-
/// The version of the pod.
28+
/// The version of the requested pod.
2929
let version: String?
3030
}
3131

3232
/// Information associated with an installed pod.
3333
public struct PodInfo {
34-
let versionedPod: VersionedPod
34+
/// The version of the generated pod.
35+
let version: String
3536

36-
/// The location of the pod on disk.
37-
var installedLocation: URL
37+
/// The pod dependencies.
38+
let dependencies: [String]
3839

39-
var name: String { return versionedPod.name }
40-
var version: String { return versionedPod.version ?? "" }
40+
/// The location of the pod on disk.
41+
let installedLocation: URL
4142
}
4243

4344
/// Executes the `pod cache clean --all` command to remove any cached CocoaPods.
@@ -54,23 +55,8 @@ public enum CocoaPodUtils {
5455
}
5556
}
5657

57-
/// Executes the `pod cache list` command to get the Pods curerntly cached on your machine.
58-
///
59-
/// - Parameter dir: The directory containing all installed pods.
60-
/// - Returns: A dictionary keyed by the pod name, then by version number.
61-
public static func listPodCache(inDir dir: URL) -> [String: [String: PodInfo]] {
62-
let result = Shell.executeCommandFromScript("pod cache list", outputToConsole: false)
63-
switch result {
64-
case let .error(code):
65-
fatalError("Could not list the pod cache in \(dir), the command exited with \(code). Try " +
66-
"running in Terminal to see what's wrong.")
67-
case let .success(output):
68-
return parsePodsCache(output: output.components(separatedBy: "\n"))
69-
}
70-
}
71-
7258
/// Gets metadata from installed Pods. Reads the `Podfile.lock` file and parses it.
73-
public static func installedPodsInfo(inProjectDir projectDir: URL) -> [PodInfo] {
59+
public static func installedPodsInfo(inProjectDir projectDir: URL) -> [String: PodInfo] {
7460
// Read from the Podfile.lock to get the installed versions and names.
7561
let podfileLock: String
7662
do {
@@ -80,33 +66,21 @@ public enum CocoaPodUtils {
8066
"\(projectDir): \(error)")
8167
}
8268

83-
// Get the versions in the format of [PodName: VersionString].
84-
let versions = loadVersionsFromPodfileLock(contents: podfileLock)
85-
86-
// Generate an InstalledPod for each Pod found.
87-
let podsDir = projectDir.appendingPathComponent("Pods")
88-
var installedPods: [PodInfo] = []
89-
for (podName, version) in versions {
90-
let podDir = podsDir.appendingPathComponent(podName)
91-
guard FileManager.default.directoryExists(at: podDir) else {
92-
fatalError("Directory for \(podName) doesn't exist at \(podDir) - failed while getting " +
93-
"information for installed Pods.")
94-
}
95-
96-
let podInfo = PodInfo(versionedPod: VersionedPod(name: podName, version: version),
97-
installedLocation: podDir)
98-
installedPods.append(podInfo)
99-
}
100-
101-
return installedPods
69+
// Get the pods in the format of [PodInfo].
70+
return loadPodInfoFromPodfileLock(contents: podfileLock)
10271
}
10372

104-
/// Install an array of pods in a specific directory, returning an array of PodInfo for each pod
73+
/// Install an array of pods in a specific directory, returning a dictionary of PodInfo for each pod
10574
/// that was installed.
75+
/// - Parameters:
76+
/// - pods: List of VersionedPods to install
77+
/// - directory: Destination directory for the pods.
78+
/// - customSpecRepos: Additional spec repos to check for installation.
79+
/// - Returns: A dictionary of PodInfo's keyed by the pod name.
10680
@discardableResult
10781
public static func installPods(_ pods: [VersionedPod],
10882
inDir directory: URL,
109-
customSpecRepos: [URL]? = nil) -> [PodInfo] {
83+
customSpecRepos: [URL]? = nil) -> [String: PodInfo] {
11084
let fileManager = FileManager.default
11185
// Ensure the directory exists, otherwise we can't install all subspecs.
11286
guard fileManager.directoryExists(at: directory) else {
@@ -148,31 +122,65 @@ public enum CocoaPodUtils {
148122
}
149123
}
150124

151-
/// Load versions of installed Pods from the contents of a `Podfile.lock` file.
125+
/// Load installed Pods from the contents of a `Podfile.lock` file.
152126
///
153127
/// - Parameter contents: The contents of a `Podfile.lock` file.
154-
/// - Returns: A dictionary with names of the pod for keys and a string representation of the
155-
/// version for values.
156-
public static func loadVersionsFromPodfileLock(contents: String) -> [String: String] {
157-
// This pattern matches a framework name with its version (two to three components)
128+
/// - Returns: A dictionary of PodInfo structs keyed by the pod name.
129+
public static func loadPodInfoFromPodfileLock(contents: String) -> [String: PodInfo] {
130+
// This pattern matches a pod name with its version (two to three components)
158131
// Examples:
159132
// - FirebaseUI/Google (4.1.1):
160133
// - GoogleSignIn (4.0.2):
161134

162135
// Force unwrap the regular expression since we know it will work, it's a constant being passed
163136
// in. If any changes are made, be sure to run this script to ensure it works.
164-
let regex = try! NSRegularExpression(pattern: " - (.+) \\((\\d+\\.\\d+\\.?\\d*)\\)",
165-
options: [])
137+
let podRegex = try! NSRegularExpression(pattern: " - (.+) \\((\\d+\\.\\d+\\.?\\d*)\\)",
138+
options: [])
139+
let depRegex: NSRegularExpression = try! NSRegularExpression(pattern: " - (.+).*",
140+
options: [])
166141
let quotes = CharacterSet(charactersIn: "\"")
167-
var frameworks: [String: String] = [:]
168-
contents.components(separatedBy: .newlines).forEach { line in
169-
if let (framework, version) = detectVersion(fromLine: line, matching: regex) {
170-
let coreFramework = framework.components(separatedBy: "/")[0]
171-
let key = coreFramework.trimmingCharacters(in: quotes)
172-
frameworks[key] = version
142+
var pods: [String: String] = [:]
143+
var deps: [String: Set<String>] = [:]
144+
var currentPod: String?
145+
for line in contents.components(separatedBy: .newlines) {
146+
if line.starts(with: "DEPENDENCIES:") {
147+
break
148+
}
149+
if let (pod, version) = detectVersion(fromLine: line, matching: podRegex) {
150+
let corePod = pod.components(separatedBy: "/")[0]
151+
currentPod = corePod.trimmingCharacters(in: quotes)
152+
pods[currentPod!] = version
153+
} else if let currentPod = currentPod {
154+
let matches = depRegex.matches(in: line, range: NSRange(location: 0, length: line.utf8.count))
155+
// Match something like - GTMSessionFetcher/Full (= 1.3.0)
156+
if let match = matches.first {
157+
let depLine = (line as NSString).substring(with: match.range(at: 0)) as String
158+
// Split spaces and subspecs.
159+
let dep = depLine.components(separatedBy: [" ", "/"])[2].trimmingCharacters(in: quotes)
160+
if dep != currentPod {
161+
if deps[currentPod] == nil {
162+
deps[currentPod] = Set()
163+
}
164+
deps[currentPod]?.insert(dep)
165+
}
166+
}
173167
}
174168
}
175-
return frameworks
169+
170+
// Generate an InstalledPod for each Pod found.
171+
let podsDir = projectDir.appendingPathComponent("Pods")
172+
var installedPods: [String: PodInfo] = [:]
173+
for (podName, version) in pods {
174+
let podDir = podsDir.appendingPathComponent(podName)
175+
guard FileManager.default.directoryExists(at: podDir) else {
176+
fatalError("Directory for \(podName) doesn't exist at \(podDir) - failed while getting " +
177+
"information for installed Pods.")
178+
}
179+
let dependencies = [String](deps[podName] ?? [])
180+
let podInfo = PodInfo(version: version, dependencies: dependencies, installedLocation: podDir)
181+
installedPods[podName] = podInfo
182+
}
183+
return installedPods
176184
}
177185

178186
public static func updateRepos() {
@@ -212,6 +220,33 @@ public enum CocoaPodUtils {
212220
}
213221
}
214222

223+
/// Get all transitive pod dependencies for a pod.
224+
/// - Returns: An array of Strings of pod names.
225+
static func transitivePodDependencies(for podName: String,
226+
in installedPods: [String: PodInfo]) -> [String] {
227+
var newDeps = Set([podName])
228+
var returnDeps = Set<String>()
229+
repeat {
230+
var foundDeps = Set<String>()
231+
for dep in newDeps {
232+
let childDeps = installedPods[dep]?.dependencies ?? []
233+
foundDeps.formUnion(Set(childDeps))
234+
}
235+
newDeps = foundDeps.subtracting(returnDeps)
236+
returnDeps.formUnion(newDeps)
237+
} while newDeps.count > 0
238+
return Array(returnDeps)
239+
}
240+
241+
/// Get all transitive pod dependencies for a pod.
242+
/// - Returns: An array of dependencies with versions for a given pod.
243+
static func transitiveVersionedPodDependencies(for podName: String,
244+
in installedPods: [String: PodInfo]) -> [VersionedPod] {
245+
return transitivePodDependencies(for: podName, in: installedPods).map {
246+
CocoaPodUtils.VersionedPod(name: $0, version: installedPods[$0]?.version)
247+
}
248+
}
249+
215250
// MARK: - Private Helpers
216251

217252
// Tests the input to see if it matches a CocoaPod framework and its version.
@@ -279,44 +314,6 @@ public enum CocoaPodUtils {
279314
return podfile
280315
}
281316

282-
/// Parse the output from Pods Cache
283-
private static func parsePodsCache(output: [String]) -> [String: [String: PodInfo]] {
284-
var podName: String?
285-
var podVersion: String?
286-
287-
var podsCache: [String: [String: PodInfo]] = [:]
288-
289-
for line in output {
290-
let trimmedLine = line.trimmingCharacters(in: .whitespaces)
291-
let parts = trimmedLine.components(separatedBy: ":")
292-
if trimmedLine.hasSuffix(":") {
293-
podName = parts[0]
294-
} else {
295-
guard parts.count == 2 else { continue }
296-
let key = parts[0].trimmingCharacters(in: .whitespaces)
297-
let value = parts[1].trimmingCharacters(in: .whitespaces)
298-
299-
switch key {
300-
case "- Version":
301-
podVersion = value
302-
case "Pod":
303-
let podLocation = URL(fileURLWithPath: value)
304-
let podInfo = PodInfo(versionedPod: VersionedPod(name: podName!, version: podVersion!),
305-
installedLocation: podLocation)
306-
if podsCache[podName!] == nil {
307-
podsCache[podName!] = [:]
308-
}
309-
podsCache[podName!]![podVersion!] = podInfo
310-
311-
default:
312-
break
313-
}
314-
}
315-
}
316-
317-
return podsCache
318-
}
319-
320317
/// Write a podfile that contains all the pods passed in to the directory passed in with a name
321318
/// "Podfile".
322319
private static func writePodfile(for pods: [VersionedPod],

ZipBuilder/Sources/ZipBuilder/ModuleMapBuilder.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,18 @@ struct ModuleMapBuilder {
4242
private let customSpecRepos: [URL]?
4343

4444
/// Dictionary of all installed pods.
45+
private let allPods: [String: CocoaPodUtils.PodInfo]
46+
47+
/// Dictionary of installed pods required for this module.
4548
private var installedPods: [String: FrameworkInfo]
4649

4750
/// Default initializer.
48-
init(frameworks: [String: [URL]], customSpecRepos: [URL]?) {
51+
init(frameworks: [String: [URL]], customSpecRepos: [URL]?, allPods: [String: CocoaPodUtils.PodInfo]) {
4952
projectDir = FileManager.default.temporaryDirectory(withName: "module")
5053
CocoaPodUtils.podInstallPrepare(inProjectDir: projectDir)
5154

5255
self.customSpecRepos = customSpecRepos
56+
self.allPods = allPods
5357

5458
var cacheDir: URL
5559
do {
@@ -90,9 +94,15 @@ struct ModuleMapBuilder {
9094

9195
// MARK: - Internal Functions
9296

93-
/// Build a module map for a single framework.
97+
/// Build a module map for a single framework. A CocoaPod install is run to extract the required frameworks
98+
/// and libraries from the generated xcconfig. All previously installed dependent pods are put into the Podfile
99+
/// to make sure we install the right version and from the right location.
94100
private func generate(framework: FrameworkInfo) {
95-
_ = CocoaPodUtils.installPods([framework.versionedPod], inDir: projectDir, customSpecRepos: customSpecRepos)
101+
let podName = framework.versionedPod.name
102+
let deps = CocoaPodUtils.transitiveVersionedPodDependencies(for: podName, in: allPods)
103+
_ = CocoaPodUtils.installPods([framework.versionedPod] + deps,
104+
inDir: projectDir,
105+
customSpecRepos: customSpecRepos)
96106
let xcconfigFile = projectDir.appendingPathComponents(["Pods", "Target Support Files",
97107
"Pods-FrameworkMaker",
98108
"Pods-FrameworkMaker.release.xcconfig"])

0 commit comments

Comments
 (0)