-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip build inside a Swift script instead
TODO sort out linting, gitignore, etc
- Loading branch information
1 parent
d06fe91
commit d8043d9
Showing
7 changed files
with
204 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")]), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |