[llvm] r274171 - Resubmit "Update llvm command line parser to support subcommands."
Zachary Turner via llvm-commits
llvm-commits at lists.llvm.org
Mon Oct 10 09:07:48 PDT 2016
If you can post the lines of code that define your option set, maybe I can
see if anything stands out as obvious.
On Mon, Oct 10, 2016 at 9:03 AM Alex L <arphaman at gmail.com> wrote:
> On 10 October 2016 at 16:56, Zachary Turner <zturner at google.com> wrote:
>
> So here, `Sub` refers to the active SubCommand. For example, if you typed
> "llvm-pdbdump raw <options>" then `Sub` will refer to the RawSubcommand.
> There are two special subcommands, `TopLevelSubcommand` and
> `AllSubcommands`. The former becomes the active subcommand when you type
> no options at all. For example, if you were to run `llvm-pdbdump -foo"
> Then the active subcommand would be `TopLevelSubcommand`. Since
> subcommands cannot be nested, `TopLevelSubcommand` is the only subcommand
> from which it is possible for other subcommands to be "children" of. So
> the first part of the conditional is saying "Only print this if the user
> did not explicitly specify any subcommand". The >2 comes from the fact
> that `TopLevelSubcommand` and `AllSubcommands` are builtin subcommands.
> Even if someone does not define any subcommands for their tool, there will
> still be those two. So the second check is equivalent to saying "If the
> user has explicitly defined at least 1 subcommand".
>
>
> Thanks for the explanation, that makes sense. I stumbled across this when
> I was investigating why the help didn't print out subcommands when I had
> less than 3 sub-commands in a private tool. Given this explanation it has
> to be some mistake in my code or my reasoning. I'll double-check my code.
>
> Alex
>
>
>
> On Mon, Oct 10, 2016 at 3:44 AM Alex L <arphaman at gmail.com> wrote:
>
> On 29 June 2016 at 22:48, Zachary Turner via llvm-commits <
> llvm-commits at lists.llvm.org> wrote:
>
> Author: zturner
> Date: Wed Jun 29 16:48:26 2016
> New Revision: 274171
>
> URL: http://llvm.org/viewvc/llvm-project?rev=274171&view=rev
> Log:
> Resubmit "Update llvm command line parser to support subcommands."
>
> This fixes an issue where occurrence counts would be unexpectedly
> reset when parsing different parts of a command line multiple
> times.
>
> **ORIGINAL COMMIT MESSAGE**
>
> This allows command line tools to use syntaxes like the following:
>
> llvm-foo.exe command1 -o1 -o2
> llvm-foo.exe command2 -p1 -p2
>
> Where command1 and command2 contain completely different sets of
> valid options. This is backwards compatible with previous uses
> of llvm cl which did not support subcommands, as any option
> which specifies no optional subcommand (e.g. all existing
> code) goes into a special "top level" subcommand that expects
> dashed options to appear immediately after the program name.
> For example, code which is subcommand unaware would generate
> a command line such as the following, where no subcommand
> is specified:
>
> llvm-foo.exe -q1 -q2
>
> The top level subcommand can co-exist with actual subcommands,
> as it is implemented as an actual subcommand which is searched
> if no explicit subcommand is specified. So llvm-foo.exe as
> specified above could be written so as to support all three
> aforementioned command lines simultaneously.
>
> There is one additional "special" subcommand called AllSubCommands,
> which can be used to inject an option into every subcommand.
> This is useful to support things like help, so that commands
> such as:
>
> llvm-foo.exe --help
> llvm-foo.exe command1 --help
> llvm-foo.exe command2 --help
>
> All work and display the help for the selected subcommand
> without having to explicitly go and write code to handle each
> one separately.
>
> This patch is submitted without an example of anything actually
> using subcommands, but a followup patch will convert the
> llvm-pdbdump tool to use subcommands.
>
> Reviewed By: beanz
>
> Modified:
> llvm/trunk/include/llvm/Support/CommandLine.h
> llvm/trunk/lib/Support/CommandLine.cpp
> llvm/trunk/unittests/Support/CommandLineTest.cpp
> llvm/trunk/unittests/Support/ProgramTest.cpp
>
> Modified: llvm/trunk/include/llvm/Support/CommandLine.h
> URL:
> http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Support/CommandLine.h?rev=274171&r1=274170&r2=274171&view=diff
>
> ==============================================================================
> --- llvm/trunk/include/llvm/Support/CommandLine.h (original)
> +++ llvm/trunk/include/llvm/Support/CommandLine.h Wed Jun 29 16:48:26 2016
> @@ -21,10 +21,12 @@
> #define LLVM_SUPPORT_COMMANDLINE_H
>
> #include "llvm/ADT/ArrayRef.h"
> +#include "llvm/ADT/SmallPtrSet.h"
> #include "llvm/ADT/SmallVector.h"
> #include "llvm/ADT/StringMap.h"
> #include "llvm/ADT/Twine.h"
> #include "llvm/Support/Compiler.h"
> +#include "llvm/Support/ManagedStatic.h"
> #include <cassert>
> #include <climits>
> #include <cstdarg>
> @@ -43,8 +45,9 @@ namespace cl {
>
> //===----------------------------------------------------------------------===//
> // ParseCommandLineOptions - Command line option processing entry point.
> //
> -void ParseCommandLineOptions(int argc, const char *const *argv,
> - const char *Overview = nullptr);
> +bool ParseCommandLineOptions(int argc, const char *const *argv,
> + const char *Overview = nullptr,
> + bool IgnoreErrors = false);
>
>
> //===----------------------------------------------------------------------===//
> // ParseEnvironmentOptions - Environment variable option processing
> alternate
> @@ -171,6 +174,45 @@ public:
> extern OptionCategory GeneralCategory;
>
>
> //===----------------------------------------------------------------------===//
> +// SubCommand class
> +//
> +class SubCommand {
> +private:
> + const char *const Name = nullptr;
> + const char *const Description = nullptr;
> +
> +protected:
> + void registerSubCommand();
> + void unregisterSubCommand();
> +
> +public:
> + SubCommand(const char *const Name, const char *const Description =
> nullptr)
> + : Name(Name), Description(Description) {
> + registerSubCommand();
> + }
> + SubCommand() {}
> +
> + void reset();
> +
> + operator bool() const;
> +
> + const char *getName() const { return Name; }
> + const char *getDescription() const { return Description; }
> +
> + SmallVector<Option *, 4> PositionalOpts;
> + SmallVector<Option *, 4> SinkOpts;
> + StringMap<Option *> OptionsMap;
> +
> + Option *ConsumeAfterOpt = nullptr; // The ConsumeAfter option if it
> exists.
> +};
> +
> +// A special subcommand representing no subcommand
> +extern ManagedStatic<SubCommand> TopLevelSubCommand;
> +
> +// A special subcommand that can be used to put an option into all
> subcommands.
> +extern ManagedStatic<SubCommand> AllSubCommands;
> +
>
> +//===----------------------------------------------------------------------===//
> // Option Base class
> //
> class alias;
> @@ -209,6 +251,7 @@ public:
> StringRef HelpStr; // The descriptive text message for -help
> StringRef ValueStr; // String describing what the value of this option
> is
> OptionCategory *Category; // The Category this option belongs to
> + SmallPtrSet<SubCommand *, 4> Subs; // The subcommands this option
> belongs to.
> bool FullyInitialized; // Has addArguemnt been called?
>
> inline enum NumOccurrencesFlag getNumOccurrencesFlag() const {
> @@ -229,6 +272,16 @@ public:
>
> // hasArgStr - Return true if the argstr != ""
> bool hasArgStr() const { return !ArgStr.empty(); }
> + bool isPositional() const { return getFormattingFlag() ==
> cl::Positional; }
> + bool isSink() const { return getMiscFlags() & cl::Sink; }
> + bool isConsumeAfter() const {
> + return getNumOccurrencesFlag() == cl::ConsumeAfter;
> + }
> + bool isInAllSubCommands() const {
> + return std::any_of(Subs.begin(), Subs.end(), [](const SubCommand *SC)
> {
> + return SC == &*AllSubCommands;
> + });
> + }
>
>
> //-------------------------------------------------------------------------===
> // Accessor functions set by OptionModifiers
> @@ -243,6 +296,7 @@ public:
> void setMiscFlag(enum MiscFlags M) { Misc |= M; }
> void setPosition(unsigned pos) { Position = pos; }
> void setCategory(OptionCategory &C) { Category = &C; }
> + void addSubCommand(SubCommand &S) { Subs.insert(&S); }
>
> protected:
> explicit Option(enum NumOccurrencesFlag OccurrencesFlag,
> @@ -287,6 +341,7 @@ public:
>
> public:
> inline int getNumOccurrences() const { return NumOccurrences; }
> + inline void reset() { NumOccurrences = 0; }
> virtual ~Option() {}
> };
>
> @@ -349,6 +404,14 @@ struct cat {
> template <class Opt> void apply(Opt &O) const {
> O.setCategory(Category); }
> };
>
> +// sub - Specify the subcommand that this option belongs to.
> +struct sub {
> + SubCommand ⋐
> + sub(SubCommand &S) : Sub(S) {}
> +
> + template <class Opt> void apply(Opt &O) const { O.addSubCommand(Sub); }
> +};
> +
>
> //===----------------------------------------------------------------------===//
> // OptionValue class
>
> @@ -1589,6 +1652,7 @@ class alias : public Option {
> error("cl::alias must have argument name specified!");
> if (!AliasFor)
> error("cl::alias must have an cl::aliasopt(option) specified!");
> + Subs = AliasFor->Subs;
> addArgument();
> }
>
> @@ -1669,7 +1733,7 @@ void PrintHelpMessage(bool Hidden = fals
> /// Hopefully this API can be depricated soon. Any situation where
> options need
> /// to be modified by tools or libraries should be handled by sane APIs
> rather
> /// than just handing around a global list.
> -StringMap<Option *> &getRegisteredOptions();
> +StringMap<Option *> &getRegisteredOptions(SubCommand &Sub);
>
>
> //===----------------------------------------------------------------------===//
> // Standalone command line processing utilities.
> @@ -1737,7 +1801,8 @@ bool ExpandResponseFiles(StringSaver &Sa
> /// Some tools (like clang-format) like to be able to hide all options
> that are
> /// not specific to the tool. This function allows a tool to specify a
> single
> /// option category to display in the -help output.
> -void HideUnrelatedOptions(cl::OptionCategory &Category);
> +void HideUnrelatedOptions(cl::OptionCategory &Category,
> + SubCommand &Sub = *TopLevelSubCommand);
>
> /// \brief Mark all options not part of the categories as
> cl::ReallyHidden.
> ///
> @@ -1746,7 +1811,19 @@ void HideUnrelatedOptions(cl::OptionCate
> /// Some tools (like clang-format) like to be able to hide all options
> that are
> /// not specific to the tool. This function allows a tool to specify a
> single
> /// option category to display in the -help output.
> -void HideUnrelatedOptions(ArrayRef<const cl::OptionCategory *>
> Categories);
> +void HideUnrelatedOptions(ArrayRef<const cl::OptionCategory *> Categories,
> + SubCommand &Sub = *TopLevelSubCommand);
> +
> +/// \brief Reset all command line options to a state that looks as if
> they have
> +/// never appeared on the command line. This is useful for being able to
> parse
> +/// a command line multiple times (especially useful for writing tests).
> +void ResetAllOptionOccurrences();
> +
> +/// \brief Reset the command line parser back to its initial state. This
> +/// removes
> +/// all options, categories, and subcommands and returns the parser to a
> state
> +/// where no options are supported.
> +void ResetCommandLineParser();
>
> } // End namespace cl
>
>
> Modified: llvm/trunk/lib/Support/CommandLine.cpp
> URL:
> http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Support/CommandLine.cpp?rev=274171&r1=274170&r2=274171&view=diff
>
> ==============================================================================
> --- llvm/trunk/lib/Support/CommandLine.cpp (original)
> +++ llvm/trunk/lib/Support/CommandLine.cpp Wed Jun 29 16:48:26 2016
> @@ -19,6 +19,7 @@
> #include "llvm/Support/CommandLine.h"
> #include "llvm-c/Support.h"
> #include "llvm/ADT/ArrayRef.h"
> +#include "llvm/ADT/DenseMap.h"
> #include "llvm/ADT/STLExtras.h"
> #include "llvm/ADT/SmallPtrSet.h"
> #include "llvm/ADT/SmallString.h"
> @@ -94,35 +95,56 @@ public:
> // This collects additional help to be printed.
> std::vector<const char *> MoreHelp;
>
> - SmallVector<Option *, 4> PositionalOpts;
> - SmallVector<Option *, 4> SinkOpts;
> - StringMap<Option *> OptionsMap;
> -
> - Option *ConsumeAfterOpt; // The ConsumeAfter option if it exists.
> -
> // This collects the different option categories that have been
> registered.
> SmallPtrSet<OptionCategory *, 16> RegisteredOptionCategories;
>
> - CommandLineParser() : ProgramOverview(nullptr),
> ConsumeAfterOpt(nullptr) {}
> + // This collects the different subcommands that have been registered.
> + SmallPtrSet<SubCommand *, 4> RegisteredSubCommands;
>
> - void ParseCommandLineOptions(int argc, const char *const *argv,
> - const char *Overview);
> + CommandLineParser() : ProgramOverview(nullptr),
> ActiveSubCommand(nullptr) {
> + registerSubCommand(&*TopLevelSubCommand);
> + registerSubCommand(&*AllSubCommands);
> + }
>
> - void addLiteralOption(Option &Opt, const char *Name) {
> - if (!Opt.hasArgStr()) {
> - if (!OptionsMap.insert(std::make_pair(Name, &Opt)).second) {
> - errs() << ProgramName << ": CommandLine Error: Option '" << Name
> - << "' registered more than once!\n";
> - report_fatal_error("inconsistency in registered CommandLine
> options");
> + void ResetAllOptionOccurrences();
> +
> + bool ParseCommandLineOptions(int argc, const char *const *argv,
> + const char *Overview, bool IgnoreErrors);
> +
> + void addLiteralOption(Option &Opt, SubCommand *SC, const char *Name) {
> + if (Opt.hasArgStr())
> + return;
> + if (!SC->OptionsMap.insert(std::make_pair(Name, &Opt)).second) {
> + errs() << ProgramName << ": CommandLine Error: Option '" << Name
> + << "' registered more than once!\n";
> + report_fatal_error("inconsistency in registered CommandLine
> options");
> + }
> +
> + // If we're adding this to all sub-commands, add it to the ones that
> have
> + // already been registered.
> + if (SC == &*AllSubCommands) {
> + for (const auto &Sub : RegisteredSubCommands) {
> + if (SC == Sub)
> + continue;
> + addLiteralOption(Opt, Sub, Name);
> }
> }
> }
>
> - void addOption(Option *O) {
> + void addLiteralOption(Option &Opt, const char *Name) {
> + if (Opt.Subs.empty())
> + addLiteralOption(Opt, &*TopLevelSubCommand, Name);
> + else {
> + for (auto SC : Opt.Subs)
> + addLiteralOption(Opt, SC, Name);
> + }
> + }
> +
> + void addOption(Option *O, SubCommand *SC) {
> bool HadErrors = false;
> if (O->hasArgStr()) {
> // Add argument to the argument map!
> - if (!OptionsMap.insert(std::make_pair(O->ArgStr, O)).second) {
> + if (!SC->OptionsMap.insert(std::make_pair(O->ArgStr, O)).second) {
> errs() << ProgramName << ": CommandLine Error: Option '" <<
> O->ArgStr
> << "' registered more than once!\n";
> HadErrors = true;
> @@ -131,15 +153,15 @@ public:
>
> // Remember information about positional options.
> if (O->getFormattingFlag() == cl::Positional)
> - PositionalOpts.push_back(O);
> + SC->PositionalOpts.push_back(O);
> else if (O->getMiscFlags() & cl::Sink) // Remember sink options
> - SinkOpts.push_back(O);
> + SC->SinkOpts.push_back(O);
> else if (O->getNumOccurrencesFlag() == cl::ConsumeAfter) {
> - if (ConsumeAfterOpt) {
> + if (SC->ConsumeAfterOpt) {
> O->error("Cannot specify more than one option with
> cl::ConsumeAfter!");
> HadErrors = true;
> }
> - ConsumeAfterOpt = O;
> + SC->ConsumeAfterOpt = O;
> }
>
> // Fail hard if there were errors. These are strictly unrecoverable
> and
> @@ -148,47 +170,102 @@ public:
> // linked LLVM distribution.
> if (HadErrors)
> report_fatal_error("inconsistency in registered CommandLine
> options");
> +
> + // If we're adding this to all sub-commands, add it to the ones that
> have
> + // already been registered.
> + if (SC == &*AllSubCommands) {
> + for (const auto &Sub : RegisteredSubCommands) {
> + if (SC == Sub)
> + continue;
> + addOption(O, Sub);
> + }
> + }
> }
>
> - void removeOption(Option *O) {
> + void addOption(Option *O) {
> + if (O->Subs.empty()) {
> + addOption(O, &*TopLevelSubCommand);
> + } else {
> + for (auto SC : O->Subs)
> + addOption(O, SC);
> + }
> + }
> +
> + void removeOption(Option *O, SubCommand *SC) {
> SmallVector<StringRef, 16> OptionNames;
> O->getExtraOptionNames(OptionNames);
> if (O->hasArgStr())
> OptionNames.push_back(O->ArgStr);
> +
> + SubCommand &Sub = *SC;
> for (auto Name : OptionNames)
> - OptionsMap.erase(Name);
> + Sub.OptionsMap.erase(Name);
>
> if (O->getFormattingFlag() == cl::Positional)
> - for (auto Opt = PositionalOpts.begin(); Opt != PositionalOpts.end();
> - ++Opt) {
> + for (auto Opt = Sub.PositionalOpts.begin();
> + Opt != Sub.PositionalOpts.end(); ++Opt) {
> if (*Opt == O) {
> - PositionalOpts.erase(Opt);
> + Sub.PositionalOpts.erase(Opt);
> break;
> }
> }
> else if (O->getMiscFlags() & cl::Sink)
> - for (auto Opt = SinkOpts.begin(); Opt != SinkOpts.end(); ++Opt) {
> + for (auto Opt = Sub.SinkOpts.begin(); Opt != Sub.SinkOpts.end();
> ++Opt) {
> if (*Opt == O) {
> - SinkOpts.erase(Opt);
> + Sub.SinkOpts.erase(Opt);
> break;
> }
> }
> - else if (O == ConsumeAfterOpt)
> - ConsumeAfterOpt = nullptr;
> + else if (O == Sub.ConsumeAfterOpt)
> + Sub.ConsumeAfterOpt = nullptr;
> }
>
> - bool hasOptions() {
> - return (!OptionsMap.empty() || !PositionalOpts.empty() ||
> - nullptr != ConsumeAfterOpt);
> + void removeOption(Option *O) {
> + if (O->Subs.empty())
> + removeOption(O, &*TopLevelSubCommand);
> + else {
> + if (O->isInAllSubCommands()) {
> + for (auto SC : RegisteredSubCommands)
> + removeOption(O, SC);
> + } else {
> + for (auto SC : O->Subs)
> + removeOption(O, SC);
> + }
> + }
> }
>
> - void updateArgStr(Option *O, StringRef NewName) {
> - if (!OptionsMap.insert(std::make_pair(NewName, O)).second) {
> + bool hasOptions(const SubCommand &Sub) const {
> + return (!Sub.OptionsMap.empty() || !Sub.PositionalOpts.empty() ||
> + nullptr != Sub.ConsumeAfterOpt);
> + }
> +
> + bool hasOptions() const {
> + for (const auto &S : RegisteredSubCommands) {
> + if (hasOptions(*S))
> + return true;
> + }
> + return false;
> + }
> +
> + SubCommand *getActiveSubCommand() { return ActiveSubCommand; }
> +
> + void updateArgStr(Option *O, StringRef NewName, SubCommand *SC) {
> + SubCommand &Sub = *SC;
> + if (!Sub.OptionsMap.insert(std::make_pair(NewName, O)).second) {
> errs() << ProgramName << ": CommandLine Error: Option '" <<
> O->ArgStr
> << "' registered more than once!\n";
> report_fatal_error("inconsistency in registered CommandLine
> options");
> }
> - OptionsMap.erase(O->ArgStr);
> + Sub.OptionsMap.erase(O->ArgStr);
> + }
> +
> + void updateArgStr(Option *O, StringRef NewName) {
> + if (O->Subs.empty())
> + updateArgStr(O, NewName, &*TopLevelSubCommand);
> + else {
> + for (auto SC : O->Subs)
> + updateArgStr(O, NewName, SC);
> + }
> }
>
> void printOptionValues();
> @@ -203,8 +280,55 @@ public:
> RegisteredOptionCategories.insert(cat);
> }
>
> + void registerSubCommand(SubCommand *sub) {
> + assert(count_if(RegisteredSubCommands,
> + [sub](const SubCommand *Sub) {
> + return (sub->getName() != nullptr) &&
> + (Sub->getName() == sub->getName());
> + }) == 0 &&
> + "Duplicate subcommands");
> + RegisteredSubCommands.insert(sub);
> +
> + // For all options that have been registered for all subcommands, add
> the
> + // option to this subcommand now.
> + if (sub != &*AllSubCommands) {
> + for (auto &E : AllSubCommands->OptionsMap) {
> + Option *O = E.second;
> + if ((O->isPositional() || O->isSink() || O->isConsumeAfter()) ||
> + O->hasArgStr())
> + addOption(O, sub);
> + else
> + addLiteralOption(*O, sub, E.first().str().c_str());
> + }
> + }
> + }
> +
> + void unregisterSubCommand(SubCommand *sub) {
> + RegisteredSubCommands.erase(sub);
> + }
> +
> + void reset() {
> + ActiveSubCommand = nullptr;
> + ProgramName.clear();
> + ProgramOverview = nullptr;
> +
> + MoreHelp.clear();
> + RegisteredOptionCategories.clear();
> +
> + ResetAllOptionOccurrences();
> + RegisteredSubCommands.clear();
> +
> + TopLevelSubCommand->reset();
> + AllSubCommands->reset();
> + registerSubCommand(&*TopLevelSubCommand);
> + registerSubCommand(&*AllSubCommands);
> + }
> +
> private:
> - Option *LookupOption(StringRef &Arg, StringRef &Value);
> + SubCommand *ActiveSubCommand;
> +
> + Option *LookupOption(SubCommand &Sub, StringRef &Arg, StringRef &Value);
> + SubCommand *LookupSubCommand(const char *Name);
> };
>
> } // namespace
> @@ -239,6 +363,32 @@ void OptionCategory::registerCategory()
> GlobalParser->registerCategory(this);
> }
>
> +// A special subcommand representing no subcommand
> +ManagedStatic<SubCommand> llvm::cl::TopLevelSubCommand;
> +
> +// A special subcommand that can be used to put an option into all
> subcommands.
> +ManagedStatic<SubCommand> llvm::cl::AllSubCommands;
> +
> +void SubCommand::registerSubCommand() {
> + GlobalParser->registerSubCommand(this);
> +}
> +
> +void SubCommand::unregisterSubCommand() {
> + GlobalParser->unregisterSubCommand(this);
> +}
> +
> +void SubCommand::reset() {
> + PositionalOpts.clear();
> + SinkOpts.clear();
> + OptionsMap.clear();
> +
> + ConsumeAfterOpt = nullptr;
> +}
> +
> +SubCommand::operator bool() const {
> + return (GlobalParser->getActiveSubCommand() == this);
> +}
> +
>
> //===----------------------------------------------------------------------===//
> // Basic, shared command line option processing machinery.
> //
> @@ -246,25 +396,29 @@ void OptionCategory::registerCategory()
> /// LookupOption - Lookup the option specified by the specified option on
> the
> /// command line. If there is a value specified (after an equal sign)
> return
> /// that as well. This assumes that leading dashes have already been
> stripped.
> -Option *CommandLineParser::LookupOption(StringRef &Arg, StringRef &Value)
> {
> +Option *CommandLineParser::LookupOption(SubCommand &Sub, StringRef &Arg,
> + StringRef &Value) {
> // Reject all dashes.
> if (Arg.empty())
> return nullptr;
> + assert(&Sub != &*AllSubCommands);
>
> size_t EqualPos = Arg.find('=');
>
> // If we have an equals sign, remember the value.
> if (EqualPos == StringRef::npos) {
> // Look up the option.
> - StringMap<Option *>::const_iterator I = OptionsMap.find(Arg);
> - return I != OptionsMap.end() ? I->second : nullptr;
> + auto I = Sub.OptionsMap.find(Arg);
> + if (I == Sub.OptionsMap.end())
> + return nullptr;
> +
> + return I != Sub.OptionsMap.end() ? I->second : nullptr;
> }
>
> // If the argument before the = is a valid option name, we match. If
> not,
> // return Arg unmolested.
> - StringMap<Option *>::const_iterator I =
> - OptionsMap.find(Arg.substr(0, EqualPos));
> - if (I == OptionsMap.end())
> + auto I = Sub.OptionsMap.find(Arg.substr(0, EqualPos));
> + if (I == Sub.OptionsMap.end())
> return nullptr;
>
> Value = Arg.substr(EqualPos + 1);
> @@ -272,6 +426,21 @@ Option *CommandLineParser::LookupOption(
> return I->second;
> }
>
> +SubCommand *CommandLineParser::LookupSubCommand(const char *Name) {
> + if (Name == nullptr)
> + return &*TopLevelSubCommand;
> + for (auto S : RegisteredSubCommands) {
> + if (S == &*AllSubCommands)
> + continue;
> + if (S->getName() == nullptr)
> + continue;
> +
> + if (StringRef(S->getName()) == StringRef(Name))
> + return S;
> + }
> + return &*TopLevelSubCommand;
> +}
> +
> /// LookupNearestOption - Lookup the closest match to the option
> specified by
> /// the specified option on the command line. If there is a value
> specified
> /// (after an equal sign) return that as well. This assumes that leading
> dashes
> @@ -820,14 +989,25 @@ void cl::ParseEnvironmentOptions(const c
> ParseCommandLineOptions(newArgc, &newArgv[0], Overview);
> }
>
> -void cl::ParseCommandLineOptions(int argc, const char *const *argv,
> - const char *Overview) {
> - GlobalParser->ParseCommandLineOptions(argc, argv, Overview);
> +bool cl::ParseCommandLineOptions(int argc, const char *const *argv,
> + const char *Overview, bool IgnoreErrors)
> {
> + return GlobalParser->ParseCommandLineOptions(argc, argv, Overview,
> + IgnoreErrors);
> }
>
> -void CommandLineParser::ParseCommandLineOptions(int argc,
> +void CommandLineParser::ResetAllOptionOccurrences() {
> + // So that we can parse different command lines multiple times in
> succession
> + // we reset all option values to look like they have never been seen
> before.
> + for (auto SC : RegisteredSubCommands) {
> + for (auto &O : SC->OptionsMap)
> + O.second->reset();
> + }
> +}
> +
> +bool CommandLineParser::ParseCommandLineOptions(int argc,
> const char *const *argv,
> - const char *Overview) {
> + const char *Overview,
> + bool IgnoreErrors) {
> assert(hasOptions() && "No options specified!");
>
> // Expand response files.
> @@ -850,6 +1030,23 @@ void CommandLineParser::ParseCommandLine
> // Determine whether or not there are an unlimited number of positionals
> bool HasUnlimitedPositionals = false;
>
> + int FirstArg = 1;
> + SubCommand *ChosenSubCommand = &*TopLevelSubCommand;
> + if (argc >= 2 && argv[FirstArg][0] != '-') {
> + // If the first argument specifies a valid subcommand, start
> processing
> + // options from the second argument.
> + ChosenSubCommand = LookupSubCommand(argv[FirstArg]);
> + if (ChosenSubCommand != &*TopLevelSubCommand)
> + FirstArg = 2;
> + }
> + GlobalParser->ActiveSubCommand = ChosenSubCommand;
> +
> + assert(ChosenSubCommand);
> + auto &ConsumeAfterOpt = ChosenSubCommand->ConsumeAfterOpt;
> + auto &PositionalOpts = ChosenSubCommand->PositionalOpts;
> + auto &SinkOpts = ChosenSubCommand->SinkOpts;
> + auto &OptionsMap = ChosenSubCommand->OptionsMap;
> +
> if (ConsumeAfterOpt) {
> assert(PositionalOpts.size() > 0 &&
> "Cannot specify cl::ConsumeAfter without a positional
> argument!");
> @@ -865,23 +1062,28 @@ void CommandLineParser::ParseCommandLine
> else if (ConsumeAfterOpt) {
> // ConsumeAfter cannot be combined with "optional" positional
> options
> // unless there is only one positional argument...
> - if (PositionalOpts.size() > 1)
> - ErrorParsing |= Opt->error(
> - "error - this positional option will never be matched, "
> - "because it does not Require a value, and a "
> - "cl::ConsumeAfter option is active!");
> + if (PositionalOpts.size() > 1) {
> + if (!IgnoreErrors)
> + Opt->error("error - this positional option will never be
> matched, "
> + "because it does not Require a value, and a "
> + "cl::ConsumeAfter option is active!");
> + ErrorParsing = true;
> + }
> } else if (UnboundedFound && !Opt->hasArgStr()) {
> // This option does not "require" a value... Make sure this
> option is
> // not specified after an option that eats all extra arguments,
> or this
> // one will never get any!
> //
> - ErrorParsing |= Opt->error("error - option can never match,
> because "
> - "another positional argument will
> match an "
> - "unbounded number of values, and this
> option"
> - " does not require a value!");
> - errs() << ProgramName << ": CommandLine Error: Option '" <<
> Opt->ArgStr
> - << "' is all messed up!\n";
> - errs() << PositionalOpts.size();
> + if (!IgnoreErrors) {
> + Opt->error("error - option can never match, because "
> + "another positional argument will match an "
> + "unbounded number of values, and this option"
> + " does not require a value!");
> + errs() << ProgramName << ": CommandLine Error: Option '"
> + << Opt->ArgStr << "' is all messed up!\n";
> + errs() << PositionalOpts.size();
> + }
> + ErrorParsing = true;
> }
> UnboundedFound |= EatsUnboundedNumberOfValues(Opt);
> }
> @@ -900,7 +1102,7 @@ void CommandLineParser::ParseCommandLine
>
> // Loop over all of the arguments... processing them.
> bool DashDashFound = false; // Have we read '--'?
> - for (int i = 1; i < argc; ++i) {
> + for (int i = FirstArg; i < argc; ++i) {
> Option *Handler = nullptr;
> Option *NearestHandler = nullptr;
> std::string NearestHandlerString;
> @@ -947,7 +1149,7 @@ void CommandLineParser::ParseCommandLine
> while (!ArgName.empty() && ArgName[0] == '-')
> ArgName = ArgName.substr(1);
>
> - Handler = LookupOption(ArgName, Value);
> + Handler = LookupOption(*ChosenSubCommand, ArgName, Value);
> if (!Handler || Handler->getFormattingFlag() != cl::Positional) {
> ProvidePositionalOption(ActivePositionalArg, argv[i], i);
> continue; // We are done!
> @@ -959,7 +1161,7 @@ void CommandLineParser::ParseCommandLine
> while (!ArgName.empty() && ArgName[0] == '-')
> ArgName = ArgName.substr(1);
>
> - Handler = LookupOption(ArgName, Value);
> + Handler = LookupOption(*ChosenSubCommand, ArgName, Value);
>
> // Check to see if this "option" is really a prefixed or grouped
> argument.
> if (!Handler)
> @@ -975,13 +1177,15 @@ void CommandLineParser::ParseCommandLine
>
> if (!Handler) {
> if (SinkOpts.empty()) {
> - errs() << ProgramName << ": Unknown command line argument '" <<
> argv[i]
> - << "'. Try: '" << argv[0] << " -help'\n";
> -
> - if (NearestHandler) {
> - // If we know a near match, report it as well.
> - errs() << ProgramName << ": Did you mean '-" <<
> NearestHandlerString
> - << "'?\n";
> + if (!IgnoreErrors) {
> + errs() << ProgramName << ": Unknown command line argument '"
> + << argv[i] << "'. Try: '" << argv[0] << " -help'\n";
> +
> + if (NearestHandler) {
> + // If we know a near match, report it as well.
> + errs() << ProgramName << ": Did you mean '-" <<
> NearestHandlerString
> + << "'?\n";
> + }
> }
>
> ErrorParsing = true;
> @@ -1004,17 +1208,21 @@ void CommandLineParser::ParseCommandLine
>
> // Check and handle positional arguments now...
> if (NumPositionalRequired > PositionalVals.size()) {
> - errs() << ProgramName
> - << ": Not enough positional command line arguments
> specified!\n"
> - << "Must specify at least " << NumPositionalRequired
> - << " positional arguments: See: " << argv[0] << " -help\n";
> + if (!IgnoreErrors) {
> + errs() << ProgramName
> + << ": Not enough positional command line arguments
> specified!\n"
> + << "Must specify at least " << NumPositionalRequired
> + << " positional arguments: See: " << argv[0] << " -help\n";
> + }
>
> ErrorParsing = true;
> } else if (!HasUnlimitedPositionals &&
> PositionalVals.size() > PositionalOpts.size()) {
> - errs() << ProgramName << ": Too many positional arguments
> specified!\n"
> - << "Can specify at most " << PositionalOpts.size()
> - << " positional arguments: See: " << argv[0] << " -help\n";
> + if (!IgnoreErrors) {
> + errs() << ProgramName << ": Too many positional arguments
> specified!\n"
> + << "Can specify at most " << PositionalOpts.size()
> + << " positional arguments: See: " << argv[0] << " -help\n";
> + }
> ErrorParsing = true;
>
> } else if (!ConsumeAfterOpt) {
> @@ -1109,8 +1317,12 @@ void CommandLineParser::ParseCommandLine
> MoreHelp.clear();
>
> // If we had an error processing our arguments, don't let the program
> execute
> - if (ErrorParsing)
> - exit(1);
> + if (ErrorParsing) {
> + if (!IgnoreErrors)
> + exit(1);
> + return false;
> + }
> + return true;
> }
>
>
> //===----------------------------------------------------------------------===//
> @@ -1460,6 +1672,11 @@ static int OptNameCompare(const std::pai
> return strcmp(LHS->first, RHS->first);
> }
>
> +static int SubNameCompare(const std::pair<const char *, SubCommand *>
> *LHS,
> + const std::pair<const char *, SubCommand *>
> *RHS) {
> + return strcmp(LHS->first, RHS->first);
> +}
> +
> // Copy Options into a vector so we can sort them as we like.
> static void sortOpts(StringMap<Option *> &OptMap,
> SmallVectorImpl<std::pair<const char *, Option *>>
> &Opts,
> @@ -1488,6 +1705,17 @@ static void sortOpts(StringMap<Option *>
> array_pod_sort(Opts.begin(), Opts.end(), OptNameCompare);
> }
>
> +static void
> +sortSubCommands(const SmallPtrSetImpl<SubCommand *> &SubMap,
> + SmallVectorImpl<std::pair<const char *, SubCommand *>>
> &Subs) {
> + for (const auto &S : SubMap) {
> + if (S->getName() == nullptr)
> + continue;
> + Subs.push_back(std::make_pair(S->getName(), S));
> + }
> + array_pod_sort(Subs.begin(), Subs.end(), SubNameCompare);
> +}
> +
> namespace {
>
> class HelpPrinter {
> @@ -1495,12 +1723,25 @@ protected:
> const bool ShowHidden;
> typedef SmallVector<std::pair<const char *, Option *>, 128>
> StrOptionPairVector;
> + typedef SmallVector<std::pair<const char *, SubCommand *>, 128>
> + StrSubCommandPairVector;
> // Print the options. Opts is assumed to be alphabetically sorted.
> virtual void printOptions(StrOptionPairVector &Opts, size_t MaxArgLen) {
> for (size_t i = 0, e = Opts.size(); i != e; ++i)
> Opts[i].second->printOptionInfo(MaxArgLen);
> }
>
> + void printSubCommands(StrSubCommandPairVector &Subs, size_t MaxSubLen) {
> + for (const auto &S : Subs) {
> + outs() << " " << S.first;
> + if (S.second->getDescription()) {
> + outs().indent(MaxSubLen - strlen(S.first));
> + outs() << " - " << S.second->getDescription();
> + }
> + outs() << "\n";
> + }
> + }
> +
> public:
> explicit HelpPrinter(bool showHidden) : ShowHidden(showHidden) {}
> virtual ~HelpPrinter() {}
> @@ -1510,23 +1751,56 @@ public:
> if (!Value)
> return;
>
> + SubCommand *Sub = GlobalParser->getActiveSubCommand();
> + auto &OptionsMap = Sub->OptionsMap;
> + auto &PositionalOpts = Sub->PositionalOpts;
> + auto &ConsumeAfterOpt = Sub->ConsumeAfterOpt;
> +
> StrOptionPairVector Opts;
> - sortOpts(GlobalParser->OptionsMap, Opts, ShowHidden);
> + sortOpts(OptionsMap, Opts, ShowHidden);
> +
> + StrSubCommandPairVector Subs;
> + sortSubCommands(GlobalParser->RegisteredSubCommands, Subs);
>
> if (GlobalParser->ProgramOverview)
> outs() << "OVERVIEW: " << GlobalParser->ProgramOverview << "\n";
>
> - outs() << "USAGE: " << GlobalParser->ProgramName << " [options]";
> + if (Sub == &*TopLevelSubCommand)
> + outs() << "USAGE: " << GlobalParser->ProgramName
> + << " [subcommand] [options]";
> + else {
> + if (Sub->getDescription() != nullptr) {
> + outs() << "SUBCOMMAND '" << Sub->getName()
> + << "': " << Sub->getDescription() << "\n\n";
> + }
> + outs() << "USAGE: " << GlobalParser->ProgramName << " " <<
> Sub->getName()
> + << " [options]";
> + }
>
> - for (auto Opt : GlobalParser->PositionalOpts) {
> + for (auto Opt : PositionalOpts) {
> if (Opt->hasArgStr())
> outs() << " --" << Opt->ArgStr;
> outs() << " " << Opt->HelpStr;
> }
>
> // Print the consume after option info if it exists...
> - if (GlobalParser->ConsumeAfterOpt)
> - outs() << " " << GlobalParser->ConsumeAfterOpt->HelpStr;
> + if (ConsumeAfterOpt)
> + outs() << " " << ConsumeAfterOpt->HelpStr;
> +
> + if (Sub == &*TopLevelSubCommand && Subs.size() > 2) {
>
>
> I'm sorry if I missed something, but I looked at the review for this
> commit, and couldn't find an answer to one question that I have, so I hope
> that you can help me:
>
> Is the > 2 intentional here? I think because of this check subcommands
> don't show up in help unless there are 3 of them. It seems like a bug to
> me, since it's valid to have a tool with just 2 subcommands.
>
> Thanks,
> Alex
>
> + // Compute the maximum subcommand length...
> + size_t MaxSubLen = 0;
> + for (size_t i = 0, e = Subs.size(); i != e; ++i)
> + MaxSubLen = std::max(MaxSubLen, strlen(Subs[i].first));
> +
> + outs() << "\n\n";
> + outs() << "SUBCOMMANDS:\n\n";
> + printSubCommands(Subs, MaxSubLen);
> + outs() << "\n";
> + outs() << " Type \"" << GlobalParser->ProgramName
> + << " <subcommand> -help\" to get more help on a specific "
> + "subcommand";
> + }
>
> outs() << "\n\n";
>
> @@ -1675,12 +1949,13 @@ static cl::opt<HelpPrinter, true, parser
> "help-list",
> cl::desc("Display list of available options (-help-list-hidden for
> more)"),
> cl::location(UncategorizedNormalPrinter), cl::Hidden,
> cl::ValueDisallowed,
> - cl::cat(GenericCategory));
> + cl::cat(GenericCategory), cl::sub(*AllSubCommands));
>
> static cl::opt<HelpPrinter, true, parser<bool>>
> HLHOp("help-list-hidden", cl::desc("Display list of all available
> options"),
> cl::location(UncategorizedHiddenPrinter), cl::Hidden,
> - cl::ValueDisallowed, cl::cat(GenericCategory));
> + cl::ValueDisallowed, cl::cat(GenericCategory),
> + cl::sub(*AllSubCommands));
>
> // Define uncategorized/categorized help printers. These printers change
> their
> // behaviour at runtime depending on whether one or more Option
> categories have
> @@ -1688,22 +1963,23 @@ static cl::opt<HelpPrinter, true, parser
> static cl::opt<HelpPrinterWrapper, true, parser<bool>>
> HOp("help", cl::desc("Display available options (-help-hidden for
> more)"),
> cl::location(WrappedNormalPrinter), cl::ValueDisallowed,
> - cl::cat(GenericCategory));
> + cl::cat(GenericCategory), cl::sub(*AllSubCommands));
>
> static cl::opt<HelpPrinterWrapper, true, parser<bool>>
> HHOp("help-hidden", cl::desc("Display all available options"),
> cl::location(WrappedHiddenPrinter), cl::Hidden,
> cl::ValueDisallowed,
> - cl::cat(GenericCategory));
> + cl::cat(GenericCategory), cl::sub(*AllSubCommands));
>
> static cl::opt<bool> PrintOptions(
> "print-options",
> cl::desc("Print non-default options after command line parsing"),
> - cl::Hidden, cl::init(false), cl::cat(GenericCategory));
> + cl::Hidden, cl::init(false), cl::cat(GenericCategory),
> + cl::sub(*AllSubCommands));
>
> static cl::opt<bool> PrintAllOptions(
> "print-all-options",
> cl::desc("Print all option values after command line parsing"),
> cl::Hidden,
> - cl::init(false), cl::cat(GenericCategory));
> + cl::init(false), cl::cat(GenericCategory), cl::sub(*AllSubCommands));
>
> void HelpPrinterWrapper::operator=(bool Value) {
> if (!Value)
> @@ -1730,7 +2006,7 @@ void CommandLineParser::printOptionValue
> return;
>
> SmallVector<std::pair<const char *, Option *>, 128> Opts;
> - sortOpts(OptionsMap, Opts, /*ShowHidden*/ true);
> + sortOpts(ActiveSubCommand->OptionsMap, Opts, /*ShowHidden*/ true);
>
> // Compute the maximum argument length...
> size_t MaxArgLen = 0;
> @@ -1839,22 +2115,26 @@ void cl::AddExtraVersionPrinter(void (*f
> ExtraVersionPrinters->push_back(func);
> }
>
> -StringMap<Option *> &cl::getRegisteredOptions() {
> - return GlobalParser->OptionsMap;
> +StringMap<Option *> &cl::getRegisteredOptions(SubCommand &Sub) {
> + auto &Subs = GlobalParser->RegisteredSubCommands;
> + (void)Subs;
> + assert(std::find(Subs.begin(), Subs.end(), &Sub) != Subs.end());
> + return Sub.OptionsMap;
> }
>
> -void cl::HideUnrelatedOptions(cl::OptionCategory &Category) {
> - for (auto &I : GlobalParser->OptionsMap) {
> +void cl::HideUnrelatedOptions(cl::OptionCategory &Category, SubCommand
> &Sub) {
> + for (auto &I : Sub.OptionsMap) {
> if (I.second->Category != &Category &&
> I.second->Category != &GenericCategory)
> I.second->setHiddenFlag(cl::ReallyHidden);
> }
> }
>
> -void cl::HideUnrelatedOptions(ArrayRef<const cl::OptionCategory *>
> Categories) {
> +void cl::HideUnrelatedOptions(ArrayRef<const cl::OptionCategory *>
> Categories,
> + SubCommand &Sub) {
> auto CategoriesBegin = Categories.begin();
> auto CategoriesEnd = Categories.end();
> - for (auto &I : GlobalParser->OptionsMap) {
> + for (auto &I : Sub.OptionsMap) {
> if (std::find(CategoriesBegin, CategoriesEnd, I.second->Category) ==
> CategoriesEnd &&
> I.second->Category != &GenericCategory)
> @@ -1862,7 +2142,12 @@ void cl::HideUnrelatedOptions(ArrayRef<c
> }
> }
>
> +void cl::ResetCommandLineParser() { GlobalParser->reset(); }
> +void cl::ResetAllOptionOccurrences() {
> + GlobalParser->ResetAllOptionOccurrences();
> +}
> +
> void LLVMParseCommandLineOptions(int argc, const char *const *argv,
> const char *Overview) {
> - llvm::cl::ParseCommandLineOptions(argc, argv, Overview);
> + llvm::cl::ParseCommandLineOptions(argc, argv, Overview, true);
> }
>
> Modified: llvm/trunk/unittests/Support/CommandLineTest.cpp
> URL:
> http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/Support/CommandLineTest.cpp?rev=274171&r1=274170&r2=274171&view=diff
>
> ==============================================================================
> --- llvm/trunk/unittests/Support/CommandLineTest.cpp (original)
> +++ llvm/trunk/unittests/Support/CommandLineTest.cpp Wed Jun 29 16:48:26
> 2016
> @@ -67,6 +67,22 @@ public:
> : Base(M0, M1, M2, M3) {}
>
> ~StackOption() override { this->removeArgument(); }
> +
> + template <class DT> StackOption<T> &operator=(const DT &V) {
> + this->setValue(V);
> + return *this;
> + }
> +};
> +
> +class StackSubCommand : public cl::SubCommand {
> +public:
> + StackSubCommand(const char *const Name,
> + const char *const Description = nullptr)
> + : SubCommand(Name, Description) {}
> +
> + StackSubCommand() : SubCommand() {}
> +
> + ~StackSubCommand() { unregisterSubCommand(); }
> };
>
>
> @@ -78,7 +94,8 @@ TEST(CommandLineTest, ModifyExisitingOpt
> const char ArgString[] = "new-test-option";
> const char ValueString[] = "Integer";
>
> - StringMap<cl::Option *> &Map = cl::getRegisteredOptions();
> + StringMap<cl::Option *> &Map =
> + cl::getRegisteredOptions(*cl::TopLevelSubCommand);
>
> ASSERT_TRUE(Map.count("test-option") == 1) <<
> "Could not find option in map.";
> @@ -237,7 +254,8 @@ TEST(CommandLineTest, HideUnrelatedOptio
> ASSERT_EQ(cl::NotHidden, TestOption2.getOptionHiddenFlag())
> << "Hid extra option that should be visable.";
>
> - StringMap<cl::Option *> &Map = cl::getRegisteredOptions();
> + StringMap<cl::Option *> &Map =
> + cl::getRegisteredOptions(*cl::TopLevelSubCommand);
> ASSERT_EQ(cl::NotHidden, Map["help"]->getOptionHiddenFlag())
> << "Hid default option that should be visable.";
> }
> @@ -261,9 +279,201 @@ TEST(CommandLineTest, HideUnrelatedOptio
> ASSERT_EQ(cl::NotHidden, TestOption3.getOptionHiddenFlag())
> << "Hid extra option that should be visable.";
>
> - StringMap<cl::Option *> &Map = cl::getRegisteredOptions();
> + StringMap<cl::Option *> &Map =
> + cl::getRegisteredOptions(*cl::TopLevelSubCommand);
> ASSERT_EQ(cl::NotHidden, Map["help"]->getOptionHiddenFlag())
> << "Hid default option that should be visable.";
> }
>
> +TEST(CommandLineTest, SetValueInSubcategories) {
> + cl::ResetCommandLineParser();
> +
> + StackSubCommand SC1("sc1", "First subcommand");
> + StackSubCommand SC2("sc2", "Second subcommand");
> +
> + StackOption<bool> TopLevelOpt("top-level", cl::init(false));
> + StackOption<bool> SC1Opt("sc1", cl::sub(SC1), cl::init(false));
> + StackOption<bool> SC2Opt("sc2", cl::sub(SC2), cl::init(false));
> +
> + EXPECT_FALSE(TopLevelOpt);
> + EXPECT_FALSE(SC1Opt);
> + EXPECT_FALSE(SC2Opt);
> + const char *args[] = {"prog", "-top-level"};
> + EXPECT_TRUE(cl::ParseCommandLineOptions(2, args, nullptr, true));
> + EXPECT_TRUE(TopLevelOpt);
> + EXPECT_FALSE(SC1Opt);
> + EXPECT_FALSE(SC2Opt);
> +
> + TopLevelOpt = false;
> +
> + cl::ResetAllOptionOccurrences();
> + EXPECT_FALSE(TopLevelOpt);
> + EXPECT_FALSE(SC1Opt);
> + EXPECT_FALSE(SC2Opt);
> + const char *args2[] = {"prog", "sc1", "-sc1"};
> + EXPECT_TRUE(cl::ParseCommandLineOptions(3, args2, nullptr, true));
> + EXPECT_FALSE(TopLevelOpt);
> + EXPECT_TRUE(SC1Opt);
> + EXPECT_FALSE(SC2Opt);
> +
> + SC1Opt = false;
> +
> + cl::ResetAllOptionOccurrences();
> + EXPECT_FALSE(TopLevelOpt);
> + EXPECT_FALSE(SC1Opt);
> + EXPECT_FALSE(SC2Opt);
> + const char *args3[] = {"prog", "sc2", "-sc2"};
> + EXPECT_TRUE(cl::ParseCommandLineOptions(3, args3, nullptr, true));
> + EXPECT_FALSE(TopLevelOpt);
> + EXPECT_FALSE(SC1Opt);
> + EXPECT_TRUE(SC2Opt);
> +}
> +
> +TEST(CommandLineTest, LookupFailsInWrongSubCommand) {
> + cl::ResetCommandLineParser();
> +
>
> + StackSubCommand SC1("sc1", "First subcommand");<br
> class="m_1046857407579580143gmail_msg
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20161010/f3877bd5/attachment-0001.html>
More information about the llvm-commits
mailing list