Files
xtool-mirror/Sources/XToolSupport/DevCommand.swift
2025-05-12 16:33:57 +05:30

178 lines
5.2 KiB
Swift

import Foundation
import ArgumentParser
import PackLib
import XKit
import Dependencies
struct PackOperation {
struct BuildOptions: ParsableArguments {
@Option(
name: .shortAndLong,
help: "Build with configuration"
) var configuration: BuildConfiguration = .debug
init() {}
init(configuration: BuildConfiguration) {
self.configuration = configuration
}
}
var buildOptions = BuildOptions(configuration: .debug)
var xcode = false
@discardableResult
func run() async throws -> URL {
print("Planning...")
let schema: PackSchema
let configPath = URL(fileURLWithPath: "xtool.yml")
if FileManager.default.fileExists(atPath: configPath.path) {
schema = try await PackSchema(url: configPath)
} else {
schema = .default
print("""
warning: Could not locate configuration file '\(configPath.path)'. Using default \
configuration with 'com.example' organization ID.
""")
}
let buildSettings = try await BuildSettings(
configuration: buildOptions.configuration,
options: []
)
let planner = Planner(
buildSettings: buildSettings,
schema: schema
)
let plan = try await planner.createPlan()
#if os(macOS)
if xcode {
return try await XcodePacker(plan: plan).createProject()
}
#endif
let packer = Packer(
buildSettings: buildSettings,
plan: plan
)
return try await packer.pack()
}
}
struct DevXcodeCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "generate-xcode-project",
abstract: "Generate Xcode project",
discussion: "This option does nothing on Linux"
)
func run() async throws {
try await PackOperation(xcode: true).run()
}
}
struct DevBuildCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "build",
abstract: "Build app with SwiftPM",
discussion: """
This command builds the SwiftPM-based iOS app in the current directory
"""
)
@OptionGroup var packOptions: PackOperation.BuildOptions
@Flag(
help: "Output a .ipa file instead of a .app"
) var ipa = false
func run() async throws {
let url = try await PackOperation(
buildOptions: packOptions
).run()
let finalURL: URL
if ipa {
@Dependency(\.zipCompressor) var compressor
finalURL = url.deletingPathExtension().appendingPathExtension("ipa")
let tmpDir = try TemporaryDirectory(name: "sh.xtool.tmp")
let payloadDir = tmpDir.url.appendingPathComponent("Payload", isDirectory: true)
try FileManager.default.createDirectory(
at: payloadDir,
withIntermediateDirectories: true
)
try FileManager.default.moveItem(at: url, to: payloadDir.appendingPathComponent(url.lastPathComponent))
let ipaURL = try await compressor.compress(directory: payloadDir) { progress in
if let progress {
let percent = Int(progress * 100)
print("\rPackaging... \(percent)%", terminator: "")
} else {
print("\rPackaging...", terminator: "")
}
}
print()
try? FileManager.default.removeItem(at: finalURL)
try FileManager.default.moveItem(at: ipaURL, to: finalURL)
} else {
finalURL = url
}
print("Wrote to \(finalURL.path)")
}
}
struct DevRunCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "run",
abstract: "Build and run app with SwiftPM",
discussion: """
This command deploys the SwiftPM-based iOS app in the current directory
"""
)
@OptionGroup var packOptions: PackOperation.BuildOptions
@OptionGroup var connectionOptions: ConnectionOptions
func run() async throws {
let output = try await PackOperation(buildOptions: packOptions).run()
let token = try AuthToken.saved()
let client = try await connectionOptions.client()
print("Installing to device: \(client.deviceName) (udid: \(client.udid))")
let installDelegate = XToolInstallerDelegate()
let installer = IntegratedInstaller(
udid: client.udid,
lookupMode: .only(client.connectionType),
auth: try token.authData(),
configureDevice: false,
delegate: installDelegate
)
defer { print() }
try await installer.install(app: output)
}
}
struct DevCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "dev",
abstract: "Build and run an xtool SwiftPM project",
subcommands: [
DevXcodeCommand.self,
DevBuildCommand.self,
DevRunCommand.self,
],
defaultSubcommand: DevRunCommand.self
)
}
extension BuildConfiguration: ExpressibleByArgument {}