Skip to content
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

Merged
merged 5 commits into from
Nov 10, 2023

Conversation

minglotus-6
Copy link
Contributor

@minglotus-6 minglotus-6 commented Nov 9, 2023

Context:

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.

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.
@llvmbot
Copy link
Collaborator

llvmbot commented Nov 9, 2023

@llvm/pr-subscribers-llvm-support

Author: Mingming Liu (minglotus-6)

Changes

Context:

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.

Full diff: https://github.com/llvm/llvm-project/pull/71776.diff

2 Files Affected:

  • (modified) llvm/lib/Support/CommandLine.cpp (+7)
  • (modified) llvm/unittests/Support/CommandLineTest.cpp (+68)
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();
 

llvm/unittests/Support/CommandLineTest.cpp Outdated Show resolved Hide resolved
std::string Errs;
raw_string_ostream OS(Errs);

for (const char *literalArg : {"--bar", "--foo", "--baz"}) {
Copy link
Contributor

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);
}

Copy link
Contributor Author

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));
Copy link
Contributor

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

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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());
Copy link
Contributor

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?

Copy link
Contributor Author

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));
Copy link
Contributor

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).

Copy link
Contributor Author

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.

Copy link
Contributor

@snehasish snehasish left a 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"};
Copy link
Member

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

Copy link
Contributor Author

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) {
Copy link
Member

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.
Copy link
Member

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.

Copy link
Contributor Author

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.

@minglotus-6 minglotus-6 merged commit b88308b into llvm:main Nov 10, 2023
2 of 3 checks passed
@minglotus-6 minglotus-6 deleted the commandline branch November 10, 2023 17:22
minglotus-6 added a commit that referenced this pull request Nov 10, 2023
…ooking options for a custom subcommand. (#71776)"

This reverts commit b88308b.
minglotus-6 added a commit that referenced this pull request Nov 10, 2023
…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
```
MaskRay added a commit that referenced this pull request Nov 10, 2023
…ooking options for a custom subcommand. (#71776)"

This reverts commit b88308b.
zahiraam pushed a commit to zahiraam/llvm-project that referenced this pull request Nov 20, 2023
…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.
zahiraam pushed a commit to zahiraam/llvm-project that referenced this pull request Nov 20, 2023
…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
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants