import Foundation import ArgumentParser import PackLib import XKit struct DevSetupCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "setup", abstract: "Set up xtool for iOS development", discussion: """ Authenticates with Apple if needed, then adds the iOS SDK to SwiftPM. Equivalent to running `xtool auth && xtool dev sdk` """ ) func run() async throws { try await DevSetupOperation().run() } } struct DevSetupOperation { var quiet = false func run() async throws { try await AuthOperation(logoutFromExisting: false, quiet: quiet).run() switch try DarwinSDK.current()?.isUpToDate() { case true?: if !quiet { print("Darwin SDK is up to date.") } case false?: if !quiet { print("Darwin SDK is outdated.") } fallthrough case nil: let path = try await Console.prompt(""" Now generating the Darwin SDK. Please download Xcode from http://developer.apple.com/download/all/?q=Xcode and enter the path to the downloaded Xcode.xip. Path to Xcode.xip: """) let expanded = (path as NSString).expandingTildeInPath try await InstallSDKOperation(path: expanded).run() } } } 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 func run() async throws { try await PackOperation(buildOptions: packOptions).run() } } 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: "Develop iOS apps with SwiftPM", subcommands: [ DevSetupCommand.self, DevNewCommand.self, DevSDKCommand.self, DevXcodeCommand.self, DevBuildCommand.self, DevRunCommand.self, ], defaultSubcommand: DevRunCommand.self ) } extension BuildConfiguration: ExpressibleByArgument {}