Skip to content

Conversation

silverweed
Copy link
Contributor

We now have enough C++ executables (with more to be ported) to justify sharing some code between them, in particular option parsing.

This PR introduces a simple option parser class that covers most of our cases. Not all cases (e.g. hadd keeps its custom parsing because it's a bit weird), but it's already enough to cover rootls and rootbrowse, and will in the future also cover rootcp, rootmv etc.

The parser is documented with code examples and properly tested and it's about 300 lines of codes (about an order of magnitude less than e.g. cxxopts which in my opinion is overkill for our use case).

Checklist:

  • tested changes locally
  • updated the docs (if necessary)

@silverweed silverweed self-assigned this Oct 9, 2025
@silverweed silverweed requested a review from jblomer October 9, 2025 12:50
Copy link

github-actions bot commented Oct 9, 2025

Test Results

    16 files      16 suites   2d 18h 50m 30s ⏱️
 3 663 tests  3 663 ✅ 0 💤 0 ❌
58 040 runs  58 040 ✅ 0 💤 0 ❌

Results for commit b91ae6a.

Comment on lines +15 to +16
/// // NOTE: `args` should not contain the program name! It should usually be `argc + 1`.
/// opts.Parse(args, nArgs);
Copy link
Member

@pcanal pcanal Oct 9, 2025

Choose a reason for hiding this comment

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

Suggested change
/// // NOTE: `args` should not contain the program name! It should usually be `argc + 1`.
/// opts.Parse(args, nArgs);
/// // NOTE: `args` should not contain the program name! It should usually be `argc + 1`.
/// // For example `main(char **argv, int argc)` might call this function as:
/// // `ParseArgs(const_cast<const_cast<const char**>(argv) + 1, argc - 1);`
/// opts.Parse(args, nArgs);

(Without this, I was side tracked for a while pondering whether the comment wanted the last line to be opts.Parse(args, nArgs+1) )

ROOT::RCmdLineOpts opts;
opts.AddFlag({"--foo"}, ROOT::RCmdLineOpts::EFlagType::kWithArg);

const char *args[] = {"--foo", "bar"};
Copy link
Member

Choose a reason for hiding this comment

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

Do we test something like:

 const char *args[] = {"somename", "--foo", "bar", "-abc"};

and

 const char *args[] = {"somename", "--noargopt", "bar"};
 const char *args[] = {"--noargopt", "bar"};

i.e. location of positional arguments. (In particular the first one might appear by mistake if argv is passed instead of argv+1)

Copy link
Member

@pcanal pcanal left a comment

Choose a reason for hiding this comment

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

LGTM (Beside the comments and portability issue)

@ferdymercury
Copy link
Collaborator

Thanks a lot for the initiative!

Even if it's 300 lines of code, isn't it better to rely on something external that gets auto-maintained, even if it's bigger in number of lines of code?
Such as the header-only https://github.com/CLIUtils/CLI11

Or to understand better the motivation, what do we gain by having a small parser for which we have to add our own tests?

  • Is it because then compile time is smaller?
  • Or is initialization time more performant?
  • Or is the overkill of CLI11 or cxxopt increasing the size of the produced binaries or sth like that?
  • Or to not rely on external sources?

@silverweed
Copy link
Contributor Author

Thanks a lot for the initiative!

Even if it's 300 lines of code, isn't it better to rely on something external that gets auto-maintained, even if it's bigger in number of lines of code? Such as the header-only https://github.com/CLIUtils/CLI11

Or to understand better the motivation, what do we gain by having a small parser for which we have to add our own tests?

* Is it because then compile time is smaller?

* Or is initialization time more performant?

* Or is the overkill of CLI11 or cxxopt increasing the size of the produced binaries or sth like that?

* Or to not rely on external sources?

It's mostly the last point (though the first is also somewhat important).
Simply put, if the functionality is not particularly complex (as is this case) it's always better to have your own code that you know what it does and can easily control than an external dependency that you need to maintain and complicates your build system.
Furthermore, if such dependency is 10x the code size (without providing at least 10x the benefits) then you would just be carrying around much more weight than you really need.
External dependencies are not free and should only be used where there is a really good argument for them, which in this case I don't see.

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.

3 participants