ArgumentParser subcommand autodiscovery!

Hi everyone, currently over on the ArgumentParser repo, we have a PR to implement "subcommand autodiscovery"! With this feature, you no longer need to manually define subcommands in an array for the configuration property. The way it works is simple: define a ParsableCommand type and any nested ParsableCommand types are treated as subcommands. For example, we can create a command similar to the swift command like this:

// The top level entry point into the Swift driver.
//
// swift
@main
struct Swift: ParsableCommand {
  // ...
}

extension Swift {
  // Builds a Swift package
  //
  // swift build
  struct Build: ParsableCommand {
    // ...
  }
}

extension Swift {
  // Top level entry point for various package subcommands
  //
  // swift package
  struct Package: ParsableCommand {
    // ...
  }
}

extension Swift.Package {
  // Create a Swift package!
  //
  // swift package init
  struct Init: ParsableCommand {
    // ...
  }
}

where Swift is a top level command and has 2 subcommands Build and Package. Swift.Pacakge is a subcommand who itself also has a subcommand Init.

The subcommands property on CommandConfiguration is now marked optional to indicate that we're requesting autodiscovery of our subcommands. Current projects using argument parser will still work as intended, but now may optionally remove their usage of the subcommands array. Of course, one can still explicitly define their own subcommands with the current method or simply turn off autodiscovery via an empty array for this property.

We believe that this helps continue ArgumentParser's goal of removing boilerplate by using the way you declare your types to help define the command-line interface. Please let us know what you think!

21 Likes

This is excellent! I'm thrilled to see the work being done to build Swift APIs to get at reflection metadata and having compelling use cases like this.

Is that _Runtime module likely to be moved into its own package soon instead of being embedded in the swift-argument-parser source tree?

4 Likes

That was part of the thinking in making it a separate module. However, this is still a focused experiment. There's still a lot of work to be done, especially performance evaluation, before general reflection APIs in a separate package would be ready for a wider audience.

5 Likes

Great news and very good use of reflection.

1 Like

I'm going to be a bit of a downer and say I think this is a bad idea: this is introducing overhead into every launch of an executable in order to make the code a little simpler. It's clever to be able to do reflection like this, but this is really the sort of thing I'd want resolved at compile-time. (I'm in favor of having those compile-time solutions too!)

11 Likes

If the overhead you're referring to is the protocol conformance cache, that can be moved to be done on first request when asked during runtime instead of on launch. We're not the Swift runtime and don't need this information on launch.

“First request” is on launch for an argument parser, though. And it wasn’t even exactly that that concerned me; walking all types to see which ones are nested already seems like an unnecessary expense.

I know this sounds like overoptimization, but I remember the cost of a do-nothing Clang job being measurable in build system performance. Maybe I’m misremembering how much constant overhead is attributable to argument parsing though. There are also cases where people do clever things with argument parsers, like autocompleting based on -help output, where extremely fast turnaround is important.

9 Likes

My only concern is about bloating the library size more, do you have an idea how much larger this makes libArgumentParser (release+stripped) binary?

1 Like

I'm with @jrose: I think this pessimises application launch time for minimal gain.

I think the calculus is a little more subtle, because my impression is that you only pay for subcommand auto-discovery if you use it.

Yes it will be on by default, but performance sensitive applications can opt out by explicitly providing the subcommands. Even for performance sensitive applications, the feature accelerates the developer experience—helping folks get something up and running faster.

Is it sufficiently slow that it's harmful to provide the feature even if it can be turned off? To answer that, I think we need to gather some data of the impact of this feature on launch time and library size.

4 Likes

I don't think that's true: I think you pay for it if you use it or if you don't have subcommands, no? Specifically, unless it occurs to you to say "I have no subcommands", the framework will go looking for your subcommands. That strikes me as the wrong default.

That’s fair, providing an explicit (possibly empty) subcommands is a pretty clear opt out, and one that lets the common case work.

2 Likes

I'm concerned about compile time. ArgumentParser is used by lots of projects, and it can already be fairly slow to compile on lower-end devices like a Raspberry Pi. I hope that, if measurements are made to consider the cost to startup time, that these sorts of devices are also considered (I'd be happy to do some benchmarks, if needed).

This feature is very cool, but ultimately I think the benefit is quite small, and it requires quite a lot of code to get it. Personally I don't think it's worth it. But it is cool.

1 Like
Terms of Service

Privacy Policy

Cookie Policy