-
Notifications
You must be signed in to change notification settings - Fork 12k
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]Look up in top-level subcommand as a fallback when looking options for a custom subcommand. #71776
Conversation
options for a custom subcommand. 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 intoa special 'top level' subcommand. Motivating Use Case: The motivating use case is to refactor llvm-profdata to use cl::SubCommand to organize subcommands. See pr/71328. A valid use case that's not supported before this patch // 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.
@llvm/pr-subscribers-llvm-support Author: Mingming Liu (minglotus-6) ChangesContext:
Motivating Use Case:
Full diff: https://github.com/llvm/llvm-project/pull/71776.diff 2 Files Affected:
diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp
index 55633d7cafa4791..a7e0cae8b855d7c 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -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,
diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index 41cc8260acfedf7..76589c7deed88e1 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -525,6 +525,74 @@ TEST(CommandLineTest, LookupFailsInWrongSubCommand) {
EXPECT_FALSE(Errs.empty());
}
+TEST(CommandLineTest, SubcommandOptions) {
+ 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");
+
+ // The positional argument.
+ StackOption<std::string> PositionalOpt(
+ cl::Positional, cl::desc("positional argument test coverage"),
+ cl::sub(SC));
+ // Thel literal argument.
+ 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> BoolOpt("enable", cl::sub(SC), cl::init(false));
+
+ std::string Errs;
+ raw_string_ostream OS(Errs);
+
+ for (const char *literalArg : {"--bar", "--foo", "--baz"}) {
+ const char *args[] = {"prog", "sc", "input-file",
+ literalArg, "-enable", "--str=csv"};
+
+ // cl::ParseCommandLineOptions returns true on success. it returns false
+ // and prints errors to caller provided error stream (&OS in this setting).
+ EXPECT_TRUE(cl::ParseCommandLineOptions(6, args, StringRef(), &OS));
+
+ // Tests that the value of `str` option is `csv` as specified.
+ EXPECT_EQ(strcmp(TopLevelOpt.getValue().c_str(), "csv"), 0);
+
+ const char *parsedLiteralOpt;
+ switch (LiteralOpt) {
+ case baz:
+ parsedLiteralOpt = "baz";
+ break;
+ case bar:
+ parsedLiteralOpt = "bar";
+ break;
+ case foo:
+ parsedLiteralOpt = "foo";
+ break;
+ default:
+ llvm_unreachable("unknown option for LiteralOpt");
+ }
+
+ // Tests that literal options are parsed correctly. Skip '--' prefix of
+ // literalArg.
+ EXPECT_EQ(strcmp(parsedLiteralOpt, literalArg + 2), 0);
+
+ // Flush and tests there is no error message.
+ OS.flush();
+ EXPECT_TRUE(Errs.empty());
+ }
+}
+
TEST(CommandLineTest, AddToAllSubCommands) {
cl::ResetCommandLineParser();
|
std::string Errs; | ||
raw_string_ostream OS(Errs); | ||
|
||
for (const char *literalArg : {"--bar", "--foo", "--baz"}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you structure this loop to iterate over a list of {input, want} pairs then you can avoid the switch case below and the conversion for checking.
for (auto& test : {std::pair{"--bar", bar}, {"--foo", foo}, {"--baz", baz}) {
...
EXPECT_EQ(LiteralOpt, test.second);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, done. Also move the loop-invariant test case out of the loop as a part of this commit.
|
||
// cl::ParseCommandLineOptions returns true on success. it returns false | ||
// and prints errors to caller provided error stream (&OS in this setting). | ||
EXPECT_TRUE(cl::ParseCommandLineOptions(6, args, StringRef(), &OS)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: sizeof(args) instead of 6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
EXPECT_TRUE(cl::ParseCommandLineOptions(6, args, StringRef(), &OS)); | ||
|
||
// Tests that the value of `str` option is `csv` as specified. | ||
EXPECT_EQ(strcmp(TopLevelOpt.getValue().c_str(), "csv"), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EXPECT_STREQ instead of EXPECT_EQ to avoid the strcmp.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
|
||
// Flush and tests there is no error message. | ||
OS.flush(); | ||
EXPECT_TRUE(Errs.empty()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is unnecessary since we already check the return value of cl::ParseCommandLineOptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I removed Errs
and OS
in this test case.
|
||
// cl::ParseCommandLineOptions returns true on success. it returns false | ||
// and prints errors to caller provided error stream (&OS in this setting). | ||
EXPECT_TRUE(cl::ParseCommandLineOptions(6, args, StringRef(), &OS)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be ASSERT_TRUE since it does not make sense to continue if we failed to parse this arg. Though note that it will fail the entire test and the rest of the tests will not be executed (which imo is fine).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep ASSERT_TRUE
is better in this context. done.
…ith upper case letter; and use std::pair initializer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm though please wait a bit for feedback from @MaskRay
StackOption<bool> BoolOpt("enable", cl::sub(SC), cl::init(false)); | ||
|
||
const char *PositionalOptVal = "input-file"; | ||
const char *args[] = {"prog", "sc", PositionalOptVal, "-enable", "--str=csv"}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add another StackOption<bool>
similar to -enable
, and test that the option can be parsed after --str=csv
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds reasonable. Make a slight change by adding an option of StackOption<int>
.
@@ -525,6 +525,58 @@ TEST(CommandLineTest, LookupFailsInWrongSubCommand) { | |||
EXPECT_FALSE(Errs.empty()); | |||
} | |||
|
|||
TEST(CommandLineTest, SubcommandOptions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are other Subcommand tests. The name SubcommandOptions
can be changed to reflect that the test intends to check, e.g. TopLevelOptInSubcommand
cl::desc("A top-level option.")); | ||
|
||
StackSubCommand SC("sc", "Subcommand"); | ||
// The positional argument. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code self explains. I think the comments are unneeded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete two one-line comments.
…ooking options for a custom subcommand (#71975) …ooking options for a custom subcommand. (#71776)" This reverts commit b88308b. The build-bot is unhappy (https://lab.llvm.org/buildbot/#/builders/186/builds/13096), `GroupingAndPrefix` fails after `TopLevelOptInSubcommand` (the newly added test). Revert while I look into this (might be related with test sharding but not sure) ``` [----------] 3 tests from CommandLineTest [ RUN ] CommandLineTest.TokenizeWindowsCommandLine2 [ OK ] CommandLineTest.TokenizeWindowsCommandLine2 (0 ms) [ RUN ] CommandLineTest.TopLevelOptInSubcommand [ OK ] CommandLineTest.TopLevelOptInSubcommand (0 ms) [ RUN ] CommandLineTest.GroupingAndPrefix #0 0x00ba8118 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x594118) #1 0x00ba5914 llvm::sys::RunSignalHandlers() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x591914) #2 0x00ba89c4 SignalHandler(int) (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5949c4) #3 0xf7828530 __default_sa_restorer /build/glibc-9MGTF6/glibc-2.31/signal/../sysdeps/unix/sysv/linux/arm/sigrestorer.S:67:0 #4 0x00af91f0 (anonymous namespace)::CommandLineParser::ResetAllOptionOccurrences() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x4e51f0) #5 0x00af8e1c llvm::cl::ResetCommandLineParser() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x4e4e1c) #6 0x0077cda0 (anonymous namespace)::CommandLineTest_GroupingAndPrefix_Test::TestBody() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x168da0) #7 0x00bc5adc testing::Test::Run() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5b1adc) #8 0x00bc6cc0 testing::TestInfo::Run() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5b2cc0) #9 0x00bc7880 testing::TestSuite::Run() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5b3880) #10 0x00bd7974 testing::internal::UnitTestImpl::RunAllTests() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5c3974) #11 0x00bd6ebc testing::UnitTest::Run() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5c2ebc) #12 0x00bb1058 main (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x59d058) #13 0xf78185a4 __libc_start_main /build/glibc-9MGTF6/glibc-2.31/csu/libc-start.c:342:3 ```
…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.
…ooking options for a custom subcommand (llvm#71975) …ooking options for a custom subcommand. (llvm#71776)" This reverts commit b88308b. The build-bot is unhappy (https://lab.llvm.org/buildbot/#/builders/186/builds/13096), `GroupingAndPrefix` fails after `TopLevelOptInSubcommand` (the newly added test). Revert while I look into this (might be related with test sharding but not sure) ``` [----------] 3 tests from CommandLineTest [ RUN ] CommandLineTest.TokenizeWindowsCommandLine2 [ OK ] CommandLineTest.TokenizeWindowsCommandLine2 (0 ms) [ RUN ] CommandLineTest.TopLevelOptInSubcommand [ OK ] CommandLineTest.TopLevelOptInSubcommand (0 ms) [ RUN ] CommandLineTest.GroupingAndPrefix #0 0x00ba8118 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x594118) llvm#1 0x00ba5914 llvm::sys::RunSignalHandlers() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x591914) llvm#2 0x00ba89c4 SignalHandler(int) (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5949c4) llvm#3 0xf7828530 __default_sa_restorer /build/glibc-9MGTF6/glibc-2.31/signal/../sysdeps/unix/sysv/linux/arm/sigrestorer.S:67:0 llvm#4 0x00af91f0 (anonymous namespace)::CommandLineParser::ResetAllOptionOccurrences() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x4e51f0) llvm#5 0x00af8e1c llvm::cl::ResetCommandLineParser() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x4e4e1c) llvm#6 0x0077cda0 (anonymous namespace)::CommandLineTest_GroupingAndPrefix_Test::TestBody() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x168da0) llvm#7 0x00bc5adc testing::Test::Run() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5b1adc) llvm#8 0x00bc6cc0 testing::TestInfo::Run() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5b2cc0) llvm#9 0x00bc7880 testing::TestSuite::Run() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5b3880) llvm#10 0x00bd7974 testing::internal::UnitTestImpl::RunAllTests() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5c3974) llvm#11 0x00bd6ebc testing::UnitTest::Run() (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x5c2ebc) llvm#12 0x00bb1058 main (/home/tcwg-buildbot/worker/clang-armv7-global-isel/stage1/unittests/Support/./SupportTests+0x59d058) llvm#13 0xf78185a4 __libc_start_main /build/glibc-9MGTF6/glibc-2.31/csu/libc-start.c:342:3 ```
Context:
cl::SubCommand
is introduced.Motivating Use Case:
llvm-profdata
to usecl::SubCommand
to organize subcommands. See pr/71328. A valid use case that's not supported before this patch