Issue #542

With protocol extension

See code Puma

Build is UsesXcodeBuild is UsesCommandLine
/// Any task that uses command line
public protocol UsesCommandLine: AnyObject {}

public extension UsesCommandLine {
    func runBash(
        workflow: Workflow,
        program: String,
        arguments: [String],
        processHandler: ProcessHandler = DefaultProcessHandler()
    ) throws {
        // Code
    }

    func runProcess(
        _ process: Process,
        workflow: Workflow,
        processHandler: ProcessHandler = DefaultProcessHandler()
    ) throws {
        // Code
    }
}

/// Any task that uses xcodebuild
public protocol UsesXcodeBuild: UsesCommandLine {
    var xcodebuild: Xcodebuild { get set }
}

public extension UsesXcodeBuild {
    func runXcodeBuild(workflow: Workflow) throws {
        try runBash(
            workflow: workflow,
            program: "xcodebuild",
            arguments: xcodebuild.arguments,
            processHandler: XcodeBuildProcessHandler()
        )
    }
}

public class Build: UsesXcodeBuild {
    public var isEnabled = true
    public var xcodebuild = Xcodebuild()
}

Without protocol extension

Build has Xcodebuild has CommandLine
public struct Xcodebuild {
    var arguments: [String] = []

    @discardableResult
    func run(workflow: Workflow) throws -> String {
        return try CommandLine().runBash(
            workflow: workflow,
            program: "xcodebuild",
            arguments: arguments,
            processHandler: XcodeBuildProcessHandler()
        )
    }
}