-
-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathXcodeProjectParserLive.swift
More file actions
102 lines (93 loc) · 4.24 KB
/
XcodeProjectParserLive.swift
File metadata and controls
102 lines (93 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import FileSystem
import Foundation
import PathKit
import XcodeProj
import XcodeProject
import XcodeProjectParser
public struct XcodeProjectParserLive: XcodeProjectParser {
private let fileSystem: FileSystem
public init(fileSystem: FileSystem) {
self.fileSystem = fileSystem
}
public func parseProject(at fileURL: URL) throws -> XcodeProject {
let path = Path(fileURL.relativePath)
let project = try XcodeProj(path: path)
let sourceRoot = fileURL.deletingLastPathComponent()
let remoteSwiftPackages = remoteSwiftPackages(in: project)
let localSwiftPackages = try localSwiftPackages(in: project, atSourceRoot: sourceRoot)
return XcodeProject(
name: fileURL.lastPathComponent,
targets: targets(in: project),
swiftPackages: (remoteSwiftPackages + localSwiftPackages)
)
}
}
private extension XcodeProjectParserLive {
func targets(in project: XcodeProj) -> [XcodeProject.Target] {
return project.pbxproj.nativeTargets.map { target in
let packageProductDependencies = target.packageProductDependencies.map(\.productName)
return .init(name: target.name, packageProductDependencies: packageProductDependencies)
}
}
func remoteSwiftPackages(in project: XcodeProj) -> [XcodeProject.SwiftPackage] {
struct IntermediateRemoteSwiftPackage {
let name: String
let repositoryURL: URL
let products: [String]
}
var swiftPackages: [IntermediateRemoteSwiftPackage] = []
for target in project.pbxproj.nativeTargets {
for dependency in target.packageProductDependencies {
guard let package = dependency.package, let packageName = package.name else {
continue
}
guard let rawRepositoryURL = package.repositoryURL, let repositoryURL = URL(string: rawRepositoryURL) else {
continue
}
if let existingSwiftPackageIndex = swiftPackages.firstIndex(where: { $0.name == packageName }) {
let existingSwiftPackage = swiftPackages[existingSwiftPackageIndex]
let newProducts = existingSwiftPackage.products + [dependency.productName]
let newSwiftPackage = IntermediateRemoteSwiftPackage(name: packageName, repositoryURL: repositoryURL, products: newProducts)
swiftPackages[existingSwiftPackageIndex] = newSwiftPackage
} else {
let products = [dependency.productName]
let swiftPackage = IntermediateRemoteSwiftPackage(name: packageName, repositoryURL: repositoryURL, products: products)
swiftPackages.append(swiftPackage)
}
}
}
return swiftPackages.map { .remote(name: $0.name, repositoryURL: $0.repositoryURL, products: $0.products) }
}
func localSwiftPackages(in project: XcodeProj, atSourceRoot sourceRoot: URL) throws -> [XcodeProject.SwiftPackage] {
return project.pbxproj.fileReferences.compactMap { fileReference in
guard fileReference.isPotentialSwiftPackage else {
return nil
}
guard let packageName = fileReference.potentialPackageName else {
return nil
}
guard let packageSwiftFileURL = fileReference.potentialPackageSwiftFileURL(forSourceRoot: sourceRoot) else {
return nil
}
guard fileSystem.fileExists(at: packageSwiftFileURL) else {
return nil
}
return .local(.init(name: packageName, fileURL: packageSwiftFileURL))
}
}
}
private extension PBXFileReference {
var isPotentialSwiftPackage: Bool {
return lastKnownFileType == "folder" || lastKnownFileType == "wrapper"
}
var potentialPackageName: String? {
return name ?? path
}
func potentialPackageSwiftFileURL(forSourceRoot sourceRoot: URL) -> URL? {
guard let fullPath = try? fullPath(sourceRoot: sourceRoot.path) else {
return nil
}
let packageUrl = URL(fileURLWithPath: fullPath)
return packageUrl.appendingPathComponent("Package.swift")
}
}