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

Add support for visionOS. #31

Merged
merged 13 commits into from
Aug 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
48 changes: 33 additions & 15 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,47 @@
}
},
{
"identity" : "swift-argument-parser",
"identity" : "stackotter-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stackotter/swift-argument-parser",
"location" : "https://github.com/Wabi-Studios/stackotter-parser.git",
"state" : {
"branch" : "main",
"revision" : "36f73bea370315e7a5af1f450fa013180ba2c584"
"revision" : "6639417d4da09cba76b2dea87362ce3ff1c157db"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1",
"version" : "1.1.4"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "ce9c0d897db8a840c39de64caaa9b60119cf4be8",
"version" : "0.8.1"
"revision" : "5da6989aae464f324eef5c5b52bdb7974725ab81",
"version" : "1.0.0"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"state" : {
"revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6",
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
"version" : "1.3.0"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
Expand All @@ -95,8 +113,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log",
"state" : {
"revision" : "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version" : "1.4.2"
"revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed",
"version" : "1.5.3"
}
},
{
Expand All @@ -113,8 +131,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-parsing.git",
"state" : {
"revision" : "28d32e9ace1c4c43f5e5a177be837a202494c2d5",
"version" : "0.9.2"
"revision" : "a0e7d73f462c1c38c59dc40a3969ac40cea42950",
"version" : "0.13.0"
}
},
{
Expand Down Expand Up @@ -168,7 +186,7 @@
"location" : "https://github.com/LebJe/TOMLKit",
"state" : {
"branch" : "main",
"revision" : "3bd15d54d5861e152c1792f1f0b046052190028a"
"revision" : "404c4dd011743461bff12d00a5118d0ed59d630c"
}
},
{
Expand All @@ -194,17 +212,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "50a70a9d3583fe228ce672e8923010c8df2deddd",
"version" : "0.2.1"
"revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
"version" : "1.0.2"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams.git",
"state" : {
"revision" : "f47ba4838c30dbd59998a4e4c87ab620ff959e8a",
"version" : "5.0.5"
"revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3",
"version" : "5.0.6"
}
}
],
Expand Down
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.6
// swift-tools-version:5.7

import PackageDescription

Expand All @@ -9,7 +9,7 @@ let package = Package(
.executable(name: "swift-bundler", targets: ["swift-bundler"])
],
dependencies: [
.package(url: "https://github.com/stackotter/swift-argument-parser", branch: "main"),
.package(url: "https://github.com/Wabi-Studios/stackotter-parser.git", branch: "main"),
.package(url: "https://github.com/apple/swift-log", from: "1.4.2"),
.package(url: "https://github.com/pointfreeco/swift-parsing.git", from: "0.7.1"),
.package(url: "https://github.com/LebJe/TOMLKit", branch: "main"),
Expand All @@ -27,7 +27,7 @@ let package = Package(
.executableTarget(
name: "swift-bundler",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "StackOtterArgParser", package: "stackotter-parser"),
.product(name: "Logging", package: "swift-log"),
.product(name: "Parsing", package: "swift-parsing"),
"TOMLKit",
Expand Down
2 changes: 2 additions & 0 deletions Sources/swift-bundler/Bundler/Bundler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func getBundler(for platform: Platform) -> any Bundler.Type {
return MacOSBundler.self
case .iOS, .iOSSimulator:
return IOSBundler.self
case .visionOS, .visionOSSimulator:
return VisionOSBundler.self
case .linux:
fatalError("Unimplemented")
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/swift-bundler/Bundler/PlistCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ enum PlistCreator {
entries["MinimumOSVersion"] = platformVersion
entries["CFBundleSupportedPlatforms"] = ["iPhoneOS"]
entries["UILaunchScreen"] = [String: Any]()
case .visionOS, .visionOSSimulator:
entries["MinimumOSVersion"] = platformVersion
entries["CFBundleSupportedPlatforms"] = ["XROS"]
entries["UISceneConfigurations"] = [String: Any]()
case .linux:
break
}
Expand Down Expand Up @@ -135,6 +139,10 @@ enum PlistCreator {
// TODO: Make the produced Info.plist for iOS identical to Xcode's
entries["MinimumOSVersion"] = platformVersion
entries["CFBundleSupportedPlatforms"] = ["iPhoneOS"]
case .visionOS, .visionOSSimulator:
// TODO: Make the produced Info.plist for visionOS identical to Xcode's
entries["MinimumOSVersion"] = platformVersion
entries["CFBundleSupportedPlatforms"] = ["XROS"]
case .linux:
break
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/swift-bundler/Bundler/ResourceBundler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ enum ResourceBundler {
"Contents/Resources")
case .iOS, .iOSSimulator:
destinationBundleResources = destinationBundle
case .visionOS, .visionOSSimulator:
destinationBundleResources = destinationBundle
case .linux:
// TODO: Implement on linux
fatalError("TODO: Implement resource bundling for linux")
Expand Down Expand Up @@ -242,6 +244,8 @@ enum ResourceBundler {
directory = bundleResources
case .iOS, .iOSSimulator:
directory = bundle
case .visionOS, .visionOSSimulator:
directory = bundle
case .linux:
// TODO: Implement for linux
fatalError("TODO: Implement resource bundling on linux")
Expand Down Expand Up @@ -280,6 +284,10 @@ enum ResourceBundler {
infoPlist =
bundle
.appendingPathComponent("Info.plist")
case .visionOS, .visionOSSimulator:
infoPlist =
bundle
.appendingPathComponent("Info.plist")
case .linux:
// TODO: Implement for linux
fatalError("Implement for linux")
Expand Down
2 changes: 2 additions & 0 deletions Sources/swift-bundler/Bundler/Runner/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Foundation
enum Device {
case macOS
case iOS
case visionOS
case linux
case iOSSimulator(id: String)
case visionOSSimulator(id: String)
}
94 changes: 94 additions & 0 deletions Sources/swift-bundler/Bundler/Runner/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ enum Runner {
arguments: arguments,
environmentVariables: environmentVariables
)
case .visionOS:
return runVisionOSApp(
bundle: bundle,
arguments: arguments,
environmentVariables: environmentVariables
)
case .visionOSSimulator(let id):
return runVisionOSSimulatorApp(
bundle: bundle,
bundleIdentifier: bundleIdentifier,
simulatorId: id,
arguments: arguments,
environmentVariables: environmentVariables
)
case .linux:
// TODO: Implement linux app running
fatalError("TODO: Implement linux app running")
Expand Down Expand Up @@ -189,4 +203,84 @@ enum Runner {
return .failedToRunOnIOSSimulator(error)
}
}

/// Runs an app on the first connected visionOS device.
/// - Parameters:
/// - bundle: The app bundle to run.
/// - arguments: Command line arguments to pass to the app.
/// - environmentVariables: Environment variables to pass to the app.
/// - Returns: A failure if an error occurs.
static func runVisionOSApp(
bundle: URL,
arguments: [String],
environmentVariables: [String: String]
) -> Result<Void, RunnerError> {
// `xros-deploy` is explicitly resolved (instead of allowing `Process.create`
// to handle running programs located on the user's PATH) so that a detailed
// error message can be emitted for this easy misconfiguration issue.
return Process.locate("xros-deploy").mapError { error in
.failedToLocateVisionOSDeploy(error)
}.flatMap { xrosDeployExecutable in
let environmentArguments: [String]
if !environmentVariables.isEmpty {
// TODO: correctly escape keys and values
let value = environmentVariables.map { key, value in
"\(key)=\(value)"
}.joined(separator: " ")
environmentArguments = ["--envs", "\(value)"]
} else {
environmentArguments = []
}

return Process.create(
xrosDeployExecutable,
arguments: [
"--noninteractive",
"--bundle", bundle.path,
] + environmentArguments
+ arguments.flatMap { argument in
["--args", argument]
},
runSilentlyWhenNotVerbose: false
).runAndWait().mapError { error in
.failedToRunVisionOSDeploy(error)
}
}
}

/// Runs an app on an visionOS simulator.
/// - Parameters:
/// - bundle: The app bundle to run.
/// - bundleIdentifier: The app's identifier.
/// - simulatorId: The id of the simulator to run.
/// - arguments: Command line arguments to pass to the app.
/// - environmentVariables: Environment variables to pass to the app.
/// - Returns: A failure if an error occurs.
static func runVisionOSSimulatorApp(
bundle: URL,
bundleIdentifier: String,
simulatorId: String,
arguments: [String],
environmentVariables: [String: String]
) -> Result<Void, RunnerError> {
log.info("Preparing simulator")
return SimulatorManager.bootSimulator(id: simulatorId).flatMap { _ in
log.info("Installing app")
return SimulatorManager.installApp(bundle, simulatorId: simulatorId)
}.flatMap { (_: Void) -> Result<Void, SimulatorManagerError> in
log.info("Opening Simulator")
return SimulatorManager.openSimulatorApp()
}.flatMap { (_: Void) -> Result<Void, SimulatorManagerError> in
log.info("Launching \(bundleIdentifier)")
return SimulatorManager.launchApp(
bundleIdentifier,
simulatorId: simulatorId,
connectConsole: true,
arguments: arguments,
environmentVariables: environmentVariables
)
}.mapError { error in
.failedToRunOnVisionOSSimulator(error)
}
}
}
21 changes: 18 additions & 3 deletions Sources/swift-bundler/Bundler/Runner/RunnerError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ enum RunnerError: LocalizedError {
case failedToRunExecutable(ProcessError)
case failedToLocateIOSDeploy(ProcessError)
case failedToRunIOSDeploy(ProcessError)
case failedToLocateVisionOSDeploy(ProcessError)
case failedToRunVisionOSDeploy(ProcessError)
case failedToReadEnvironmentFile(URL, Error)
case failedToParseEnvironmentFileEntry(line: String)
case failedToRunOnIOSSimulator(SimulatorManagerError)
case failedToRunOnVisionOSSimulator(SimulatorManagerError)

var errorDescription: String? {
switch self {
Expand All @@ -23,12 +26,24 @@ enum RunnerError: LocalizedError {
"Failed to run 'ios-deploy'"
"Have you trusted the provisioning profile in settings? (General > VPN & Device Management)"
}.body
case .failedToReadEnvironmentFile(let file, _):
case .failedToLocateVisionOSDeploy:
return Output {
"'xros-deploy' must be installed to run apps on visionOS"
ExampleCommand("brew install xros-deploy")
}.body
case .failedToRunVisionOSDeploy:
return Output {
"Failed to run 'xros-deploy'"
"Have you trusted the provisioning profile in settings? (General > VPN & Device Management)"
}.body
case let .failedToReadEnvironmentFile(file, _):
return "Failed to read contents of environment file '\(file.relativePath)'"
case .failedToParseEnvironmentFileEntry(let line):
case let .failedToParseEnvironmentFileEntry(line):
return "Failed to parse environment file, lines must contain '=': '\(line)'"
case .failedToRunOnIOSSimulator(let error):
case let .failedToRunOnIOSSimulator(error):
return "Failed to run app on iOS simulator: \(error.localizedDescription)"
case let .failedToRunOnVisionOSSimulator(error):
return "Failed to run app on visionOS simulator: \(error.localizedDescription)"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ enum SimulatorManager {
if platform.hasPrefix("com.apple.CoreSimulator.SimRuntime.iOS") {
simulators.append(contentsOf: platformSimulators)
}
if platform.hasPrefix("com.apple.CoreSimulator.SimRuntime.xrOS") {
simulators.append(contentsOf: platformSimulators)
}
}

return .success(simulators)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ArgumentParser
import Foundation
import StackOtterArgParser

/// An architecture to build for.
enum BuildArchitecture: String, CaseIterable, ExpressibleByArgument {
Expand Down
Loading