Skip to content

Commit 90debf7

Browse files
committed
Process: Deprecate old methods and properties, implement run()
- Mark launchPath and currentDirectoryPath as deprecated, replace with executableURL and currentDirectoryURL. - Implement run() and Process.run(_:arguments:terminationHandler:) and mark launch() and Process.launchedProcess(path:arguments:) as deprecated. - Update DarwinCompatibility Tests to 10.13 to use run().
1 parent ce50f22 commit 90debf7

File tree

3 files changed

+89
-60
lines changed

3 files changed

+89
-60
lines changed

DarwinCompatibilityTests.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@
642642
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
643643
GCC_WARN_UNUSED_FUNCTION = YES;
644644
GCC_WARN_UNUSED_VARIABLE = YES;
645-
MACOSX_DEPLOYMENT_TARGET = 10.12;
645+
MACOSX_DEPLOYMENT_TARGET = 10.13;
646646
MTL_ENABLE_DEBUG_INFO = YES;
647647
ONLY_ACTIVE_ARCH = YES;
648648
SDKROOT = macosx;
@@ -693,7 +693,7 @@
693693
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
694694
GCC_WARN_UNUSED_FUNCTION = YES;
695695
GCC_WARN_UNUSED_VARIABLE = YES;
696-
MACOSX_DEPLOYMENT_TARGET = 10.12;
696+
MACOSX_DEPLOYMENT_TARGET = 10.13;
697697
MTL_ENABLE_DEBUG_INFO = NO;
698698
SDKROOT = macosx;
699699
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";

Foundation/Process.swift

Lines changed: 66 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// This source file is part of the Swift.org open source project
22
//
3-
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
3+
// Copyright (c) 2014 - 2016, 2018 Apple Inc. and the Swift project authors
44
// Licensed under Apache License v2.0 with Runtime Library Exception
55
//
66
// See http://swift.org/LICENSE.txt for license information
@@ -153,7 +153,7 @@ open class Process: NSObject {
153153
}
154154
}
155155
}
156-
156+
157157
// Create an Process which can be run at a later time
158158
// An Process can only be run once. Subsequent attempts to
159159
// run an Process will raise.
@@ -162,48 +162,36 @@ open class Process: NSObject {
162162
//
163163

164164
public override init() {
165-
165+
166166
}
167-
168-
// These methods can only be set before a launch.
169-
170-
open var launchPath: String?
167+
168+
// These properties can only be set before a launch.
169+
open var executableURL: URL?
170+
open var currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath, isDirectory: true)
171171
open var arguments: [String]?
172172
open var environment: [String : String]? // if not set, use current
173-
174-
open var currentDirectoryPath: String = FileManager.default.currentDirectoryPath
175-
176-
open var executableURL: URL? {
177-
get {
178-
guard let launchPath = self.launchPath else {
179-
return nil
180-
}
181-
182-
return URL(fileURLWithPath: launchPath)
183-
}
184-
set {
185-
self.launchPath = newValue?.path
186-
}
173+
174+
@available(*, deprecated: 4, renamed: "executableURL")
175+
open var launchPath: String? {
176+
get { return executableURL?.path }
177+
set { executableURL = (newValue != nil) ? URL(fileURLWithPath: newValue!) : nil }
187178
}
188-
189-
open var currentDirectoryURL: URL {
190-
get {
191-
return URL(fileURLWithPath: self.currentDirectoryPath)
192-
}
193-
set {
194-
self.currentDirectoryPath = newValue.path
195-
}
179+
180+
@available(*, deprecated: 4, renamed: "currentDirectoryURL")
181+
open var currentDirectoryPath: String {
182+
get { return currentDirectoryURL.path }
183+
set { currentDirectoryURL = URL(fileURLWithPath: newValue) }
196184
}
197-
185+
198186
// Standard I/O channels; could be either a FileHandle or a Pipe
199-
187+
200188
open var standardInput: Any? {
201189
willSet {
202190
precondition(newValue is Pipe || newValue is FileHandle,
203191
"standardInput must be either Pipe or FileHandle")
204192
}
205193
}
206-
194+
207195
open var standardOutput: Any? {
208196
willSet {
209197
precondition(newValue is Pipe || newValue is FileHandle,
@@ -227,7 +215,30 @@ open class Process: NSObject {
227215

228216
// Actions
229217

218+
@available(*, deprecated: 4, renamed: "run")
230219
open func launch() {
220+
do {
221+
try run()
222+
} catch let nserror as NSError {
223+
if let path = nserror.userInfo[NSFilePathErrorKey] as? String, path == currentDirectoryPath {
224+
// Foundation throws an NSException when changing the working directory fails,
225+
// and unfortunately launch() is not marked `throws`, so we get away with a
226+
// fatalError.
227+
switch CocoaError.Code(rawValue: nserror.code) {
228+
case .fileReadNoSuchFile:
229+
fatalError("Process: The specified working directory does not exist.")
230+
case .fileReadNoPermission:
231+
fatalError("Process: The specified working directory cannot be accessed.")
232+
default:
233+
fatalError("Process: The specified working directory cannot be set.")
234+
}
235+
}
236+
} catch {
237+
fatalError(String(describing: error))
238+
}
239+
}
240+
241+
open func run() throws {
231242

232243
self.processLaunchedCondition.lock()
233244

@@ -236,9 +247,8 @@ open class Process: NSObject {
236247
Process.setup()
237248

238249
// Ensure that the launch path is set
239-
240-
guard let launchPath = self.launchPath else {
241-
fatalError()
250+
guard let launchPath = self.executableURL?.path else {
251+
throw NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError)
242252
}
243253

244254
// Convert the arguments array into a posix_spawn-friendly format
@@ -260,7 +270,6 @@ open class Process: NSObject {
260270
for arg in argv ..< argv + args.count {
261271
free(UnsafeMutableRawPointer(arg.pointee))
262272
}
263-
264273
argv.deallocate()
265274
}
266275

@@ -294,7 +303,7 @@ open class Process: NSObject {
294303
context.version = 0
295304
context.retain = runLoopSourceRetain
296305
context.release = runLoopSourceRelease
297-
context.info = Unmanaged.passUnretained(self).toOpaque()
306+
context.info = Unmanaged.passUnretained(self).toOpaque()
298307

299308
let socket = CFSocketCreateWithNative( nil, taskSocketPair[0], CFOptionFlags(kCFSocketDataCallBack), {
300309
(socket, type, address, data, info ) in
@@ -316,7 +325,7 @@ open class Process: NSObject {
316325
}
317326
#endif
318327
var waitResult : Int32 = 0
319-
328+
320329
repeat {
321330
#if CYGWIN
322331
waitResult = waitpid( process.processIdentifier, exitCodePtrWrapper, 0)
@@ -344,9 +353,9 @@ open class Process: NSObject {
344353
}
345354

346355
// Set the running flag to false
347-
348356
process.isRunning = false
349-
357+
process.processIdentifier = -1
358+
350359
// Invalidate the source and wake up the run loop, if it's available
351360

352361
CFRunLoopSourceInvalidate(process.runLoopSource)
@@ -417,24 +426,16 @@ open class Process: NSObject {
417426

418427
let fileManager = FileManager()
419428
let previousDirectoryPath = fileManager.currentDirectoryPath
420-
if !fileManager.changeCurrentDirectoryPath(currentDirectoryPath) {
421-
// Foundation throws an NSException when changing the working directory fails,
422-
// and unfortunately launch() is not marked `throws`, so we get away with a
423-
// fatalError.
424-
switch errno {
425-
case ENOENT:
426-
fatalError("Process: The specified working directory does not exist.")
427-
case EACCES:
428-
fatalError("Process: The specified working directory cannot be accessed.")
429-
default:
430-
fatalError("Process: The specified working directory cannot be set.")
431-
}
429+
if !fileManager.changeCurrentDirectoryPath(currentDirectoryURL.path) {
430+
throw _NSErrorWithErrno(errno, reading: true, url: currentDirectoryURL)
432431
}
433432

434433
// Launch
435434

436435
var pid = pid_t()
437-
posix(posix_spawn(&pid, launchPath, &fileActions, nil, argv, envp))
436+
guard posix_spawn(&pid, launchPath, &fileActions, nil, argv, envp) == 0 else {
437+
throw _NSErrorWithErrno(errno, reading: true, path: launchPath)
438+
}
438439

439440
// Reset the previous working directory path.
440441
fileManager.changeCurrentDirectoryPath(previousDirectoryPath)
@@ -506,10 +507,18 @@ open class Process: NSObject {
506507
*/
507508
open var terminationHandler: ((Process) -> Void)?
508509
open var qualityOfService: QualityOfService = .default // read-only after the process is launched
509-
}
510510

511-
extension Process {
512-
511+
512+
open class func run(_ url: URL, arguments: [String], terminationHandler: ((Process) -> Void)? = nil) throws -> Process {
513+
let process = Process()
514+
process.executableURL = url
515+
process.arguments = arguments
516+
process.terminationHandler = terminationHandler
517+
try process.run()
518+
return process
519+
}
520+
521+
@available(*, deprecated: 4, renamed: "run(_:arguments:terminationHandler:)")
513522
// convenience; create and launch
514523
open class func launchedProcess(launchPath path: String, arguments: [String]) -> Process {
515524
let process = Process()
@@ -519,7 +528,7 @@ extension Process {
519528

520529
return process
521530
}
522-
531+
523532
// poll the runLoop in defaultMode until process completes
524533
open func waitUntilExit() {
525534

TestFoundation/TestProcess.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// This source file is part of the Swift.org open source project
22
//
3-
// Copyright (c) 2015 - 2016 Apple Inc. and the Swift project authors
3+
// Copyright (c) 2015 - 2016, 2018 Apple Inc. and the Swift project authors
44
// Licensed under Apache License v2.0 with Runtime Library Exception
55
//
66
// See http://swift.org/LICENSE.txt for license information
@@ -28,6 +28,7 @@ class TestProcess : XCTestCase {
2828
("test_passthrough_environment", test_passthrough_environment),
2929
("test_no_environment", test_no_environment),
3030
("test_custom_environment", test_custom_environment),
31+
("test_run", test_run),
3132
]
3233
#endif
3334
}
@@ -286,6 +287,25 @@ class TestProcess : XCTestCase {
286287
XCTFail("Test failed: \(error)")
287288
}
288289
}
290+
291+
func test_run() {
292+
do {
293+
let process = try Process.run(URL(fileURLWithPath: "/bin/sh", isDirectory: false), arguments: ["-c", "exit 123"], terminationHandler: nil)
294+
process.waitUntilExit()
295+
XCTAssertEqual(process.terminationReason, .exit)
296+
XCTAssertEqual(process.terminationStatus, 123)
297+
} catch {
298+
XCTFail("Cant execute /bin/sh: error")
299+
}
300+
301+
do {
302+
let process = try Process.run(URL(fileURLWithPath: "/..", isDirectory: false), arguments: [], terminationHandler: nil)
303+
XCTFail("Somehow executed a directory!")
304+
process.terminate()
305+
} catch {
306+
}
307+
}
308+
289309
#endif
290310
}
291311

0 commit comments

Comments
 (0)