Files
xtool-mirror/Sources/XToolSupport/HotReloadCommand.swift
Kabir Oberai fa9a374efb Command
2026-06-21 16:08:41 -04:00

98 lines
3.1 KiB
Swift

import Foundation
import ArgumentParser
import PackLib
import XUtils
import Subprocess
struct HotReloadCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "hot-reload",
abstract: "Run an app with support for hot reloading",
)
@Flag(
help: "Use the SwiftPM build system to determine compile commands"
) var swiftpm = false
@Option(
help: .init(
"Use the specified Build Server to determine compile commands",
discussion: """
The file at the specified path should correspond to the BSP \
Connection Details specification. See:
https://build-server-protocol.github.io/docs/overview/server-discovery
""",
)
) var buildServerPath: String?
@Option(
help: "The directory in which to store scratch files"
) var scratchDirectory: String?
@Option(
help: "String to prepend to all env var names"
) var envPrefix: String = ""
@Option(
help: .hidden
) var xloadLibrary: String?
@Argument(
help: .init(
"The command to run with hot reloading",
discussion: """
xtool spawns the provided process with env vars set to enable hot reloading.
"""
)
) var command: [String]
func run() async throws {
let configuration: BuildServerConfiguration
switch (swiftpm, buildServerPath) {
case (true, _?):
throw ValidationError("Both --swiftpm or --build-server-path were provided; please only provide one.")
case (true, nil):
configuration = try await .swiftPM(settings: BuildSettings(configuration: .debug, triple: "arm64-apple-ios-simulator"))
case (false, let serverPath?):
configuration = try await .external(definition: URL(filePath: serverPath))
case (false, nil):
if let discovered = try await BuildServerConfiguration.discover(in: URL(filePath: ".")) {
configuration = discovered
} else {
throw ValidationError("Could not guess build system. Please specify one with --swiftpm or --build-server-path")
}
}
let reloader = try await HotReloader(
configuration: configuration,
scratchDirectory: scratchDirectory.map { FilePath($0) },
xLoadLibrary: xloadLibrary
)
let watchTask = Task {
do {
try await reloader.watch()
} catch {
print("Error: watcher failed: \(error)")
}
}
var arguments = command
let executable = arguments.removeFirst()
try await Subprocess.run(
.name(executable),
arguments: Arguments(arguments),
environment: .inherit.updating(reloader.subprocessEnvironment(prefix: envPrefix)),
platformOptions: .withGracefulShutDown,
output: .standardOutput,
error: .standardError,
)
.checkSuccess()
watchTask.cancel()
await watchTask.value
}
}