import Dependencies import Foundation import ShellClient extension ShellCommand { /// Create a ``ShellCommand`` instance that's a wrapper for the `swift build` /// command. /// /// - Parameters: /// - build: The build command to use. public static func swiftBuild(_ build: SwiftBuild) -> Self { build.command } } /// A wrapper around the `swift build` command. This allows you to build a project from /// swift. /// public struct SwiftBuild { @Dependency(\.logger) var logger @Dependency(\.shellClient) var shell /// Represents the default arguments for the build command. public static let defaults: [Argument] = [ .disableSandBox, .xSwiftC("-cross-module-optimization") ] /// The arguments for the build command. public let arguments: [Argument] /// The configuration for the build command. public let configuration: Configuration /// Create a ``SwiftBuild`` instance for the ``Configuration/debug`` configuration. /// /// - Parameters: /// - arguments: The arguments for the `swift build` command. public static func debug(_ arguments: [Argument] = Self.defaults) -> Self { .init(configuration: .debug, arguments: arguments) } /// Create a ``SwiftBuild`` instance for the ``Configuration/release`` configuration. /// /// - Parameters: /// - arguments: The arguments for the `swift build` command. public static func release(_ arguments: [Argument] = Self.defaults) -> Self { .init(configuration: .release, arguments: arguments) } internal init( configuration: Configuration = .debug, arguments: [Argument] = Self.defaults ) { self.arguments = arguments self.configuration = configuration } internal var command: ShellCommand { .init( shell: .env, [ "swift", "build", "--configuration", "\(configuration)" ] + arguments.strings ) } internal func run() throws { logger.info("\("Building.".blue)") try withDependencies { $0.logger.logLevel = .debug } operation: { try shell.foreground(command) } } /// Represents an argument for the ``SwiftBuild`` command. public enum Argument { /// Disable the sandbox. case disableSandBox /// Pass a string through to the `swift build` command. case custom([String]) /// Pass an `-Xswiftc` compiler flag through to the `swift build` command. case xSwiftC(String) } /// Represents the configuration for the ``SwiftBuild`` command. public enum Configuration: String, CustomStringConvertible, CaseIterable { /// The debug configuration. case debug /// The release configuration. case release public var description: String { rawValue } } } //extension ShellClient { // // /// Reads contents at the given file path, then allows the caller to act on the // /// results after "nil" has been replaced with the current version string. // /// // /// - Parameters: // /// - filePath: The file path to replace nil in. // /// - workingDirectory: Customize the working directory for the command. // /// - closure: The closure to run with the updated file content string. // public func replacingNilWithVersionString( // in filePath: String, // from workingDirectory: String? = nil, // _ closure: @escaping (String) throws -> Void // ) throws { // @Dependency(\.fileClient) var fileClient: FileClient // @Dependency(\.gitVersionClient) var gitClient: GitVersionClient // @Dependency(\.logger) var logger: Logger // // let currentVersion = try gitClient.currentVersion(in: workingDirectory) // let originalContents = try fileClient.readAsString(path: filePath) // // logger.debug("Setting version: \(currentVersion)") // // let updatedContents = originalContents // .replacingOccurrences(of: "nil", with: "\"\(currentVersion)\"") // // logger.debug("Set version") // logger.debug("Updated contents:") // logger.debug("\(updatedContents)") // // try closure(updatedContents) // } // // // /// Replace nil in the given file path and then run the given closure. // /// // /// > Note: The file contents will be reset back to nil after the closure operation. // /// // /// - Parameters: // /// - filePath: The file path to replace nil in. // /// - workingDirectory: Customize the working directory for the command. // /// - build: The swift builder to use. // public func replacingNilWithVersionString( // in filePath: String, // from workingDirectory: String? = nil, // _ closure: @escaping () throws -> Void // ) throws { // @Dependency(\.fileClient) var fileClient: FileClient // @Dependency(\.logger) var logger: Logger // // // grab the original contents, to set it back when done. // let originalContents = try fileClient.readAsString(path: filePath) // // try self.replacingNilWithVersionString( // in: filePath, // from: workingDirectory // ) { update in // try fileClient.write(string: update, to: filePath) // defer { try! fileClient.write(string: originalContents, to: filePath) } // // try closure() // } // // } // // /// Replace nil in the given file path and then build the project, using the // /// given builder. // /// // /// > Note: The file contents will be reset back to nil after the build operation. // /// // /// - Parameters: // /// - filePath: The file path to replace nil in. // /// - workingDirectory: Customize the working directory for the command. // /// - build: The swift builder to use. // public func replacingNilWithVersionString( // in filePath: String, // from workingDirectory: String? = nil, // build: SwiftBuild // ) throws { // try replacingNilWithVersionString( // in: filePath, // from: workingDirectory, // build.run // ) // } //} fileprivate extension Array where Element == SwiftBuild.Argument { var strings: [String] { reduce(into: [String]()) { current, argument in switch argument { case .disableSandBox: current.append("--disable-sandbox") case .custom(let strings): current += strings case .xSwiftC(let value): current += ["-Xswiftc", value] } } } }