# Develop a command-line tool using Swift Concurrency

**Updated for ArgumentParser 1.1.0 and its native support of async/await**

Apple's `ArgumentParser` library makes developing a command-line tool in Swift significant easier. The library parses the command-line arguments, instantiates your command type, and then either executes your run() method or exits with a useful message.

> Straightforward, type-safe argument parsing for Swift

```swift
import ArgumentParser

@main
struct Repeat: ParsableCommand {
    @Flag(help: "Include a counter with each repetition.")
    var includeCounter = false

    @Option(name: .shortAndLong, help: "The number of times to repeat 'phrase'.")
    var count: Int?

    @Argument(help: "The phrase to repeat.")
    var phrase: String

    mutating func run() throws {
        let repeatCount = count ?? .max

        for i in 1...repeatCount {
            if includeCounter {
                print("\(i): \(phrase)")
            } else {
                print(phrase)
            }
        }
    }
}
```

You might want to use modern Swift [Concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) and await an asynchronous function in your command-line program. Trying so will result in a compilation error.

![Compilation error when awaiting an async function in a function which has no async keyword](https://cdn.hashnode.com/res/hashnode/image/upload/v1641937603775/vmweukeaY.png)

Maybe changing the definition of the `run` function to add the `async` keyword?

Nope, because this particular function is needed "as-is" in the struct according to its `ParsableCommand` protocol requirement.

Does the ArgumentParser framework support other functions for async/await?

### 1.1.0 and up

Swift ArgumentParser `1.1.0` brings out-of-the-box support for async/await.

%[https://github.com/apple/swift-argument-parser/pull/404]

Example

```Swift
import ArgumentParser

@main
struct Repeat: ParsableCommand, AsyncParsableCommand {
    @Flag(help: "Include a counter with each repetition.")
    var includeCounter = false

    @Option(name: .shortAndLong, help: "The number of times to repeat 'phrase'.")
    var count: Int?

    @Argument(help: "The phrase to repeat.")
    var phrase: String

    // A command's run() method now supports async/await when the command conforms to AsyncParsableCommand. (#404)
    mutating func run() async throws {
        let repeatCount = count ?? .max
        await asyncRepeat(phrase: phrase, repeatCount: repeatCount)
    }

    func asyncRepeat(phrase: String, repeatCount: Int) async {
        try? await Task.sleep(nanoseconds: 5 * 1_000_000_000) // dummy use of an aysnc operation .. wait 5 seconds :)
        for i in 1...repeatCount {
            if includeCounter {
                print("\(i): \(phrase)")
            } else {
                print(phrase)
            }
        }
    }
}
```

The secret is to conform your command to `AsyncParsableCommand` protocol and then use the keyword `async` in the `run` function declaration. 

### Before 1.0.0

At the time I wrote this blog post originally (beginning of January 2022) there was no out-of-the-box support by the ArgumentParser framework.

%[https://github.com/apple/swift-argument-parser/issues/326]

Lucky for us the issue contains a code snippet from [Sergio Campamá](https://github.com/sergiocampama) how to add support for async/await by ourself!

I am using this pattern and adopt the official Repeat example from Apple, share my lessons learned and which pitfalls to avoid.

First, in my program I define a new protocol to indicate that commands need to perform asynchronous work.

```swift
protocol AsyncParsableCommand: ParsableCommand {
    mutating func runAsync() async throws
}
```

Every command conforming to `AsyncParsableCommand` needs to implement the asynchronous function named `runAsync`.

Similar to what the ArgumentParser does to invoke the `run` function for an `ParsableCommand` I create an extension on ParsableCommand and define a static *async* main() function.

```swift
extension ParsableCommand {
    static func main() async {
        do {
            var command = try parseAsRoot(nil) /// `parseAsRoot` uses the program's command-line arguments when passing `nil`
            if var asyncCommand = command as? AsyncParsableCommand {
                try await asyncCommand.runAsync()
            } else {
                try command.run()
            }
        } catch {
            exit(withError: error)
        }
    }
}
```

In the implementation I reuse the public function `parseAsRoot` from the ArgumentParser framework. It will return the main command (here: the Repeat struct from the example).

If the command implements the `AsyncParsableCommand` protocol then let's await the `runAsync` function. Otherwise simply call the command's `run` function.

By the way: you do not see any conditional statements despite that Swift Concurrency is only available starting from specific platform versions. The reason for this is that I restricted the platform to macOS 10.15 and above in my Swift Package manifest.

```
import PackageDescription

let package = Package(
    name: "RepeatCommandLineTool",
    platforms: [.macOS(.v10_15)],
    products: [
        .executable(name: "repeat", targets: ["RepeatCommandLineTool"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
    ],
    targets: [
        .executableTarget(
            name: "RepeatCommandLineTool",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
            ]),
        .testTarget(
            name: "RepeatCommandLineToolTests",
            dependencies: ["RepeatCommandLineTool"]),
    ]
)
```

You have to rename your `main.swift` file if you haven't done that already. Otherwise you will run in the error

> 'main' attribute cannot be used in a module that contains top-level code

This has nothing to do with Swift Concurrency but because the `@main` attribute is used. Speaking of it ... I move the `@main` attribute, to indicate the top-level entry point for program, to a new enum which has an *async* static function which will await the newly *async* main function of the `ParsableCommand`. 

```
@main
enum CLI {
    static func main() async {
        await Repeat.main()
    }
}
```

Now I can finally adopt the example

```
struct Repeat: ParsableCommand, AsyncParsableCommand {
    @Flag(help: "Include a counter with each repetition.")
    var includeCounter = false

    @Option(name: .shortAndLong, help: "The number of times to repeat 'phrase'.")
    var count: Int?

    @Argument(help: "The phrase to repeat.")
    var phrase: String

    mutating func runAsync() async throws {
        let repeatCount = count ?? .max
        await asyncRepeat(phrase: phrase, repeatCount: repeatCount)
    }

    func asyncRepeat(phrase: String, repeatCount: Int) async {
        try? await Task.sleep(nanoseconds: 5 * 1_000_000_000) // dummy use of an aysnc operation .. wait 5 seconds :)
        for i in 1...repeatCount {
            if includeCounter {
                print("\(i): \(phrase)")
            } else {
                print(phrase)
            }
        }
    }
}
```

Finally I can await the `asyncRepeat` function which waits for 5 seconds before repeating the phrase passed as argument to the program :)

All the code shared is stored in the following GitHub repository.

%[https://github.com/MarcoEidinger/Repeat]

In addition to this blog post I created a YouTube Video

%[https://youtu.be/6Vue2Ariqcw]

## Bonus Tip

Do you struggle with the error `dyld: Library not loaded: @rpath/libswift_Concurrency.dylib` when running your command-line tool from within Xcode?

Here is my advice on how to avoid the error. I was able to successfully test this on my local machine running macOS 11 and Xcode 13.2.

Add to the `Package.swift` file a new import statement on the top.

```swift
import Foundation
```

At the end of the file add the following code:

```Swift
// IMPORTANT: enable the following function call if you encounter the error
//    `dyld: Library not loaded: @rpath/libswift_Concurrency.dylib`

//hookInternalSwiftConcurrency()

func hookInternalSwiftConcurrency() {
    let isFromTerminal = ProcessInfo.processInfo.environment.values.contains("/usr/bin/swift")
    if !isFromTerminal {
        package.targets.first?.addLinkerSettingUnsafeFlagRunpathSearchPath()
    }
}

extension PackageDescription.Target {
    func addLinkerSettingUnsafeFlagRunpathSearchPath() {
        linkerSettings = [linkerSetting]
    }

    private var linkerSetting: LinkerSetting {
        guard let xcodeFolder = Executable("/usr/bin/xcode-select")("-p") else {
            fatalError("Could not run `xcode-select -p`")
        }

        let toolchainFolder = "\(xcodeFolder.trimmed)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx"

        return .unsafeFlags(["-rpath", toolchainFolder])
    }
}

extension String {
    var trimmed: String { trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) }
}

private struct Executable {
    private let url: URL

    init(_ filePath: String) {
        url = URL(fileURLWithPath: filePath)
    }

    func callAsFunction(_ arguments: String...) -> String? {
        let process = Process()
        process.executableURL = url
        process.arguments = arguments

        let stdout = Pipe()
        process.standardOutput = stdout

        process.launch()
        process.waitUntilExit()

        return stdout.readStringToEndOfFile()
    }
}

extension Pipe {
    func readStringToEndOfFile() -> String? {
        let data: Data
        if #available(OSX 10.15.4, *) {
            data = (try? fileHandleForReading.readToEnd()) ?? Data()
        } else {
            data = fileHandleForReading.readDataToEndOfFile()
        }

        return String(data: data, encoding: .utf8)
    }
}
```

Uncomment line `// hookInternalSwiftConcurrency()` which should solve the problem. In case the command-line tool is executed not from the terminal (= Xcode) then additional linker settings get added to the target. In particular the [Runpath Search Path](https://blog.krzyzanowskim.com/2018/12/05/rpath-what/) is set so that the dynamic linker is able to find `libswift_Concurrency.dylib` in your installed toolchain.

