Skip to content

Commit

Permalink
return
Browse files Browse the repository at this point in the history
  • Loading branch information
lawrence-forooghian committed Aug 2, 2024
1 parent 065ecfe commit 75016a6
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ jobs:
xcode-version: ${{ matrix.tooling.xcode-version }}

- name: Build and run tests
run: swift run --verbose BuildTool --platform ${{ matrix.platform }} --swift-version ${{ matrix.tooling.swift-version }}
run: swift run BuildTool --platform ${{ matrix.platform }} --swift-version ${{ matrix.tooling.swift-version }}
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "abf0212cf7f5807f0a38bb5ea5677cd2221e4e3f168ab056e3453924a395c8ee",
"originHash" : "c439e098601989f67bd2c29a4864484dc4ec12c7727f42cae88c55264aa77869",
"pins" : [
{
"identity" : "ably-cocoa",
Expand Down
24 changes: 12 additions & 12 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ let package = Package(
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
/*.library(*/
/*name: "AblyChat",*/
/*targets: ["AblyChat"]*/
/*),*/
.library(
name: "AblyChat",
targets: ["AblyChat"]
),
],
dependencies: [
.package(url: "https://github.com/ably/ably-cocoa", from: "1.2.0"),
Expand All @@ -26,13 +26,13 @@ let package = Package(
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
/*.target(*/
/*name: "AblyChat", dependencies: [.product(name: "Ably", package: "ably-cocoa")], swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]*/
/*),*/
/*.testTarget(*/
/*name: "AblyChatTests",*/
/*dependencies: ["AblyChat"]*/
/*),*/
.executableTarget(name: "BuildTool", dependencies: [], swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]),
.target(
name: "AblyChat", dependencies: [.product(name: "Ably", package: "ably-cocoa")], swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.testTarget(
name: "AblyChatTests",
dependencies: ["AblyChat"]
),
.executableTarget(name: "BuildTool", dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")], swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]),
]
)
170 changes: 169 additions & 1 deletion Sources/BuildTool/BuildTool.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,171 @@
import ArgumentParser
import Foundation

print("Here it is")
enum DestinationSpecifier {
case platform(String)
case deviceID(String)

var xcodebuildArgument: String {
switch self {
case let .platform(platform):
"platform=\(platform)"
case let .deviceID(deviceID):
"id=\(deviceID)"
}
}
}

enum DestinationStrategy {
case fixed(platform: String)
case lookup(destinationPredicate: DestinationPredicate)
}

struct DestinationPredicate {
// TODO: document
var runtime: String
var deviceType: String
}

enum Platform: String, CaseIterable {
case macOS
case iOS
case tvOS

var destinationStrategy: DestinationStrategy {
// TODO: why is xcodebuild giving locally with iOS "--- xcodebuild: WARNING: Using the first of multiple matching destinations:"
switch self {
case .macOS:
.fixed(platform: "macOS")
case .iOS:
.lookup(destinationPredicate: .init(runtime: "iOS-17-5", deviceType: "iPhone-15"))
case .tvOS:
.lookup(destinationPredicate: .init(runtime: "tvOS-17-5", deviceType: "Apple TV"))
}
}
}

extension Platform: ExpressibleByArgument {
init?(argument: String) {
self.init(rawValue: argument)
}
}

struct SimctlOutput: Codable {
var devices: [String: [Device]]

struct Device: Codable {
var udid: String
var deviceTypeIdentifier: String
}
}

enum Error: Swift.Error {
case terminatedWithExitCode(Int32)
}

// TODO: Is there a better way to make sure that this script has access to macOS APIs that are more recent than the package’s deployment target?
@available(macOS 14, *)
@main
struct BuildTool: ParsableCommand {
@Option var platform: Platform

@Option var swiftVersion: Int

mutating func run() throws {
let destinationSpecifier: DestinationSpecifier = switch platform.destinationStrategy {
case let .fixed(platform):
.platform(platform)
case let .lookup(destinationPredicate):
try .deviceID(fetchDeviceUDID(destinationPredicate: destinationPredicate))
}

try runXcodebuild(action: nil, destination: destinationSpecifier)
try runXcodebuild(action: "test", destination: destinationSpecifier)
}

func runXcodebuild(action: String?, destination: DestinationSpecifier) throws {
var arguments: [String] = []

if let action {
arguments.append(action)
}

arguments.append(contentsOf: ["-scheme", "AblyChat"])
arguments.append(contentsOf: ["-destination", destination.xcodebuildArgument])

arguments.append(contentsOf: [
"SWIFT_TREAT_WARNINGS_AS_ERRORS=YES",
"SWIFT_VERSION=\(swiftVersion)",
])

try run(executableName: "xcodebuild", arguments: arguments)
}

func fetchDeviceUDID(destinationPredicate: DestinationPredicate) throws -> String {
let simctlOutput = try fetchSimctlOutput()

let runtimeIdentifier = "com.apple.CoreSimulator.SimRuntime.\(destinationPredicate.runtime)"
let deviceTypeIdentifier = "com.apple.CoreSimulator.SimDeviceType.\(destinationPredicate.deviceType)"
guard let matchingDevices = simctlOutput.devices[runtimeIdentifier]?.filter({ $0.deviceTypeIdentifier == deviceTypeIdentifier }) else {
fatalError("Couldn’t find a simulator with runtime \(runtimeIdentifier) and device type \(deviceTypeIdentifier); available devices are \(simctlOutput.devices)")
}

if matchingDevices.count > 1 {
fatalError("Found multiple simulators with runtime \(runtimeIdentifier) and device type \(deviceTypeIdentifier); matching devices are \(matchingDevices)")
}

return matchingDevices[0].udid
}

func fetchSimctlOutput() throws -> SimctlOutput {
let data = try runAndReturnStdout(
executableName: "xcrun",
arguments: ["simctl", "list", "--json", "devices", "available"]
)

return try JSONDecoder().decode(SimctlOutput.self, from: data)
}

// I would have liked to use Swift concurrency for this but it felt like it would be a bit of a faff and it’s only a script. There’s a proposal for a Subprocess API coming up in Foundation which will marry Process with Swift concurrency.
private func run(executableName: String, arguments: [String]) throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = [executableName] + arguments

try process.run()
process.waitUntilExit()

if process.terminationStatus != 0 {
throw Error.terminatedWithExitCode(process.terminationStatus)
}
}

// I would have liked to use Swift concurrency for this but it felt like it would be a bit of a faff and it’s only a script. There’s a proposal for a Subprocess API coming up in Foundation which will marry Process with Swift concurrency.
private func runAndReturnStdout(executableName: String, arguments: [String]) throws -> Data {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = [executableName] + arguments

let standardOutput = Pipe()
process.standardOutput = standardOutput

try process.run()

var stdoutData = Data()
while true {
if let data = try standardOutput.fileHandleForReading.readToEnd() {
stdoutData.append(data)
} else {
break
}
}

process.waitUntilExit()

if process.terminationStatus != 0 {
throw Error.terminatedWithExitCode(process.terminationStatus)
}

return stdoutData
}
}

0 comments on commit 75016a6

Please sign in to comment.