Skip to content

Commit

Permalink
[Support]Look up in top-level subcommand as a fallback when looking o…
Browse files Browse the repository at this point in the history
…ptions for a custom subcommand. (llvm#71776)

**Context:**

- In https://lists.llvm.org/pipermail/llvm-dev/2016-June/101804.html and commit 07670b3, `cl::SubCommand` is introduced.
- Options that don't specify subcommand goes into a special 'top level' subcommand.

**Motivating Use Case:**
- The motivating use case is to refactor `llvm-profdata` to use `cl::SubCommand` to organize subcommands. See
llvm#71328. A valid use case that's not supported before this patch is shown below

```
  // show-option{1,2} are associated with 'show' subcommand.
  // top-level-option3 is in top-level subcomand (e.g., `profile-isfs` in SampleProfReader.cpp)
  llvm-profdata show --show-option1 --show-option2 --top-level-option3
```

- Before this patch, option handler look-up will fail with the following error message "Unknown command line argument --top-level-option3".
- After this patch, option handler look-up will look up in sub-command options first, and use top-level subcommand as a fallback, so 'top-level-option3' is parsed correctly.
  • Loading branch information
mingmingl-llvm authored Nov 10, 2023
1 parent a3bd87b commit b88308b
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 0 deletions.
7 changes: 7 additions & 0 deletions llvm/lib/Support/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,13 @@ bool CommandLineParser::ParseCommandLineOptions(int argc,
Handler = LookupLongOption(*ChosenSubCommand, ArgName, Value,
LongOptionsUseDoubleDash, HaveDoubleDash);

// If Handler is not found in a specialized subcommand, look up handler
// in the top-level subcommand.
// cl::opt without cl::sub belongs to top-level subcommand.
if (!Handler && ChosenSubCommand != &SubCommand::getTopLevel())
Handler = LookupLongOption(SubCommand::getTopLevel(), ArgName, Value,
LongOptionsUseDoubleDash, HaveDoubleDash);

// Check to see if this "option" is really a prefixed or grouped argument.
if (!Handler && !(LongOptionsUseDoubleDash && HaveDoubleDash))
Handler = HandlePrefixedOrGroupedOption(ArgName, Value, ErrorParsing,
Expand Down
53 changes: 53 additions & 0 deletions llvm/unittests/Support/CommandLineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,59 @@ TEST(CommandLineTest, LookupFailsInWrongSubCommand) {
EXPECT_FALSE(Errs.empty());
}

TEST(CommandLineTest, TopLevelOptInSubcommand) {
enum LiteralOptionEnum {
foo,
bar,
baz,
};

cl::ResetCommandLineParser();

// This is a top-level option and not associated with a subcommand.
// A command line using subcommand should parse both subcommand options and
// top-level options. A valid use case is that users of llvm command line
// tools should be able to specify top-level options defined in any library.
cl::opt<std::string> TopLevelOpt("str", cl::init("txt"),
cl::desc("A top-level option."));

StackSubCommand SC("sc", "Subcommand");
StackOption<std::string> PositionalOpt(
cl::Positional, cl::desc("positional argument test coverage"),
cl::sub(SC));
StackOption<LiteralOptionEnum> LiteralOpt(
cl::desc("literal argument test coverage"), cl::sub(SC), cl::init(bar),
cl::values(clEnumVal(foo, "foo"), clEnumVal(bar, "bar"),
clEnumVal(baz, "baz")));
StackOption<bool> EnableOpt("enable", cl::sub(SC), cl::init(false));
StackOption<int> ThresholdOpt("threshold", cl::sub(SC), cl::init(1));

const char *PositionalOptVal = "input-file";
const char *args[] = {"prog", "sc", PositionalOptVal,
"-enable", "--str=csv", "--threshold=2"};

// cl::ParseCommandLineOptions returns true on success. Otherwise, it will
// print the error message to stderr and exit in this setting (`Errs` ostream
// is not set).
ASSERT_TRUE(cl::ParseCommandLineOptions(sizeof(args) / sizeof(args[0]), args,
StringRef()));
EXPECT_STREQ(PositionalOpt.getValue().c_str(), PositionalOptVal);
EXPECT_TRUE(EnableOpt);
// Tests that the value of `str` option is `csv` as specified.
EXPECT_STREQ(TopLevelOpt.getValue().c_str(), "csv");
EXPECT_EQ(ThresholdOpt, 2);

for (auto &[LiteralOptVal, WantLiteralOpt] :
{std::pair{"--bar", bar}, {"--foo", foo}, {"--baz", baz}}) {
const char *args[] = {"prog", "sc", LiteralOptVal};
ASSERT_TRUE(cl::ParseCommandLineOptions(sizeof(args) / sizeof(args[0]),
args, StringRef()));

// Tests that literal options are parsed correctly.
EXPECT_EQ(LiteralOpt, WantLiteralOpt);
}
}

TEST(CommandLineTest, AddToAllSubCommands) {
cl::ResetCommandLineParser();

Expand Down

0 comments on commit b88308b

Please sign in to comment.