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

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@274171 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Zachary Turner 2016-06-29 21:48:26 +00:00
parent a872e23126
commit eb2c1ebbb2
4 changed files with 679 additions and 107 deletions

View File

@ -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
@ -170,6 +173,45 @@ public:
// The general Option Category (used as default category).
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
//
@ -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;
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 = false, bool Categorized = false);
/// 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 &Saver, TokenizerCallback Tokenizer,
/// 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::OptionCategory &Category);
/// 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

View File

@ -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(StringRef &Arg, StringRef &Value) {
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 char *progName, const char *envVar,
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::ParseCommandLineOptions(int argc,
// 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::ParseCommandLineOptions(int argc,
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::ParseCommandLineOptions(int argc,
// 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::ParseCommandLineOptions(int argc,
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::ParseCommandLineOptions(int argc,
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::ParseCommandLineOptions(int argc,
if (!Handler) {
if (SinkOpts.empty()) {
errs() << ProgramName << ": Unknown command line argument '" << argv[i]
<< "'. Try: '" << argv[0] << " -help'\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";
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::ParseCommandLineOptions(int argc,
// 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::ParseCommandLineOptions(int argc,
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::pair<const char *, Option *> *LHS,
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 *> &OptMap,
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) {
// 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<bool>> HLOp(
"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<bool>>
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::printOptionValues() {
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 (*func)()) {
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<const cl::OptionCategory *> Categories) {
}
}
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);
}

View File

@ -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, ModifyExisitingOption) {
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, HideUnrelatedOptions) {
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, HideUnrelatedOptionsMulti) {
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");
StackSubCommand SC2("sc2", "Second subcommand");
StackOption<bool> SC1Opt("sc1", cl::sub(SC1), cl::init(false));
StackOption<bool> SC2Opt("sc2", cl::sub(SC2), cl::init(false));
const char *args[] = {"prog", "sc1", "-sc2"};
EXPECT_FALSE(cl::ParseCommandLineOptions(3, args, nullptr, true));
}
TEST(CommandLineTest, AddToAllSubCommands) {
cl::ResetCommandLineParser();
StackSubCommand SC1("sc1", "First subcommand");
StackOption<bool> AllOpt("everywhere", cl::sub(*cl::AllSubCommands),
cl::init(false));
StackSubCommand SC2("sc2", "Second subcommand");
const char *args[] = {"prog", "-everywhere"};
const char *args2[] = {"prog", "sc1", "-everywhere"};
const char *args3[] = {"prog", "sc2", "-everywhere"};
EXPECT_FALSE(AllOpt);
EXPECT_TRUE(cl::ParseCommandLineOptions(2, args, nullptr, true));
EXPECT_TRUE(AllOpt);
AllOpt = false;
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(AllOpt);
EXPECT_TRUE(cl::ParseCommandLineOptions(3, args2, nullptr, true));
EXPECT_TRUE(AllOpt);
AllOpt = false;
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(AllOpt);
EXPECT_TRUE(cl::ParseCommandLineOptions(3, args3, nullptr, true));
EXPECT_TRUE(AllOpt);
}
TEST(CommandLineTest, ReparseCommandLineOptions) {
cl::ResetCommandLineParser();
StackOption<bool> TopLevelOpt("top-level", cl::sub(*cl::TopLevelSubCommand),
cl::init(false));
const char *args[] = {"prog", "-top-level"};
EXPECT_FALSE(TopLevelOpt);
EXPECT_TRUE(cl::ParseCommandLineOptions(2, args, nullptr, true));
EXPECT_TRUE(TopLevelOpt);
TopLevelOpt = false;
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(TopLevelOpt);
EXPECT_TRUE(cl::ParseCommandLineOptions(2, args, nullptr, true));
EXPECT_TRUE(TopLevelOpt);
}
TEST(CommandLineTest, RemoveFromRegularSubCommand) {
cl::ResetCommandLineParser();
StackSubCommand SC("sc", "Subcommand");
StackOption<bool> RemoveOption("remove-option", cl::sub(SC), cl::init(false));
StackOption<bool> KeepOption("keep-option", cl::sub(SC), cl::init(false));
const char *args[] = {"prog", "sc", "-remove-option"};
EXPECT_FALSE(RemoveOption);
EXPECT_TRUE(cl::ParseCommandLineOptions(3, args, nullptr, true));
EXPECT_TRUE(RemoveOption);
RemoveOption.removeArgument();
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(cl::ParseCommandLineOptions(3, args, nullptr, true));
}
TEST(CommandLineTest, RemoveFromTopLevelSubCommand) {
cl::ResetCommandLineParser();
StackOption<bool> TopLevelRemove(
"top-level-remove", cl::sub(*cl::TopLevelSubCommand), cl::init(false));
StackOption<bool> TopLevelKeep(
"top-level-keep", cl::sub(*cl::TopLevelSubCommand), cl::init(false));
const char *args[] = {"prog", "-top-level-remove"};
EXPECT_FALSE(TopLevelRemove);
EXPECT_TRUE(cl::ParseCommandLineOptions(2, args, nullptr, true));
EXPECT_TRUE(TopLevelRemove);
TopLevelRemove.removeArgument();
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(cl::ParseCommandLineOptions(2, args, nullptr, true));
}
TEST(CommandLineTest, RemoveFromAllSubCommands) {
cl::ResetCommandLineParser();
StackSubCommand SC1("sc1", "First Subcommand");
StackSubCommand SC2("sc2", "Second Subcommand");
StackOption<bool> RemoveOption("remove-option", cl::sub(*cl::AllSubCommands),
cl::init(false));
StackOption<bool> KeepOption("keep-option", cl::sub(*cl::AllSubCommands),
cl::init(false));
const char *args0[] = {"prog", "-remove-option"};
const char *args1[] = {"prog", "sc1", "-remove-option"};
const char *args2[] = {"prog", "sc2", "-remove-option"};
// It should work for all subcommands including the top-level.
EXPECT_FALSE(RemoveOption);
EXPECT_TRUE(cl::ParseCommandLineOptions(2, args0, nullptr, true));
EXPECT_TRUE(RemoveOption);
RemoveOption = false;
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(RemoveOption);
EXPECT_TRUE(cl::ParseCommandLineOptions(3, args1, nullptr, true));
EXPECT_TRUE(RemoveOption);
RemoveOption = false;
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(RemoveOption);
EXPECT_TRUE(cl::ParseCommandLineOptions(3, args2, nullptr, true));
EXPECT_TRUE(RemoveOption);
RemoveOption.removeArgument();
// It should not work for any subcommands including the top-level.
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(cl::ParseCommandLineOptions(2, args0, nullptr, true));
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(cl::ParseCommandLineOptions(3, args1, nullptr, true));
cl::ResetAllOptionOccurrences();
EXPECT_FALSE(cl::ParseCommandLineOptions(3, args2, nullptr, true));
}
} // anonymous namespace

View File

@ -231,7 +231,7 @@ TEST_F(ProgramEnvTest, TestExecuteNoWait) {
// LoopCount should only be incremented once.
while (true) {
++LoopCount;
ProcessInfo WaitResult = Wait(PI1, 0, true, &Error);
ProcessInfo WaitResult = llvm::sys::Wait(PI1, 0, true, &Error);
ASSERT_TRUE(Error.empty());
if (WaitResult.Pid == PI1.Pid)
break;
@ -248,7 +248,7 @@ TEST_F(ProgramEnvTest, TestExecuteNoWait) {
// cse, LoopCount should be greater than 1 (more than one increment occurs).
while (true) {
++LoopCount;
ProcessInfo WaitResult = Wait(PI2, 0, false, &Error);
ProcessInfo WaitResult = llvm::sys::Wait(PI2, 0, false, &Error);
ASSERT_TRUE(Error.empty());
if (WaitResult.Pid == PI2.Pid)
break;