I'm trying to tinker with creating async command line tools. I've created a command line tool using ArgumentParser, and am following Apple's documentation on creating an AsyncParsableCommand. It says to define your run() function like this:
mutating func run() async throws
However, when I add async to the definition of the function and try to run the command (either from the shell using swift run AsyncTest or from Xcode) I get the help for the command as if I have provided an invalid option or flag. (If I don't include async in the function definition it does execute the run() function, but it exits before my async operations can complete.)
The docs on the run() function say "This method has a default implementation that prints the help screen for this command." That suggests to me that when I add async to the function definition, it changes the function signature such that the compiler or runtime can't find it and so it executes the default implementation.
What am I doing wrong? Here is a link to my project on Github.
Also here is the entire contents of my file AsyncTest.swift in case you want to read the code rather than downloading the project and trying it for yourself:
import ArgumentParser
import Foundation
@main
struct AsyncTest: AsyncParsableCommand {
@Option(name: .shortAndLong, help: "The number of times to snork.")
var count: Int? = nil
func doAsyncInLoop(count: Int) async {
if #available(macOS 10.15, *) {
print("In macOS ≥10.15")
Task {
for index in 1...count {
let result = try await doAsyncTask(index: index)
print(result)
}
}
} else {
print("In macOS <10.15. Task() not available.")
}
}
@discardableResult func doAsyncTask(index: Int) async throws -> String {
print("In \(#function).")
//Wait 10-500 milliseconds (.01 to .5 seconds)
let millisecond = NSEC_PER_SEC / 1000
let duration = millisecond * UInt64.random(in: 10...500)
if #available(macOS 10.15, *) {
try await Task.sleep(nanoseconds: duration)
} else {
// Fallback on earlier versions
}
return "Async job \(index) complete"
}
mutating func run() async throws {
print("In \(#function). count == \(count ?? 0)")
if let count, count > 0 {
await doAsyncInLoop(count: count)
}
}
}
Ok, I have a solution. I don't fully understand it, but you have to add an
@available(macOS 10.15)on the AsyncParsableCommand or it fails to execute the async version of the run() command. It still compiles, but at runtime it executes the default version ofrun(), which just prints the help text for the command.Adding an
@available(macOS 10.15)lets me get rid of the runtimeif #available(macOS 10.15, *)checks around my async code.And updated version of the code from the question looks like this: