apple / swift-argument-parser

Straightforward, type-safe argument parsing for Swift
Apache License 2.0
3.3k stars 311 forks source link

failAsyncPlatform called under valid conditions #605

Closed StarLard closed 8 months ago

StarLard commented 8 months ago

failAsyncPlatform is always called for AsyncParsableCommand with AsyncParsableCommand subcommands running on platforms macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, or above with package requirements at or above the aforementioned versions. This is due to a faulty platform check which asserts that the desired platforms are available and then throws an error when they are available.

ArgumentParser version: main branch. Swift version:

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

Checklist

Steps to Reproduce

  1. Create an AsyncParsableCommand root command with at least one AsyncParsableCommand subcommand.
  2. Specify a minimum platform version at or above macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, or watchOS 6.
  3. Attempt to run in a debug configuration

Expected behavior

Command executes successfully

Actual behavior

failAsyncPlatform is called and execution stops due to a fatal error.

StarLard commented 8 months ago

In my package I have declared a minimum platform of macOS 13 like so:

platforms: [
        .macOS(.v13)
    ],

and have declared a command as follows:

main.swift

struct MyCommand: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "My Command",
        abstract: "An async command.",
        subcommands: [
            MySubCommand.self
        ],
        defaultSubcommand: MySubCommand.self
    )
}

MyCommand.main()

MySubCommand.swift

struct MySubCommand: AsyncParsableCommand {
    func run() async throws {
        print("Hello World")
    }
}

When running in debug, I then trigger the aforementioned error while running on macOS 13.5.2.

StarLard commented 8 months ago

It seems that what this logic is intended to do is check if the specified platforms are unavailable, but I'm not sure why such a check would be needed as AsyncParsableCommand is itself annotated with @available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *) and attempting to conform to it on a platform where it isn't available should throw a compiler error.

natecook1000 commented 8 months ago

The goal of that code is to diagnose when someone has placed an async command in a hierarchy with a non-async command at its root. In that case, the async version of the command will never get its run() implementation called.

I think you're running into this in your example because you're calling main() explicitly, and the non-async version is being selected. You should be able to resolve this in a couple ways:

StarLard commented 8 months ago

Thanks! Would be great to clarify this in the error message.