Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected “Asynchronous subcommand of a synchronous root” #536

Open
1 of 2 tasks
fboundp opened this issue Dec 20, 2022 · 7 comments
Open
1 of 2 tasks

Unexpected “Asynchronous subcommand of a synchronous root” #536

fboundp opened this issue Dec 20, 2022 · 7 comments
Labels
good first issue Good for newcomers

Comments

@fboundp
Copy link

fboundp commented Dec 20, 2022

When attempting to create an application with a AsyncParsableCommand root and AsyncParsableCommand subcommands, a fatal error is thrown at runtime “Asynchronous subcommand of a synchronous root.”

ArgumentParser version: 1.2.0
Swift version: swift-driver version: 1.62.8 Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50) Target: arm64-apple-macosx13.0

Checklist

  • If possible, I've reproduced the issue using the main branch of this package
  • I've searched for existing GitHub issues

Steps to Reproduce

source:

import protocol ArgumentParser.AsyncParsableCommand
import struct ArgumentParser.CommandConfiguration

@main
struct asyncissue: AsyncParsableCommand {
  static var configuration = CommandConfiguration(subcommands: [Cmd1.self])

  struct Cmd1: AsyncParsableCommand {
    mutating func run() async throws {
      print("Hello")
    }
  }
}

Expected behavior

When resulting command is run, help with a subcommand would be displayed.

Actual behavior

ArgumentParser/ParsableCommand.swift:184: Fatal error: 
--------------------------------------------------------------------
Asynchronous subcommand of a synchronous root.

The asynchronous command `Cmd1` is declared as a subcommand of the
synchronous root command `asyncissue`.

With this configuration, your asynchronous `run()` method will not be
called. To fix this issue, change `asyncissue`'s `ParsableCommand`
conformance to `AsyncParsableCommand`.
--------------------------------------------------------------------

zsh: trace trap  swift run
@fboundp
Copy link
Author

fboundp commented Dec 22, 2022

Adding @available(macOS 10.15, *) to the definition of struct asyncissue seems to fix this.

@natecook1000
Copy link
Member

@fboundp Thanks for the report and your research into this! I think the best resolution here is to add the workaround as a suggestion in the error message.

@natecook1000 natecook1000 added the good first issue Good for newcomers label Jan 5, 2023
@TiagoMaiaL
Copy link
Contributor

TiagoMaiaL commented Jan 15, 2023

I've tried reproducing this issue, but it seems to be working correctly here.

  • swift -v output:
Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50)
Target: arm64-apple-macosx13.0
  • Xcode version: 14.0.1
  • Code:
import ArgumentParser

@main
struct Root: AsyncParsableCommand {
    static var configuration = CommandConfiguration(subcommands: [Sub.self])
    
    struct Sub: AsyncParsableCommand {
        mutating func run() async throws {
            print("Hello World")
        }
    }
}

I've tried reproducing it from both main and 1.2.0.

@timwredwards
Copy link

I'm experiencing this issue using main, and with the annotation added.

Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
Target: arm64-apple-macosx13.0

@natecook1000
Copy link
Member

#547 should improve the error message when this happens, so that at least it's describing the problem accurately.

@timwredwards Can you provide some more detail on your project and what you're seeing?

@calebhailey
Copy link

calebhailey commented Aug 4, 2023

Not sure if I'm seeing this same thing or something related, but I just went through a migration from ParsableCommand to AsyncParsableCommand and had the following experience:

  • First I (incorrectly) changed a subcommand from ParsableCommand to AsyncParsableCommand and encountered the (helpful and correct) "Asynchronous subcommand of a synchronous root" error message

  • Next I changed my root command struct from ParsableCommand to AsyncParsableCommand and encountered the following error message:

    /ParsableCommand.:248: Fatal error: 
    --------------------------------------------------------------------
    Asynchronous root command needs availability annotation.
    
    The asynchronous root command `ExampleCLI` needs an availability
    annotation in order to be executed asynchronously. To fix this issue,
    add the following availability attribute to your `ExampleCLI`
    declaration or set the minimum platform in your "Package." file.
    
    @available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
    --------------------------------------------------------------------
    
    Trace/BPT trap: 5
    

Then it seemed that no matter what I did I couldn't resolve this error. I tried adding the annotation to my struct as follows:

@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
struct ExampleCLI: AsyncParsableCommand {}

and I tried setting platforms in Package. as follows:

let package: Package = Package(
  name: "example",
  platforms: [
      .macOS(.v13),
      .macCatalyst(.v13),
      .iOS(.v13),
      .tvOS(.v13),
      .watchOS(.v6),
  ]
}

...and still no dice.

Then I found this issue and noticed that some folks were having success with the @available annotation, however in the snippets that were shared here I observed that most people are setting the @main annotation on the root command (as recommended by the docs). However, I have been using a different struct as my entry point, like this:

// CLI Entrypoint
@main
struct CLI {

    static var stdin: String?
    static func main() {
        // check for STDIN before parsing arguments
        // usage: 
        //     if let stdin: String = CLI.stdin {
        //         print("stdin: \(stdin)")
        //     }
        self.stdin = standardInput()
        ExampleCLI.main()
    }
}

As soon as I set the @main annotation on my root command, along with the @available annotation as suggested by the error message, I was able to resolve the error. This seems like a bug, and also related to this issue, but I could also be doing something super obviously wrong.

I hope this helps! 😊

PS: I'm doing this whole root command wrapper struct as a dance around capturing standard input and stripping trailing - (hyphen) characters (e.g. echo "helloworld" | examplecli --dosomething -), which ArgumentParser seems to complain about. My standardInput() function looks like this:

// FeatherDB CLI STDIN Helper
func standardInput() -> String? {
    // Only read STDIN if the last argument is "-"
    guard CommandLine.arguments.last == "-" else {
        return nil
    }

    // Remove the trailing "-" argument to avoid tripping up ArgumentParser
    CommandLine.arguments.removeLast()

    // Read & return STDIN
    var input: String = String()
    while let line: String = readLine() {
        input += line
    }
    return input
}

@calebhailey
Copy link

@natecook1000 👋 thanks for your work on ArgumentParser! Coming from a background in developing golang CLI tools – which has a robust ecosystem of stdlib & OSS libraries for CLI argument parsing – I've found it to be quite impressive. It has almost everything I could ask for!

Any suggestions RE: the behavior I described above? Should I open a separate issue for what I'm seeing, or do you think the behavior I observed is related to this issue?

Thanks in advance for your time! 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

5 participants