diff --git a/.travis.yml b/.travis.yml index 08a049c..c58b8a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,6 @@ script: - set -o pipefail && xcodebuild -workspace "$TRAVIS_XCODE_WORKSPACE" -scheme "$TRAVIS_XCODE_SCHEME" test | xcpretty matrix: include: - - osx_image: xcode7.3 - xcode_scheme: ReactiveTask - env: JOB=Xcode7.3 - osx_image: xcode8 xcode_scheme: ReactiveTask env: JOB=Xcode8 @@ -33,4 +30,4 @@ deploy: on: repo: Carthage/ReactiveTask tags: true - condition: $JOB = Xcode7.3 + condition: $JOB = Xcode8 diff --git a/Cartfile b/Cartfile index c46e125..38f3bb3 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "ReactiveCocoa/ReactiveCocoa" ~> 4.2.2 +github "ReactiveCocoa/ReactiveCocoa" "master" diff --git a/Cartfile.private b/Cartfile.private index 50a5225..6c9286a 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,3 +1,3 @@ -github "Quick/Quick" ~> 0.9.3 -github "Quick/Nimble" "188caeb" -github "jspahrsummers/xcconfigs" "1ef9763" +github "jspahrsummers/xcconfigs" "3d9d996" +github "Quick/Quick" "swift-3.0" +github "Quick/Nimble" "swift-3.0" diff --git a/Cartfile.resolved b/Cartfile.resolved index 7785076..3f630f0 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,5 +1,5 @@ -github "Quick/Nimble" "188caeb094bc342614d8a5c706cd8bb9a6c355eb" -github "Quick/Quick" "v0.9.3" -github "antitypical/Result" "2.1.3" -github "jspahrsummers/xcconfigs" "1ef97639ffbe041da0b1392b2114fa19b922a7a1" -github "ReactiveCocoa/ReactiveCocoa" "v4.2.2" +github "Quick/Nimble" "220152be528dcc0537764c179c95b8174028c80c" +github "Quick/Quick" "81d2a7bd95ef91e2604ee0431bba6fe59ad662dc" +github "antitypical/Result" "3.0.0-alpha.4" +github "jspahrsummers/xcconfigs" "3d9d99634cae6d586e272543d527681283b33eb0" +github "ReactiveCocoa/ReactiveCocoa" "a135a1f755c5e4435634a5432a0b622eb323f5d7" diff --git a/Carthage/Checkouts/Nimble b/Carthage/Checkouts/Nimble index 188caeb..220152b 160000 --- a/Carthage/Checkouts/Nimble +++ b/Carthage/Checkouts/Nimble @@ -1 +1 @@ -Subproject commit 188caeb094bc342614d8a5c706cd8bb9a6c355eb +Subproject commit 220152be528dcc0537764c179c95b8174028c80c diff --git a/Carthage/Checkouts/Quick b/Carthage/Checkouts/Quick index faa6056..81d2a7b 160000 --- a/Carthage/Checkouts/Quick +++ b/Carthage/Checkouts/Quick @@ -1 +1 @@ -Subproject commit faa6056c0c7da69fc1fb494cf61fa264aea4d9bc +Subproject commit 81d2a7bd95ef91e2604ee0431bba6fe59ad662dc diff --git a/Carthage/Checkouts/ReactiveCocoa b/Carthage/Checkouts/ReactiveCocoa index f214c9d..a135a1f 160000 --- a/Carthage/Checkouts/ReactiveCocoa +++ b/Carthage/Checkouts/ReactiveCocoa @@ -1 +1 @@ -Subproject commit f214c9d508db77f751c3af5be967c4cb235fc0a1 +Subproject commit a135a1f755c5e4435634a5432a0b622eb323f5d7 diff --git a/Carthage/Checkouts/Result b/Carthage/Checkouts/Result index 9b5e373..cc1699d 160000 --- a/Carthage/Checkouts/Result +++ b/Carthage/Checkouts/Result @@ -1 +1 @@ -Subproject commit 9b5e373891dfe0de11ba2a8e9a5cafd570b858c4 +Subproject commit cc1699dbd812c71bee7c44c8cbd75497713bc279 diff --git a/Carthage/Checkouts/xcconfigs b/Carthage/Checkouts/xcconfigs index 1ef9763..3d9d996 160000 --- a/Carthage/Checkouts/xcconfigs +++ b/Carthage/Checkouts/xcconfigs @@ -1 +1 @@ -Subproject commit 1ef97639ffbe041da0b1392b2114fa19b922a7a1 +Subproject commit 3d9d99634cae6d586e272543d527681283b33eb0 diff --git a/README.md b/README.md index 7fd8ccd..cf2b0ae 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ ReactiveTask is a Swift framework for launching shell tasks (processes), built u ```swift let strings = [ "foo\n", "bar\n", "buzz\n", "fuzz\n" ] -let input = SignalProducer(values: strings.map { $0.dataUsingEncoding(NSUTF8StringEncoding)! }) +let input = SignalProducer(values: strings.map { $0.data(using: .utf8)! }) let task = Task("/usr/bin/sort") // Run the task, ignoring the output, and do something with the final result. let result: Result? = launchTask(task, standardInput: input) .ignoreTaskData() - .map { String(data: $0, encoding: NSUTF8StringEncoding) } + .map { String(data: $0, encoding: .utf8) } .ignoreNil() .single() print("Output of `\(task)`: \(result?.value ?? "")") @@ -17,21 +17,21 @@ print("Output of `\(task)`: \(result?.value ?? "")") // Start the task and print all the events, which includes all the output // that was received. launchTask(task, standardInput: input) - .flatMapTaskEvents(.Concat) { data in - return SignalProducer(value: String(data: data, encoding: NSUTF8StringEncoding)) + .flatMapTaskEvents(.concat) { data in + return SignalProducer(value: String(data: data, encoding: .utf8)) } .startWithNext { (event: TaskEvent) in switch event { - case let .Launch(task): + case let .launch(task): print("launched task: \(task)") - case let .StandardError(data): + case let .standardError(data): print("stderr: \(data)") - case let .StandardOutput(data): + case let .standardOutput(data): print("stdout: \(data)") - case let .Success(string): + case let .success(string): print("value: \(string ?? "")") } } diff --git a/ReactiveTask.xcodeproj/project.pbxproj b/ReactiveTask.xcodeproj/project.pbxproj index 924127f..d5d8ad8 100644 --- a/ReactiveTask.xcodeproj/project.pbxproj +++ b/ReactiveTask.xcodeproj/project.pbxproj @@ -269,7 +269,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0710; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = Carthage; TargetAttributes = { D0BFEA571A2D1E5E00E23194 = { @@ -350,6 +350,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = D0BFEA781A2D1E7C00E23194 /* Debug.xcconfig */; buildSettings = { + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CURRENT_PROJECT_VERSION = 1; ENABLE_TESTABILITY = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -366,6 +368,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = D0BFEA7A1A2D1E7C00E23194 /* Release.xcconfig */; buildSettings = { + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CURRENT_PROJECT_VERSION = 1; GCC_NO_COMMON_BLOCKS = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -386,6 +390,7 @@ INFOPLIST_FILE = ReactiveTask/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -399,6 +404,7 @@ INFOPLIST_FILE = ReactiveTask/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -413,6 +419,7 @@ INFOPLIST_FILE = ReactiveTaskTests/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -427,6 +434,7 @@ INFOPLIST_FILE = ReactiveTaskTests/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -434,6 +442,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = D0BFEA7B1A2D1E7C00E23194 /* Test.xcconfig */; buildSettings = { + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CURRENT_PROJECT_VERSION = 1; GCC_NO_COMMON_BLOCKS = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -454,6 +464,7 @@ INFOPLIST_FILE = ReactiveTask/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Test; }; @@ -468,6 +479,7 @@ INFOPLIST_FILE = ReactiveTaskTests/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Test; }; @@ -475,6 +487,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = D0BFEA791A2D1E7C00E23194 /* Profile.xcconfig */; buildSettings = { + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CURRENT_PROJECT_VERSION = 1; GCC_NO_COMMON_BLOCKS = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -495,6 +509,7 @@ INFOPLIST_FILE = ReactiveTask/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Profile; }; @@ -509,6 +524,7 @@ INFOPLIST_FILE = ReactiveTaskTests/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Profile; }; diff --git a/ReactiveTask.xcodeproj/xcshareddata/xcschemes/ReactiveTask.xcscheme b/ReactiveTask.xcodeproj/xcshareddata/xcschemes/ReactiveTask.xcscheme index 4fa3d93..e027fea 100644 --- a/ReactiveTask.xcodeproj/xcshareddata/xcschemes/ReactiveTask.xcscheme +++ b/ReactiveTask.xcodeproj/xcshareddata/xcschemes/ReactiveTask.xcscheme @@ -1,6 +1,6 @@ Bool { switch (lhs, rhs) { - case let (.ShellTaskFailed(lhsTask, lhsCode, lhsErr), .ShellTaskFailed(rhsTask, rhsCode, rhsErr)): + case let (.shellTaskFailed(lhsTask, lhsCode, lhsErr), .shellTaskFailed(rhsTask, rhsCode, rhsErr)): return lhsTask == rhsTask && lhsCode == rhsCode && lhsErr == rhsErr - case let (.POSIXError(lhsCode), .POSIXError(rhsCode)): + case let (.posixError(lhsCode), .posixError(rhsCode)): return lhsCode == rhsCode default: diff --git a/ReactiveTask/Task.swift b/ReactiveTask/Task.swift index 722a1ee..048675f 100644 --- a/ReactiveTask/Task.swift +++ b/ReactiveTask/Task.swift @@ -38,17 +38,17 @@ public struct Task { } /// A GCD group which to wait completion - private static let group = dispatch_group_create() + fileprivate static let group = DispatchGroup() /// wait for all task termination public static func waitForAllTaskTermination() { - dispatch_group_wait(Task.group, DISPATCH_TIME_FOREVER) + let _ = Task.group.wait(timeout: DispatchTime.distantFuture) } } private extension String { var escaped: String { - if rangeOfCharacterFromSet(.whitespaceCharacterSet()) != nil { + if rangeOfCharacter(from: .whitespaces) != nil { return "\"\(self)\"" } else { return self @@ -58,7 +58,7 @@ private extension String { extension Task: CustomStringConvertible { public var description: String { - return "\(launchPath) \(arguments.map { $0.escaped }.joinWithSeparator(" "))" + return "\(launchPath) \(arguments.map { $0.escaped }.joined(separator: " "))" } } @@ -80,7 +80,7 @@ private func ==(lhs: [Key: Value]?, rhs: [Key: case let (lhs?, rhs?): return lhs == rhs - case (.None, .None): + case (.none, .none): return true default: @@ -94,7 +94,7 @@ public func ==(lhs: Task, rhs: Task) -> Bool { /// A private class used to encapsulate a Unix pipe. private final class Pipe { - typealias ReadProducer = SignalProducer + typealias ReadProducer = SignalProducer /// The file descriptor for reading data. let readFD: Int32 @@ -103,25 +103,25 @@ private final class Pipe { let writeFD: Int32 /// A GCD queue upon which to deliver I/O callbacks. - let queue: dispatch_queue_t + let queue: DispatchQueue /// A GCD group which to wait completion - let group: dispatch_group_t + let group: DispatchGroup /// Creates an NSFileHandle corresponding to the `readFD`. The file handle /// will not automatically close the descriptor. - var readHandle: NSFileHandle { - return NSFileHandle(fileDescriptor: readFD, closeOnDealloc: false) + var readHandle: FileHandle { + return FileHandle(fileDescriptor: readFD, closeOnDealloc: false) } /// Creates an NSFileHandle corresponding to the `writeFD`. The file handle /// will not automatically close the descriptor. - var writeHandle: NSFileHandle { - return NSFileHandle(fileDescriptor: writeFD, closeOnDealloc: false) + var writeHandle: FileHandle { + return FileHandle(fileDescriptor: writeFD, closeOnDealloc: false) } /// Initializes a pipe object using existing file descriptors. - init(readFD: Int32, writeFD: Int32, queue: dispatch_queue_t, group: dispatch_group_t) { + init(readFD: Int32, writeFD: Int32, queue: DispatchQueue, group: DispatchGroup) { precondition(readFD >= 0) precondition(writeFD >= 0) @@ -132,12 +132,12 @@ private final class Pipe { } /// Instantiates a new descriptor pair. - class func create(queue: dispatch_queue_t, _ group: dispatch_group_t) -> Result { + class func create(_ queue: DispatchQueue, _ group: DispatchGroup) -> Result { var fildes: [Int32] = [ 0, 0 ] if pipe(&fildes) == 0 { - return .Success(self.init(readFD: fildes[0], writeFD: fildes[1], queue: queue, group: group)) + return .success(self.init(readFD: fildes[0], writeFD: fildes[1], queue: queue, group: group)) } else { - return .Failure(.POSIXError(errno)) + return .failure(.posixError(errno)) } } @@ -154,39 +154,45 @@ private final class Pipe { /// anywhere else, as it may close unexpectedly. func transferReadsToProducer() -> ReadProducer { return SignalProducer { observer, disposable in - dispatch_group_enter(self.group) - let channel = dispatch_io_create(DISPATCH_IO_STREAM, self.readFD, self.queue) { error in + self.group.enter() + let channel = DispatchIO(type: .stream, fileDescriptor: self.readFD, queue: self.queue) { error in if error == 0 { observer.sendCompleted() } else if error == ECANCELED { observer.sendInterrupted() } else { - observer.sendFailed(.POSIXError(error)) + observer.sendFailed(.posixError(error)) } close(self.readFD) - dispatch_group_leave(self.group) + self.group.leave() } - dispatch_io_set_low_water(channel, 1) - dispatch_io_read(channel, 0, Int.max, self.queue) { (done, data, error) in - if let data = data { - observer.sendNext(data as! NSData) + channel.setLimit(lowWater: 1) + channel.read(offset: 0, length: Int.max, queue: self.queue) { (done, dispatchData, error) in + if let dispatchData = dispatchData { + let bytes = UnsafeMutablePointer.allocate(capacity: dispatchData.count) + dispatchData.copyBytes(to: bytes, count: dispatchData.count) + let data = Data(bytes: bytes, count: dispatchData.count) + bytes.deinitialize(count: dispatchData.count) + bytes.deallocate(capacity: dispatchData.count) + + observer.sendNext(data) } if error == ECANCELED { observer.sendInterrupted() } else if error != 0 { - observer.sendFailed(.POSIXError(error)) + observer.sendFailed(.posixError(error)) } if done { - dispatch_io_close(channel, 0) + channel.close() } } - disposable.addDisposable { - dispatch_io_close(channel, DISPATCH_IO_STOP) + let _ = disposable.add { + channel.close(flags: .stop) } } } @@ -199,44 +205,47 @@ private final class Pipe { /// anywhere else, as it may close unexpectedly. /// /// Returns a producer that will complete or error. - func writeDataFromProducer(producer: SignalProducer) -> SignalProducer<(), TaskError> { + func writeDataFromProducer(_ producer: SignalProducer) -> SignalProducer<(), TaskError> { return SignalProducer { observer, disposable in - dispatch_group_enter(self.group) - let channel = dispatch_io_create(DISPATCH_IO_STREAM, self.writeFD, self.queue) { error in + self.group.enter() + let channel = DispatchIO(type: .stream, fileDescriptor: self.writeFD, queue: self.queue) { error in if error == 0 { observer.sendCompleted() } else if error == ECANCELED { observer.sendInterrupted() } else { - observer.sendFailed(.POSIXError(error)) + observer.sendFailed(.posixError(error)) } close(self.writeFD) - dispatch_group_leave(self.group) + self.group.leave() } producer.startWithSignal { signal, producerDisposable in - disposable.addDisposable(producerDisposable) + disposable.add(producerDisposable) signal.observe(Observer(next: { data in - let dispatchData = dispatch_data_create(data.bytes, data.length, self.queue, nil) - - dispatch_io_write(channel, 0, dispatchData, self.queue) { (done, data, error) in + let dispatchData = data.withUnsafeBytes { (bytes: UnsafePointer) -> DispatchData in + let buffer = UnsafeBufferPointer(start: bytes, count: data.count) + return DispatchData(bytes: buffer) + } + + channel.write(offset: 0, data: dispatchData, queue: self.queue) { (done, data, error) in if error == ECANCELED { observer.sendInterrupted() } else if error != 0 { - observer.sendFailed(.POSIXError(error)) + observer.sendFailed(.posixError(error)) } } }, completed: { - dispatch_io_close(channel, 0) + channel.close() }, interrupted: { observer.sendInterrupted() })) } - disposable.addDisposable { - dispatch_io_close(channel, DISPATCH_IO_STOP) + let _ = disposable.add { + channel.close(flags: .stop) } } } @@ -250,83 +259,83 @@ public protocol TaskEventType { var value: T? { get } /// Maps over the value embedded in a `Success` event. - func map(@noescape transform: T -> U) -> TaskEvent + func map(_ transform: (T) -> U) -> TaskEvent /// Convenience operator for mapping TaskEvents to SignalProducers. - func producerMap(@noescape transform: T -> SignalProducer) -> SignalProducer, Error> + func producerMap(_ transform: (T) -> SignalProducer) -> SignalProducer, Error> } /// Represents events that can occur during the execution of a task that is /// expected to terminate with a result of type T (upon success). public enum TaskEvent: TaskEventType { /// The task is about to be launched. - case Launch(Task) + case launch(Task) /// Some data arrived from the task on `stdout`. - case StandardOutput(NSData) + case standardOutput(Data) /// Some data arrived from the task on `stderr`. - case StandardError(NSData) + case standardError(Data) /// The task exited successfully (with status 0), and value T was produced /// as a result. - case Success(T) + case success(T) /// The resulting value, if the event is `Success`. public var value: T? { - if case let .Success(value) = self { + if case let .success(value) = self { return value } return nil } /// Maps over the value embedded in a `Success` event. - public func map(@noescape transform: T -> U) -> TaskEvent { + public func map(_ transform: (T) -> U) -> TaskEvent { switch self { - case let .Launch(task): - return .Launch(task) + case let .launch(task): + return .launch(task) - case let .StandardOutput(data): - return .StandardOutput(data) + case let .standardOutput(data): + return .standardOutput(data) - case let .StandardError(data): - return .StandardError(data) + case let .standardError(data): + return .standardError(data) - case let .Success(value): - return .Success(transform(value)) + case let .success(value): + return .success(transform(value)) } } /// Convenience operator for mapping TaskEvents to SignalProducers. - public func producerMap(@noescape transform: T -> SignalProducer) -> SignalProducer, Error> { + public func producerMap(_ transform: (T) -> SignalProducer) -> SignalProducer, Error> { switch self { - case let .Launch(task): - return .init(value: .Launch(task)) + case let .launch(task): + return .init(value: .launch(task)) - case let .StandardOutput(data): - return .init(value: .StandardOutput(data)) + case let .standardOutput(data): + return .init(value: .standardOutput(data)) - case let .StandardError(data): - return .init(value: .StandardError(data)) + case let .standardError(data): + return .init(value: .standardError(data)) - case let .Success(value): - return transform(value).map(TaskEvent.Success) + case let .success(value): + return transform(value).map(TaskEvent.success) } } } public func == (lhs: TaskEvent, rhs: TaskEvent) -> Bool { switch (lhs, rhs) { - case let (.Launch(left), .Launch(right)): + case let (.launch(left), .launch(right)): return left == right - case let (.StandardOutput(left), .StandardOutput(right)): + case let (.standardOutput(left), .standardOutput(right)): return left == right - case let (.StandardError(left), .StandardError(right)): + case let (.standardError(left), .standardError(right)): return left == right - case let (.Success(left), .Success(right)): + case let (.success(left), .success(right)): return left == right default: @@ -336,21 +345,21 @@ public func == (lhs: TaskEvent, rhs: TaskEvent) -> Bool { extension TaskEvent: CustomStringConvertible { public var description: String { - func dataDescription(data: NSData) -> String { - return NSString(data: data, encoding: NSUTF8StringEncoding).map { $0 as String } ?? data.description + func dataDescription(_ data: Data) -> String { + return String(data: data, encoding: .utf8) ?? data.description } switch self { - case let .Launch(task): + case let .launch(task): return "launch: \(task)" - case let .StandardOutput(data): + case let .standardOutput(data): return "stdout: " + dataDescription(data) - case let .StandardError(data): + case let .standardError(data): return "stderr: " + dataDescription(data) - case let .Success(value): + case let .success(value): return "success(\(value))" } } @@ -358,7 +367,7 @@ extension TaskEvent: CustomStringConvertible { extension SignalProducer where Value: TaskEventType { /// Maps the values inside a stream of TaskEvents into new SignalProducers. - public func flatMapTaskEvents(strategy: FlattenStrategy, transform: Value.T -> SignalProducer) -> SignalProducer, Error> { + public func flatMapTaskEvents(_ strategy: FlattenStrategy, transform: @escaping (Value.T) -> SignalProducer) -> SignalProducer, Error> { return self.flatMap(strategy) { taskEvent in return taskEvent.producerMap(transform) } @@ -379,7 +388,7 @@ extension Signal where Value: TaskEventType { .map { event in return event.value } - .ignoreNil() + .skipNil() } } @@ -392,57 +401,57 @@ extension Signal where Value: TaskEventType { /// /// - Returns: A producer that will launch the task when started, then send /// `TaskEvent`s as execution proceeds. -public func launchTask(task: Task, standardInput: SignalProducer? = nil) -> SignalProducer, TaskError> { +public func launchTask(_ task: Task, standardInput: SignalProducer? = nil) -> SignalProducer, TaskError> { return SignalProducer { observer, disposable in - let queue = dispatch_queue_create(task.description, DISPATCH_QUEUE_SERIAL) + let queue = DispatchQueue(label: task.description, attributes: []) let group = Task.group - let rawTask = NSTask() - rawTask.launchPath = task.launchPath - rawTask.arguments = task.arguments + let process = Process() + process.launchPath = task.launchPath + process.arguments = task.arguments if let cwd = task.workingDirectoryPath { - rawTask.currentDirectoryPath = cwd + process.currentDirectoryPath = cwd } if let env = task.environment { - rawTask.environment = env + process.environment = env } var stdinProducer: SignalProducer<(), TaskError> = .empty if let input = standardInput { switch Pipe.create(queue, group) { - case let .Success(pipe): - rawTask.standardInput = pipe.readHandle + case let .success(pipe): + process.standardInput = pipe.readHandle stdinProducer = pipe.writeDataFromProducer(input).on(started: { close(pipe.readFD) }) - case let .Failure(error): + case let .failure(error): observer.sendFailed(error) return } } SignalProducer(result: Pipe.create(queue, group) &&& Pipe.create(queue, group)) - .flatMap(.Merge) { stdoutPipe, stderrPipe -> SignalProducer, TaskError> in + .flatMap(.merge) { stdoutPipe, stderrPipe -> SignalProducer, TaskError> in let stdoutProducer = stdoutPipe.transferReadsToProducer() let stderrProducer = stderrPipe.transferReadsToProducer() enum Aggregation { - case Value(NSData) - case Failed(TaskError) - case Interrupted + case value(Data) + case failed(TaskError) + case interrupted var producer: Pipe.ReadProducer { switch self { - case let .Value(data): + case let .value(data): return .init(value: data) - case let .Failed(error): + case let .failed(error): return .init(error: error) - case .Interrupted: + case .interrupted: return SignalProducer { observer, _ in observer.sendInterrupted() } @@ -451,63 +460,63 @@ public func launchTask(task: Task, standardInput: SignalProducer TaskEvent) -> Pipe.ReadProducer { + func startAggregating(producer: Pipe.ReadProducer, chunk: @escaping (Data) -> TaskEvent) -> Pipe.ReadProducer { let aggregated = MutableProperty(nil) producer.startWithSignal { signal, signalDisposable in disposable += signalDisposable - let aggregate = NSMutableData() + var aggregate = Data() signal.observe(Observer(next: { data in observer.sendNext(chunk(data)) - aggregate.appendData(data) + aggregate.append(data) }, failed: { error in observer.sendFailed(error) - aggregated.value = .Failed(error) + aggregated.value = .failed(error) }, completed: { - aggregated.value = .Value(aggregate) + aggregated.value = .value(aggregate) }, interrupted: { - aggregated.value = .Interrupted + aggregated.value = .interrupted })) } return aggregated.producer - .ignoreNil() - .flatMap(.Concat) { $0.producer } + .skipNil() + .flatMap(.concat) { $0.producer } } - let stdoutAggregated = startAggregating(stdoutProducer, chunk: TaskEvent.StandardOutput) - let stderrAggregated = startAggregating(stderrProducer, chunk: TaskEvent.StandardError) + let stdoutAggregated = startAggregating(producer: stdoutProducer, chunk: TaskEvent.standardOutput) + let stderrAggregated = startAggregating(producer: stderrProducer, chunk: TaskEvent.standardError) - rawTask.standardOutput = stdoutPipe.writeHandle - rawTask.standardError = stderrPipe.writeHandle + process.standardOutput = stdoutPipe.writeHandle + process.standardError = stderrPipe.writeHandle - dispatch_group_enter(group) - rawTask.terminationHandler = { nstask in + group.enter() + process.terminationHandler = { nstask in let terminationStatus = nstask.terminationStatus if terminationStatus == EXIT_SUCCESS { // Wait for stderr to finish, then pass // through stdout. disposable += stderrAggregated .then(stdoutAggregated) - .map(TaskEvent.Success) + .map(TaskEvent.success) .start(observer) } else { // Wait for stdout to finish, then pass // through stderr. disposable += stdoutAggregated .then(stderrAggregated) - .flatMap(.Concat) { data -> SignalProducer, TaskError> in - let errorString = (data.length > 0 ? NSString(data: data, encoding: NSUTF8StringEncoding) as? String : nil) - return SignalProducer(error: .ShellTaskFailed(task, exitCode: terminationStatus, standardError: errorString)) + .flatMap(.concat) { data -> SignalProducer, TaskError> in + let errorString = (data.count > 0 ? String(data: data, encoding: .utf8) : nil) + return SignalProducer(error: .shellTaskFailed(task, exitCode: terminationStatus, standardError: errorString)) } .start(observer) } - dispatch_group_leave(group) + group.leave() } - observer.sendNext(.Launch(task)) - rawTask.launch() + observer.sendNext(.launch(task)) + process.launch() close(stdoutPipe.writeFD) close(stderrPipe.writeFD) @@ -515,13 +524,13 @@ public func launchTask(task: Task, standardInput: SignalProducer