Skip to content

Commit

Permalink
wip build inside a Swift script instead
Browse files Browse the repository at this point in the history
TODO sort out linting, gitignore, etc
  • Loading branch information
lawrence-forooghian committed Aug 2, 2024
1 parent d06fe91 commit d8043d9
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 98 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,5 @@ jobs:
with:
xcode-version: ${{ matrix.tooling.xcode-version }}

# TODO OS version, and ambiguity in platforms, and no match
- run: xcodebuild -scheme AblyChat -destination "platform=${{ matrix.platform }}" SWIFT_TREAT_WARNINGS_AS_ERRORS=YES SWIFT_VERSION=${{ matrix.tooling.swift-version }}
- run: xcodebuild test -scheme AblyChat -destination "platform=${{ matrix.platform }}" SWIFT_TREAT_WARNINGS_AS_ERRORS=YES SWIFT_VERSION=${{ matrix.tooling.swift-version }}
- run: swift run BuildTool --platform ${{ matrix.platform }} --swift-version ${{ matrix.swift-version }}
working-directory: ./script
11 changes: 1 addition & 10 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "bb9e762d580eaf6bdfc4c2b4ef738357fe7528fca621f88bf37eab5f324406ae",
"originHash" : "10b6989ad831255bec51a3b6a49da12c471d57a9611e6834de10ee5a8863e652",
"pins" : [
{
"identity" : "ably-cocoa",
Expand Down Expand Up @@ -28,15 +28,6 @@
"version" : "0.4.0"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "41982a3656a71c768319979febd796c6fd111d5c",
"version" : "1.5.0"
}
},
{
"identity" : "swiftformat",
"kind" : "remoteSourceControl",
Expand Down
2 changes: 0 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ let package = Package(
.package(url: "https://github.com/ably/ably-cocoa", from: "1.2.0"),
.package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", from: "0.55.1"),
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.54.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
Expand All @@ -33,6 +32,5 @@ let package = Package(
name: "AblyChatTests",
dependencies: ["AblyChat"]
),
.executableTarget(name: "TestScript", dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")], swiftSettings: [.enableUpcomingFeature("StrictConcurrency")]),
]
)
83 changes: 0 additions & 83 deletions Sources/TestScript/GetDestination.swift

This file was deleted.

15 changes: 15 additions & 0 deletions script/Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"originHash" : "422fe248db2d0a796fdbfcd73b31069c259a3cc7bfb84dd769af3bd9c6fd84c4",
"pins" : [
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "41982a3656a71c768319979febd796c6fd111d5c",
"version" : "1.5.0"
}
}
],
"version" : 3
}
19 changes: 19 additions & 0 deletions script/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

// TODO: explain why a separate package

let package = Package(
name: "AblyChatBuildTools",
platforms: [
.macOS(.v14),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
],
targets: [
.executableTarget(name: "BuildTool", dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")], swiftSettings: [.enableUpcomingFeature("StrictConcurrency")]),
]
)
167 changes: 167 additions & 0 deletions script/Sources/BuildTool/BuildTool.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import ArgumentParser
import Foundation

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

enum Platform: String {
case macOS
case iOS
case tvOS

// TODO: why is xcodebuild giving locally with iOS "--- xcodebuild: WARNING: Using the first of multiple matching destinations:"
var destinationPredicate: DestinationPredicate? {
switch self {
case .macOS: nil
case .iOS: .init(runtime: "iOS-17-4", deviceType: "iPhone-15")
case .tvOS: .init(runtime: "tvOS", deviceType: "TODO")
}
}
}

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)
}

@main
struct BuildTool: ParsableCommand {
@Option
var platform: Platform

@Option
var swiftVersion: Int

mutating func run() throws {
print("got platform \(platform)")
let deviceUDID: String? = if let destinationPredicate = platform.destinationPredicate {
try fetchDeviceUDID(destinationPredicate: destinationPredicate)
} else {
nil
}

// This tool runs from the repo’s `script` directory, so change into the repo root
FileManager.default.changeCurrentDirectoryPath("..")
try runXcodebuild(action: nil, deviceUDID: deviceUDID)
try runXcodebuild(action: "test", deviceUDID: deviceUDID)
}

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

if let action {
arguments.append(action)
}

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

if let deviceUDID {
arguments.append(contentsOf: ["-destination", "id=\(deviceUDID)"])
}

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"]
)

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

let semaphore = DispatchSemaphore(value: 0)

process.terminationHandler = { _ in
semaphore.signal()
}

try process.run()

semaphore.wait()

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

let semaphore = DispatchSemaphore(value: 0)

process.terminationHandler = { _ in
semaphore.signal()
}

try process.run()

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

semaphore.wait()

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

return stdoutData
}
}

0 comments on commit d8043d9

Please sign in to comment.