forked from Carthage/Carthage
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ProjectLocator.swift
143 lines (126 loc) · 4.36 KB
/
ProjectLocator.swift
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import Foundation
#if swift(>=3)
import ReactiveSwift
#else
import ReactiveCocoa
#endif
/// Describes how to locate the actual project or workspace that Xcode should
/// build.
public enum ProjectLocator: Comparable {
/// The `xcworkspace` at the given file URL should be built.
case workspace(URL)
/// The `xcodeproj` at the given file URL should be built.
case projectFile(URL)
/// The file URL this locator refers to.
public var fileURL: URL {
switch self {
case let .workspace(url):
assert(url.isFileURL)
return url
case let .projectFile(url):
assert(url.isFileURL)
return url
}
}
/// The number of levels deep the current object is in the directory hierarchy.
public var level: Int {
return fileURL.carthage_pathComponents.count - 1
}
/// Attempts to locate projects and workspaces within the given directory.
///
/// Sends all matches in preferential order.
public static func locate(in directoryURL: URL) -> SignalProducer<ProjectLocator, CarthageError> {
let enumerationOptions: FileManager.DirectoryEnumerationOptions = [ .skipsHiddenFiles, .skipsPackageDescendants ]
return gitmodulesEntriesInRepository(directoryURL, revision: nil)
.map { directoryURL.appendingPathComponent($0.path) }
.concat(value: directoryURL.appendingPathComponent(CarthageProjectCheckoutsPath))
.collect()
.flatMap(.merge) { directoriesToSkip in
return FileManager.`default`
.carthage_enumerator(at: directoryURL.resolvingSymlinksInPath(), includingPropertiesForKeys: [ .typeIdentifierKey ], options: enumerationOptions, catchErrors: true)
.map { _, url in url }
.filter { url in
return !directoriesToSkip.contains { $0.hasSubdirectory(url) }
}
}
.map { url -> ProjectLocator? in
if let uti = url.typeIdentifier.value {
if (UTTypeConformsTo(uti as CFString, "com.apple.dt.document.workspace" as CFString)) {
return .workspace(url)
} else if (UTTypeConformsTo(uti as CFString, "com.apple.xcode.project" as CFString)) {
return .projectFile(url)
}
}
return nil
}
.skipNil()
.collect()
.map { $0.sorted() }
.flatMap(.merge) { SignalProducer<ProjectLocator, CarthageError>($0) }
}
/// Sends each scheme found in the receiver.
public func schemes() -> SignalProducer<String, CarthageError> {
let task = xcodebuildTask("-list", BuildArguments(project: self))
return task.launch()
.ignoreTaskData()
.mapError(CarthageError.taskError)
// xcodebuild has a bug where xcodebuild -list can sometimes hang
// indefinitely on projects that don't share any schemes, so
// automatically bail out if it looks like that's happening.
.timeout(after: 60, raising: .xcodebuildTimeout(self), on: QueueScheduler(qos: .default))
.retry(upTo: 2)
.map { data in
return String(data: data, encoding: .utf8)!
}
.flatMap(.merge) { string in
return string.linesProducer
}
.flatMap(.merge) { line -> SignalProducer<String, CarthageError> in
// Matches one of these two possible messages:
//
// ' This project contains no schemes.'
// 'There are no schemes in workspace "Carthage".'
if line.hasSuffix("contains no schemes.") || line.hasPrefix("There are no schemes") {
return SignalProducer(error: .noSharedSchemes(self, nil))
} else {
return SignalProducer(value: line)
}
}
.skip { line in !line.hasSuffix("Schemes:") }
.skip(first: 1)
.take { line in !line.isEmpty }
.map { line in line.trimmingCharacters(in: .whitespaces) }
}
}
public func ==(_ lhs: ProjectLocator, _ rhs: ProjectLocator) -> Bool {
switch (lhs, rhs) {
case let (.workspace(left), .workspace(right)):
return left == right
case let (.projectFile(left), .projectFile(right)):
return left == right
default:
return false
}
}
public func <(_ lhs: ProjectLocator, _ rhs: ProjectLocator) -> Bool {
// Prefer top-level directories
let leftLevel = lhs.level
let rightLevel = rhs.level
guard leftLevel == rightLevel else {
return leftLevel < rightLevel
}
// Prefer workspaces over projects.
switch (lhs, rhs) {
case (.workspace, .projectFile):
return true
case (.projectFile, .workspace):
return false
default:
return lhs.fileURL.carthage_path.characters.lexicographicallyPrecedes(rhs.fileURL.carthage_path.characters)
}
}
extension ProjectLocator: CustomStringConvertible {
public var description: String {
return fileURL.carthage_lastPathComponent
}
}