forked from Carthage/Carthage
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Build.swift
240 lines (201 loc) · 7.47 KB
/
Build.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//
// Build.swift
// Carthage
//
// Created by Justin Spahr-Summers on 2014-10-11.
// Copyright (c) 2014 Carthage. All rights reserved.
//
import Box
import CarthageKit
import Commandant
import Foundation
import Result
import ReactiveCocoa
import ReactiveTask
public struct BuildCommand: CommandType {
public let verb = "build"
public let function = "Build the project's dependencies"
public func run(mode: CommandMode) -> Result<(), CommandantError<CarthageError>> {
return producerWithOptions(BuildOptions.evaluate(mode))
|> map { options in
return self.buildWithOptions(options)
|> promoteErrors
}
|> flatten(.Merge)
|> waitOnCommand
}
/// Builds a project with the given options.
public func buildWithOptions(options: BuildOptions) -> SignalProducer<(), CarthageError> {
return self.createLoggingSink(options)
|> map { (stdoutSink, temporaryURL) -> SignalProducer<(), CarthageError> in
let directoryURL = NSURL.fileURLWithPath(options.directoryPath, isDirectory: true)!
let (stdoutSignal, schemeSignals) = self.buildProjectInDirectoryURL(directoryURL, options: options)
// Redirect any error-looking messages from stdout, because
// Xcode doesn't always forward them.
var grepDisposable: Disposable?
if !options.verbose {
let coldOutput = SignalProducer { observer, disposable in
disposable.addDisposable(stdoutSignal.observe(observer))
}
let stderrSink: FileSink<NSData> = FileSink.standardErrorSink()
stdoutSignal
|> filter { _ in false }
|> observe(stderrSink)
let stderrSinkWrapper: SinkOf<NSData> = SinkOf { data in
stderrSink.put(.Next(Box(data)))
return
}
let task = TaskDescription(launchPath: "/usr/bin/grep", arguments: [ "--extended-regexp", "(warning|error|failed):" ], standardInput: coldOutput)
grepDisposable = launchTask(task, standardOutput: stderrSinkWrapper).start()
}
stdoutSignal.observe(stdoutSink)
let formatting = options.colorOptions.formatting
return schemeSignals
|> flatten(.Concat)
|> on(started: {
if let temporaryURL = temporaryURL {
carthage.println(formatting.bullets + "xcodebuild output can be found in " + formatting.path(string: temporaryURL.path!))
}
}, next: { (project, scheme) in
carthage.println(formatting.bullets + "Building scheme " + formatting.quote(scheme) + " in " + formatting.projectName(string: project.description))
}, disposed: {
grepDisposable?.dispose()
return
})
|> then(.empty)
}
|> flatten(.Merge)
}
/// Builds the project in the given directory, using the given options.
///
/// Returns a hot signal of `stdout` from `xcodebuild`, and a cold signal of
/// cold signals representing each scheme being built.
private func buildProjectInDirectoryURL(directoryURL: NSURL, options: BuildOptions) -> (Signal<NSData, NoError>, SignalProducer<BuildSchemeProducer, CarthageError>) {
let (stdoutSignal, stdoutSink) = Signal<NSData, NoError>.pipe()
let project = Project(directoryURL: directoryURL)
var buildProducer = project.loadCombinedCartfile()
|> map { _ in project }
|> catch { error in
if options.skipCurrent {
return SignalProducer(error: error)
} else {
// Ignore Cartfile loading failures. Assume the user just
// wants to build the enclosing project.
return .empty
}
}
|> map { project in
return project.migrateIfNecessary(options.colorOptions)
|> on(next: carthage.println)
|> then(SignalProducer(value: project))
}
|> flatten(.Merge)
|> map { (project: Project) -> SignalProducer<BuildSchemeProducer, CarthageError> in
let (dependenciesOutput, dependencies) = project.buildCheckedOutDependenciesWithConfiguration(options.configuration, forPlatform: options.buildPlatform.platform)
dependenciesOutput.observe(stdoutSink)
return dependencies
}
|> flatten(.Merge)
if !options.skipCurrent {
let (currentOutput, currentProducers) = buildInDirectory(directoryURL, withConfiguration: options.configuration, platform: options.buildPlatform.platform)
currentOutput.observe(stdoutSink)
buildProducer = buildProducer |> concat(currentProducers)
}
return (stdoutSignal, buildProducer)
}
/// Creates a sink for logging, returning the sink and the URL to any
/// temporary file on disk.
private func createLoggingSink(options: BuildOptions) -> SignalProducer<(FileSink<NSData>, NSURL?), CarthageError> {
if options.verbose {
let out: (FileSink<NSData>, NSURL?) = (FileSink.standardOutputSink(), nil)
return SignalProducer(value: out)
} else {
return FileSink.openTemporaryFile()
|> map { sink, URL in (sink, .Some(URL)) }
|> mapError { error in
let temporaryDirectoryURL = NSURL.fileURLWithPath(NSTemporaryDirectory(), isDirectory: true)!
return .WriteFailed(temporaryDirectoryURL, error)
}
}
}
}
public struct BuildOptions: OptionsType {
public let configuration: String
public let buildPlatform: BuildPlatform
public let skipCurrent: Bool
public let colorOptions: ColorOptions
public let verbose: Bool
public let directoryPath: String
public static func create(configuration: String)(buildPlatform: BuildPlatform)(skipCurrent: Bool)(colorOptions: ColorOptions)(verbose: Bool)(directoryPath: String) -> BuildOptions {
return self(configuration: configuration, buildPlatform: buildPlatform, skipCurrent: skipCurrent, colorOptions: colorOptions, verbose: verbose, directoryPath: directoryPath)
}
public static func evaluate(m: CommandMode) -> Result<BuildOptions, CommandantError<CarthageError>> {
return create
<*> m <| Option(key: "configuration", defaultValue: "Release", usage: "the Xcode configuration to build")
<*> m <| Option(key: "platform", defaultValue: .All, usage: "the platform to build for (one of ‘all’, ‘Mac’, or ‘iOS’)")
<*> m <| Option(key: "skip-current", defaultValue: true, usage: "don't skip building the Carthage project (in addition to its dependencies)")
<*> ColorOptions.evaluate(m)
<*> m <| Option(key: "verbose", defaultValue: false, usage: "print xcodebuild output inline")
<*> m <| Option(defaultValue: NSFileManager.defaultManager().currentDirectoryPath, usage: "the directory containing the Carthage project")
}
}
/// Represents the user’s chosen platform to build for.
public enum BuildPlatform: Equatable {
/// Build for all available platforms.
case All
/// Build only for iOS.
case iOS
/// Build only for OS X.
case Mac
/// The `Platform` corresponding to this setting.
public var platform: Platform? {
switch self {
case .All:
return nil
case .iOS:
return .iOS
case .Mac:
return .Mac
}
}
}
public func == (lhs: BuildPlatform, rhs: BuildPlatform) -> Bool {
switch (lhs, rhs) {
case (.All, .All):
return true
case (.iOS, .iOS):
return true
case (.Mac, .Mac):
return true
default:
return false
}
}
extension BuildPlatform: Printable {
public var description: String {
switch self {
case .All:
return "all"
case .iOS:
return "iOS"
case .Mac:
return "Mac"
}
}
}
extension BuildPlatform: ArgumentType {
public static let name = "platform"
private static let acceptedStrings: [String: BuildPlatform] = [
"Mac": .Mac, "macosx": .Mac,
"iOS": .iOS, "iphoneos": .iOS, "iphonesimulator": .iOS,
"all": .All
]
public static func fromString(string: String) -> BuildPlatform? {
for (key, platform) in acceptedStrings {
if string.caseInsensitiveCompare(key) == NSComparisonResult.OrderedSame {
return platform
}
}
return nil
}
}