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

Support an async entry point for commands #404

Merged
merged 11 commits into from Mar 14, 2022
Merged

Support an async entry point for commands #404

merged 11 commits into from Mar 14, 2022

Conversation

natecook1000
Copy link
Member

@natecook1000 natecook1000 commented Feb 11, 2022

Description

Adds an AsyncParsableCommand type that sports an run() async method, allowing for asynchronous code in a command line tool.

Detailed Design

This includes the new AsyncParsableCommand protocol, which provides a static func main() async entry point and can call through to the root command's or a subcommand's asynchronous run() method. For this asynchronous execution, the root command must conform to AsyncParsableCommand, but its subcommands can be a mix of asynchronous and synchronous commands.

/// A type that can be executed as part of a nested tree of commands.
public protocol AsyncParsableCommand: ParsableCommand {
  mutating func run() async throws
}

extension AsyncParsableCommand {
  public static func main() async { ... }
}

Due to an issue in Swift 5.5, you can only use @main on an AsyncParsableCommand root command starting in Swift 5.6. This PR also includes a workaround for clients that are using Swift 5.5. Declare a separate type that conforms to AsyncMainProtocol and add the @main attribute to that type. For example:

@main enum Main: AsyncMain {
    typealias Command = <#command#>
}

AsyncMainProtocol is deprecated for Swift 5.6.

Documentation Plan

There's a new example included (count-lines) that uses AsyncParsableCommand to read lines asynchronously, demonstrating the feature. A new guide for creating asynchronous commands is still TK.

Test Plan

As an entry point, this feature is tested by invoking the built executable via a new test in ArgumentParserExampleTests.

Source Impact

This change requires an upgrade in Swift versions to 5.5, but is otherwise source compatible.

Checklist

  • I've added at least one test that validates that my change is working, if appropriate
  • I've followed the code style of the rest of the project
  • I've read the Contribution Guidelines
  • I've updated the documentation if necessary

Adds a new `AsyncParsableCommand` protocol, which provides a
`static func main() async` entry point and can call through to the root
command's or a subcommand's asynchronous `run()` method. For this
asynchronous execution, the root command must conform to `AsyncParsableCommand`,
but its subcommands can be a mix of asynchronous and synchronous commands.

Due to an issue in Swift 5.5, you can only use `@main` on an
`AsyncParsableCommand` root command starting in Swift 5.6.
This change also includes a workaround for clients that are using Swift 5.5.
Declare a separate type that conforms to `AsyncMainProtocol` and add the `@main`
attribute to that type.

```
@main enum Main: AsyncMain {
    typealias Command = <#command#>
}
```
@natecook1000 natecook1000 linked an issue Feb 11, 2022 that may be closed by this pull request
@natecook1000 natecook1000 linked an issue Feb 11, 2022 that may be closed by this pull request
@natecook1000
Copy link
Member Author

@swift-ci Please test

@natecook1000
Copy link
Member Author

@swift-ci Please test

@@ -1,4 +1,4 @@
// swift-tools-version:5.2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this PR ends up changing the minimum supported Swift version from 5.2 to 5.5, that will be a breaking change for any consumers who support compiling with Swift < 5.5. Is the intention to ship this as part of a 2.0 release or is breaking Swift version support not considered a semver breaking change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, @jpsim! The plan is to ship this change as part of the 1.1.0 release. My understanding is that this isn't a source breaking change, since clients using older versions of Swift will just continue to get version 1.0.3. That said, I do have the wrong platform setting below — will update that. Thanks!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we don't wrap the new stuff in #if swift(>=5.5), and in turn keep support for older Swift versions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ffried Not to say that you shouldn't, what are your use cases where you need to support older swift versions like 5.2?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rauhul I don't have any myself. I was just wondering in case this is considered a breaking change.

Copy link
Contributor

@rauhul rauhul Feb 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usage of older Swift versions like 5.1/5.2 is higher than you might think (15% of this survey's respondents).

That might be a convincing enough reason to upgrade. IMO we (the community + apple package owners) should come up with some guidelines for version support. I think NIO announced they will support only the latest 2 swift versions. (Though I might be mis-remembering).

@Lukasa what is swift-nio's policy for swift version support and do you consider dropping support for a swift version a breaking change?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are proposing to support the latest Swift version and the two versions prior. We do not consider dropping Swift versions to be a breaking change, because as @natecook1000 says, SwiftPM takes the tools version into account when resolving what version of a package will be used. Users silently get the last version that supported their Swift version.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SwiftPM takes the tools version into account when resolving what version of a package will be used. Users silently get the last version that supported their Swift version

This is a big surprise to me since it means SwiftPM needs to check out multiple versions of a dependency in order to identify which on is the last to be compatible with the current swift version. Until today I thought SwiftPM just looked at the git tags to determine which version of a dependency to resolve.

If this is the case, then I have no objections to versioning this with 1.1.0.

@rauhul it'd be great if there were guidelines for Swift version support for Swift library authors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jpsim This seems like a good conversation to start on the swift forums, I'm 100% certain people have opinions... (for better or worse)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A fun irony here is that we discussed the tools version change, but I missed that this also added a platforms stanza, and while changing the tools version isn't breaking, changing the platforms stanza very much is.

@natecook1000
Copy link
Member Author

@swift-ci Please test

@natecook1000
Copy link
Member Author

@swift-ci Please test

@rauhul
Copy link
Contributor

rauhul commented Feb 16, 2022

@swift-ci Please test

@natecook1000
Copy link
Member Author

@swift-ci Please test macOS platform

@@ -14,6 +14,7 @@ import PackageDescription

var package = Package(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@natecook1000 can we take this opportunity to make the indentation 2 spaces?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sure. It should be 4 in code samples and example code and 2 everywhere else.

@rauhul
Copy link
Contributor

rauhul commented Feb 22, 2022

@swift-ci please test

@natecook1000
Copy link
Member Author

@swift-ci Please test macOS platform

1 similar comment
@natecook1000
Copy link
Member Author

@swift-ci Please test macOS platform

@Jerry-Carter
Copy link

Shifted our test platform to 5.6 for our async version. Wanted to let you know that we've been able to considerably simplify our code around swift-argument-parser. This is looking good, Nate.

@natecook1000
Copy link
Member Author

@swift-ci Please test

@Blackjacx
Copy link

I refactored my App Store Connect API tool with using the async branch and integrating async/await using the async branch of the argument parser was sooooo satisfying. Good job everyone 👍🏼 hope this makes it in the official release soon. It is soo soo great :)

For the interested of you here are some links: twitter.com/Blackjacxxx/status/1411730725393555456

@natecook1000
Copy link
Member Author

@swift-ci Please test

@natecook1000 natecook1000 merged commit 1141ed1 into main Mar 14, 2022
@natecook1000 natecook1000 deleted the async branch March 14, 2022 23:14
compnerd added a commit to compnerd/swift-argument-parser that referenced this pull request Mar 15, 2022
This fixes the build with CMake after apple#404.
@compnerd compnerd mentioned this pull request Mar 15, 2022
4 tasks
natecook1000 pushed a commit that referenced this pull request Mar 15, 2022
This fixes the build with CMake after #404.
leuski pushed a commit to leuski/swift-argument-parser that referenced this pull request Jun 17, 2022
* 'main' of github.com:apple/swift-argument-parser: (114 commits)
  Fix `AsyncParseableCommand` hierarchy (apple#436)
  Add experimental manual page generation (apple#332)
  Improving edit distance string extension (apple#446)
  List valid options in error messages for enum array argument (apple#445)
  Remove LinuxMain.swift (apple#367)
  Hide hidden subcommands from completions (apple#443)
  Update changelog for 1.1.2 release (apple#441)
  Fix error message for @option array without values (apple#435)
  Fix Repeat's endless printing (apple#437)
  build: statically link ArgumentParserToolInfo always (apple#424)
  Update changelog for the 1.1.1 release (apple#428)
  build: complete the changes from apple#423 (apple#425)
  Remove platform requirement from Package.swift (apple#427)
  build: repair the build after apple#404 (apple#423)
  Fix broken links/incorrect variance calculation (apple#422)
  Update changelog for the 1.1.0 release (apple#421)
  Update documentation (apple#420)
  Make `@OptionGroup(visibility:)` a public API (apple#419)
  Support an `async` entry point for commands (apple#404)
  Fix a typo and template links (apple#418)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for async/await where available
7 participants