Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CLI tool to list devices #76

Merged
merged 3 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 27 additions & 18 deletions Blocks.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "76d7791b5bda47df7e3d4690c4c3aaf089730707",
"version" : "1.2.1"
"revision" : "e593aba2c6222daad7c4f2732a431eed2c09bb07",
"version" : "1.3.0"
}
},
{
Expand All @@ -41,17 +41,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections",
"state" : {
"revision" : "d029d9d39c87bed85b1c50adee7c41795261a192",
"version" : "1.0.6"
"revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
"version" : "1.1.0"
}
},
{
"identity" : "swift-composable-architecture",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-composable-architecture",
"state" : {
"revision" : "ae491c9e3f66631e72d58db8bb4c27dfc3d3afd4",
"version" : "1.6.0"
"revision" : "115fe5af41d333b6156d4924d7c7058bc77fd580",
"version" : "1.9.2"
}
},
{
Expand All @@ -68,17 +68,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "aedcf6f4cd486ccef5b312ccac85d4b3f6e58605",
"version" : "1.1.2"
"revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c",
"version" : "1.3.0"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "adb04a8e35f07edc001877af9f9f97fcc21d409e",
"version" : "1.2.0"
"revision" : "d3a5af3038a09add4d7682f66555d6212058a3c0",
"version" : "1.2.2"
}
},
{
Expand All @@ -90,40 +90,49 @@
"version" : "1.0.0"
}
},
{
"identity" : "swift-perception",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-perception",
"state" : {
"revision" : "a5bb578d963fcdbffe4fd56c92b2e222f5b02c8a",
"version" : "1.1.2"
}
},
{
"identity" : "swift-snapshot-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
"state" : {
"revision" : "8e68404f641300bfd0e37d478683bb275926760c",
"version" : "1.15.2"
"revision" : "5b0c434778f2c1a4c9b5ebdb8682b28e84dd69bd",
"version" : "1.15.4"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax",
"state" : {
"revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
"version" : "509.1.1"
"revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd",
"version" : "510.0.1"
}
},
{
"identity" : "swiftui-navigation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swiftui-navigation",
"state" : {
"revision" : "78f9d72cf667adb47e2040aa373185c88c63f0dc",
"version" : "1.2.0"
"revision" : "d9e72f3083c08375794afa216fb2f89c0114f303",
"version" : "1.2.1"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "b58e6627149808b40634c4552fcf2f44d0b3ca87",
"version" : "1.1.0"
"revision" : "b13b1d1a8e787a5ffc71ac19dcaf52183ab27ba2",
"version" : "1.1.1"
}
}
],
Expand Down
24 changes: 24 additions & 0 deletions Examples/BlocksCLI/BlocksCLI.xctestplan
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"configurations" : [
{
"id" : "CB799EAE-2D68-4671-A995-ED20F7784350",
"name" : "Configuration 1",
"options" : {

}
}
],
"defaultOptions" : {

},
"testTargets" : [
{
"target" : {
"containerPath" : "container:",
"identifier" : "BlocksCLITests",
"name" : "BlocksCLITests"
}
}
],
"version" : 1
}
6 changes: 5 additions & 1 deletion Examples/BlocksCLI/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ let package = Package(
.macOS(.v10_15),
.iOS(.v13) // CLI not intended to work on iOS but Xcode will fail without this.
],
products: [
.executable(name: "blocks-cli", targets: ["BlocksCLI"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"),
.package(name: "Blocks", path: "../../")
Expand All @@ -20,7 +23,8 @@ let package = Package(
]),
.testTarget(
name: "BlocksCLITests",
dependencies: ["BlocksCLI"]
dependencies: ["BlocksCLI"],
resources: [.process("Resources")]
)
]
)
116 changes: 116 additions & 0 deletions Examples/BlocksCLI/Sources/BlocksCLI/DevTools/ListDevicesCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import ArgumentParser
import Foundation

// import ShellOut
// import TSCBasic
import Blocks

struct ListDevicesCommand: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "list-simulators",
abstract: "List (or filter) the available simulators Xcode provide."
)

@Option var osFilter: String?

@Option var deviceFilter: String?

mutating func run() throws {
let rawAvailableDevicesJSON = CLIUtils.shell("xcrun simctl list --json devices available")

let filteredZippedResults = try find(osName: osFilter, deviceName: deviceFilter, in: rawAvailableDevicesJSON)

switch (osFilter, deviceFilter) {
case (.some, .some):
// We want to output the UDID
precondition(filteredZippedResults.count == 1)
print(filteredZippedResults.first!.simulator.udid)
default:
let output = filteredZippedResults
.sorted()
.map(\.description)
print(output.joined(separator: "\n"))
}
}

func find(osName: String?, deviceName: String?, in rawOutput: String) throws -> [ZipOSSimulator] {
try zip(rawOutput: rawOutput)
.filter { osName == nil ? true : $0.formattedOS == osName }
.filter { deviceName == nil ? true : $0.simulator.name == deviceName }
}

private func zip(rawOutput: String) throws -> [ZipOSSimulator] {
try JSONDecoder()
.decode(DeviceContainer.self, from: Data(rawOutput.utf8))
.devices
.reduce(into: [ZipOSSimulator].init()) { partialResult, newItem in
partialResult.append(contentsOf: newItem.value.map {
ZipOSSimulator(osIdentifier: newItem.key,
simulator: $0)
})
}
}
}

struct DeviceContainer: Codable {
let devices: [String: [Simulator]]
}

struct Simulator: Codable, Equatable {
let lastBootedAt: String?
let dataPath: String
let dataPathSize: Int
let logPath: String
let udid: String
let isAvailable: Bool
let logPathSize: Int?
let deviceTypeIdentifier: String
let state: String
let name: String
}

struct ZipOSSimulator: Comparable, CustomStringConvertible {
let osIdentifier: String
let simulator: Simulator

// MARK: - CustomStringConvertible

var description: String {
"\(simulator.udid): \(formattedOSAndSimulator)"
}

var formattedOS: String {
(try? formatOSIdentifier(osIdentifier)) ?? "n/a"
}

var formattedOSAndSimulator: String {
"\(formattedOS), \(simulator.name)"
}

// MARK: - Comparable

static func < (lhs: ZipOSSimulator, rhs: ZipOSSimulator) -> Bool {
lhs.formattedOSAndSimulator < rhs.formattedOSAndSimulator
}

private func formatOSIdentifier(_ identifier: String) throws -> String {
let pattern = #"com\.apple\.CoreSimulator\.SimRuntime\.([a-zA-Z]+)-(\d+)-(\d+)"#

let regex = try? NSRegularExpression(pattern: pattern, options: [])
let nsrange = NSRange(identifier.startIndex ..< identifier.endIndex, in: identifier)

if let match = regex?.firstMatch(in: identifier, options: [], range: nsrange) {
if let osRange = Range(match.range(at: 1), in: identifier),
let majorVersionRange = Range(match.range(at: 2), in: identifier),
let minorVersionRange = Range(match.range(at: 3), in: identifier) {
let os = identifier[osRange]
let majorVersion = identifier[majorVersionRange]
let minorVersion = identifier[minorVersionRange]

return "\(os) \(majorVersion).\(minorVersion)"
}
}

throw SimpleMessageError(message: "formatDeviceIdentifier could not format \(identifier)")
}
}
3 changes: 2 additions & 1 deletion Examples/BlocksCLI/Sources/BlocksCLI/RootCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ struct BlocksCLI: AsyncParsableCommand {
ReadBarcodeCommand.self,
ReadPasswordCommand.self,
PrintColorsCommand.self,
LintCopyCommand.self
LintCopyCommand.self,
ListDevicesCommand.self
]
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Blocks
@testable import BlocksCLI
import Foundation
import XCTest

final class ListDevicesCommandTests: XCTestCase {
func testParsingOfOutput() throws {
let testData: Data = try Bundle.module.contents(
ofResource: "simctl-list-devices",
withExtension: "json"
)

let decoder = JSONDecoder()
let result = try decoder.decode(DeviceContainer.self, from: testData)

let all17dot2Devices = try XCTUnwrap(result.devices["com.apple.CoreSimulator.SimRuntime.iOS-17-2"])
XCTAssert(all17dot2Devices.count > 5)
}
}
Loading
Loading