From a52b6bf5dc29d3c37ea0da30a94a66601d44607c Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Thu, 10 Aug 2017 13:42:31 -0700 Subject: [PATCH] Add Xcode 9 support (#5) * Add Xcode 9 support With the large number of simulator changes in Xcode 9 came a change where the notification we're hijacking needs to send along the UDIDs of the simulators where the change should take effect. This is because you can have multiple running simulators with different locations. To do this, for now, we're just reading the output of `simctl list` and sending all booted UDIDs along. * Use JSON output instead --- sources/main.swift | 8 ++++++-- sources/notification.swift | 3 ++- sources/simulators.swift | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 sources/simulators.swift diff --git a/sources/main.swift b/sources/main.swift index eefb334..3c9e4c3 100644 --- a/sources/main.swift +++ b/sources/main.swift @@ -17,8 +17,12 @@ guard let command = commands[flag] else { switch command(Array(arguments)) { case .success(let coordinate) where coordinate.isValid: - print("Setting location to \(coordinate.latitude) \(coordinate.longitude)") - postNotification(for: coordinate) + do { + postNotification(for: coordinate, to: try getBootedSimulators()) + print("Setting location to \(coordinate.latitude) \(coordinate.longitude)") + } catch let error as SimulatorFetchError { + exitWithUsage(error: error.rawValue) + } case .success(let coordinate): exitWithUsage(error: "Coordinate: \(coordinate) is invalid") case .failure(let error): diff --git a/sources/notification.swift b/sources/notification.swift index 2439a78..c0a0176 100644 --- a/sources/notification.swift +++ b/sources/notification.swift @@ -3,10 +3,11 @@ import Foundation private let kNotificationName = "com.apple.iphonesimulator.simulateLocation" -func postNotification(for coordinate: CLLocationCoordinate2D) { +func postNotification(for coordinate: CLLocationCoordinate2D, to simulators: [String]) { let userInfo: [AnyHashable: Any] = [ "simulateLocationLatitude": coordinate.latitude, "simulateLocationLongitude": coordinate.longitude, + "simulateLocationDevices": simulators, ] let notification = Notification(name: Notification.Name(rawValue: kNotificationName), object: nil, diff --git a/sources/simulators.swift b/sources/simulators.swift new file mode 100644 index 0000000..90301e1 --- /dev/null +++ b/sources/simulators.swift @@ -0,0 +1,42 @@ +import Foundation + +enum SimulatorFetchError: String, Error { + case simctlFailed = "Running `simctl list` failed" + case failedToReadOutput = "Failed to read output from simctl" + case noBootedSimulators = "No simulators are currently booted" +} + +func getBootedSimulators() throws -> [String] { + let task = Process() + task.launchPath = "/usr/bin/xcrun" + task.arguments = ["simctl", "list", "-j", "devices"] + + let pipe = Pipe() + task.standardOutput = pipe + + task.launch() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + task.waitUntilExit() + pipe.fileHandleForReading.closeFile() + + if task.terminationStatus != 0 { + throw SimulatorFetchError.simctlFailed + } + + guard let json = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else { + throw SimulatorFetchError.failedToReadOutput + } + + let devices = json["devices"] as? [String: [[String: String]]] ?? [:] + let bootedIDs = devices + .flatMap { $1 } + .filter { $0["state"] == "Booted" } + .flatMap { $0["udid"] } + + if bootedIDs.isEmpty { + throw SimulatorFetchError.noBootedSimulators + } + + return bootedIDs +}