Skip to content

Commit b88308b

Browse files
[Support]Look up in top-level subcommand as a fallback when looking options for a custom subcommand. (#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 #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.
1 parent a3bd87b commit b88308b

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

llvm/lib/Support/CommandLine.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -1667,6 +1667,13 @@ bool CommandLineParser::ParseCommandLineOptions(int argc,
16671667
Handler = LookupLongOption(*ChosenSubCommand, ArgName, Value,
16681668
LongOptionsUseDoubleDash, HaveDoubleDash);
16691669

1670+
// If Handler is not found in a specialized subcommand, look up handler
1671+
// in the top-level subcommand.
1672+
// cl::opt without cl::sub belongs to top-level subcommand.
1673+
if (!Handler && ChosenSubCommand != &SubCommand::getTopLevel())
1674+
Handler = LookupLongOption(SubCommand::getTopLevel(), ArgName, Value,
1675+
LongOptionsUseDoubleDash, HaveDoubleDash);
1676+
16701677
// Check to see if this "option" is really a prefixed or grouped argument.
16711678
if (!Handler && !(LongOptionsUseDoubleDash && HaveDoubleDash))
16721679
Handler = HandlePrefixedOrGroupedOption(ArgName, Value, ErrorParsing,

llvm/unittests/Support/CommandLineTest.cpp

+53
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,59 @@ TEST(CommandLineTest, LookupFailsInWrongSubCommand) {
525525
EXPECT_FALSE(Errs.empty());
526526
}
527527

528+
TEST(CommandLineTest, TopLevelOptInSubcommand) {
529+
enum LiteralOptionEnum {
530+
foo,
531+
bar,
532+
baz,
533+
};
534+
535+
cl::ResetCommandLineParser();
536+
537+
// This is a top-level option and not associated with a subcommand.
538+
// A command line using subcommand should parse both subcommand options and
539+
// top-level options. A valid use case is that users of llvm command line
540+
// tools should be able to specify top-level options defined in any library.
541+
cl::opt<std::string> TopLevelOpt("str", cl::init("txt"),
542+
cl::desc("A top-level option."));
543+
544+
StackSubCommand SC("sc", "Subcommand");
545+
StackOption<std::string> PositionalOpt(
546+
cl::Positional, cl::desc("positional argument test coverage"),
547+
cl::sub(SC));
548+
StackOption<LiteralOptionEnum> LiteralOpt(
549+
cl::desc("literal argument test coverage"), cl::sub(SC), cl::init(bar),
550+
cl::values(clEnumVal(foo, "foo"), clEnumVal(bar, "bar"),
551+
clEnumVal(baz, "baz")));
552+
StackOption<bool> EnableOpt("enable", cl::sub(SC), cl::init(false));
553+
StackOption<int> ThresholdOpt("threshold", cl::sub(SC), cl::init(1));
554+
555+
const char *PositionalOptVal = "input-file";
556+
const char *args[] = {"prog", "sc", PositionalOptVal,
557+
"-enable", "--str=csv", "--threshold=2"};
558+
559+
// cl::ParseCommandLineOptions returns true on success. Otherwise, it will
560+
// print the error message to stderr and exit in this setting (`Errs` ostream
561+
// is not set).
562+
ASSERT_TRUE(cl::ParseCommandLineOptions(sizeof(args) / sizeof(args[0]), args,
563+
StringRef()));
564+
EXPECT_STREQ(PositionalOpt.getValue().c_str(), PositionalOptVal);
565+
EXPECT_TRUE(EnableOpt);
566+
// Tests that the value of `str` option is `csv` as specified.
567+
EXPECT_STREQ(TopLevelOpt.getValue().c_str(), "csv");
568+
EXPECT_EQ(ThresholdOpt, 2);
569+
570+
for (auto &[LiteralOptVal, WantLiteralOpt] :
571+
{std::pair{"--bar", bar}, {"--foo", foo}, {"--baz", baz}}) {
572+
const char *args[] = {"prog", "sc", LiteralOptVal};
573+
ASSERT_TRUE(cl::ParseCommandLineOptions(sizeof(args) / sizeof(args[0]),
574+
args, StringRef()));
575+
576+
// Tests that literal options are parsed correctly.
577+
EXPECT_EQ(LiteralOpt, WantLiteralOpt);
578+
}
579+
}
580+
528581
TEST(CommandLineTest, AddToAllSubCommands) {
529582
cl::ResetCommandLineParser();
530583

0 commit comments

Comments
 (0)