CommandLine 2.0 库手册¶
简介¶
本文档描述了命令行参数处理库。它将向您展示如何使用它以及它可以做什么。CommandLine 库使用声明式方法来指定程序使用的命令行选项。默认情况下,这些选项声明隐式地保存为已解析的选项值(当然,这可以更改)。
尽管在许多不同的语言中有很多大量的命令行参数解析库,但没有一个完全适合我的需求。通过查看其他库的功能和问题,我设计了 CommandLine 库以具有以下功能
速度:CommandLine 库非常快并且使用很少的资源。库的解析时间与解析的参数数量成正比,而不是与识别的选项数量成正比。此外,命令行参数值会透明地捕获到用户定义的全局变量中,这些变量可以像任何其他变量一样访问(并且具有相同的性能)。
类型安全:作为 CommandLine 的用户,您不必担心记住您想要的参数类型(是整数?字符串?布尔值?枚举?),并且不断地进行类型转换。这不仅有助于防止容易出错的结构,而且还导致更简洁的源代码。
无需子类:要使用 CommandLine,您需要实例化与您想要捕获的参数相对应的变量,您无需子类化解析器。这意味着您不必编写任何样板代码。
全局可访问:库可以指定命令行参数,这些参数会在链接到该库的任何工具中自动启用。这是可能的,因为应用程序不必维护要传递给解析器的参数列表。这也使支持动态加载的选项变得微不足道。
更简洁:CommandLine 直接支持枚举和其他类型,这意味着库中内置了更少的错误和更高的安全性。您不必担心您的整型命令行参数是否意外地被分配了一个对您的枚举类型无效的值。
功能强大:CommandLine 库支持许多不同类型的参数,从简单的布尔标志到标量参数(字符串、整数、枚举、双精度浮点数),到参数列表。这是可能的,因为 CommandLine 是…
可扩展的:向 CommandLine 添加新的参数类型非常简单。在声明命令行选项时,只需指定要使用的解析器即可。自定义解析器 也没有问题。
节省劳动:CommandLine 库减少了您(用户)需要做的繁琐工作量。例如,它自动提供了一个
-help
选项,该选项显示工具可用的命令行选项。此外,它还为您完成了大部分基本的正确性检查。功能完备:CommandLine 库可以处理实际程序中经常遇到的许多不同形式的选项。例如,位置参数、
ls
样式的分组选项(以允许自然地处理“ls -lad
”)、ld
样式的前缀选项(以解析“-lmalloc -L/usr/lib
”)和解释器样式的选项。
希望本文档能让您快速轻松地开始在您的实用程序中使用 CommandLine。此外,它还应该是一份简单的参考手册,以便了解事物的工作原理。
快速入门指南¶
本节手册介绍了一个基本编译器工具的简单 CommandLine 化过程。旨在向您展示如何在自己的程序中开始使用 CommandLine 库,并向您展示它可以做的一些很酷的事情。
首先,您需要将 CommandLine 头文件包含到您的程序中
#include "llvm/Support/CommandLine.h"
此外,您需要将此作为主程序的第一行
int main(int argc, char **argv) {
cl::ParseCommandLineOptions(argc, argv);
...
}
…它实际上解析参数并填充变量声明。
现在您已准备好支持命令行参数,我们需要告诉系统我们想要哪些参数以及它们是什么类型的参数。CommandLine 库使用声明式语法来使用捕获解析值的全局变量声明来建模命令行参数。这意味着对于您想要支持的每个命令行选项,都应该有一个全局变量声明来捕获结果。例如,在编译器中,我们希望支持 Unix 标准的“-o <filename>
”选项来指定输出位置。使用 CommandLine 库,它表示如下
cl::opt<string> OutputFilename("o", cl::desc("Specify output filename"), cl::value_desc("filename"));
这声明了一个全局变量“OutputFilename
”,用于捕获“o
”参数的结果(第一个参数)。我们使用“cl::opt
”模板(而不是“cl::list
”模板)指定这是一个简单的标量选项,并告诉 CommandLine 库我们正在解析的数据类型是字符串。
第二个和第三个参数(可选)用于指定“-help
”选项的输出内容。在这种情况下,我们得到一行如下所示
USAGE: compiler [options]
OPTIONS:
-h - Alias for -help
-help - display available options (-help-hidden for more)
-o <filename> - Specify output filename
因为我们指定了命令行选项应使用string
数据类型进行解析,所以声明的变量可以自动用作所有上下文中真正的字符串,在这些上下文中可以使用普通的 C++ 字符串对象。例如
...
std::ofstream Output(OutputFilename.c_str());
if (Output.good()) ...
...
您可以使用许多不同的选项来自定义命令行选项处理库,但上面的示例显示了这些选项的通用接口。这些选项可以按任何顺序指定,并使用cl::desc(…)等辅助函数指定,因此没有需要记住的位置依赖关系。可用选项在参考指南中详细讨论。
继续上面的示例,我们希望我们的编译器既能接收输入文件名又能接收输出文件名,但我们不希望使用连字符来指定输入文件名(即,不是-filename.c
)。为了支持这种类型的参数,CommandLine 库允许为程序指定位置参数。这些位置参数由不是选项形式的命令行参数填充。我们像这样使用此功能
cl::opt<string> InputFilename(cl::Positional, cl::desc("<input file>"), cl::init("-"));
此声明表明第一个位置参数应被视为输入文件名。这里我们使用cl::init选项为命令行选项指定初始值,如果未指定选项,则使用该值(如果您没有为选项指定cl::init修饰符,则使用数据类型的默认构造函数来初始化值)。命令行选项默认为可选,因此如果我们希望要求用户始终指定输入文件名,我们将添加cl::Required标志,并且可以删除cl::init修饰符,如下所示
cl::opt<string> InputFilename(cl::Positional, cl::desc("<input file>"), cl::Required);
同样,CommandLine 库不要求以任何特定顺序指定选项,因此上面的声明等同于
cl::opt<string> InputFilename(cl::Positional, cl::Required, cl::desc("<input file>"));
只需添加cl::Required标志,CommandLine 库就会在未指定参数时自动发出错误,这将所有命令行选项验证代码从您的应用程序转移到库中。这仅仅是使用标志如何在每个选项的基础上更改库的默认行为的一个示例。通过添加上述声明之一,“-help
”选项概要现在扩展为
USAGE: compiler [options] <input file>
OPTIONS:
-h - Alias for -help
-help - display available options (-help-hidden for more)
-o <filename> - Specify output filename
…表明需要输入文件名。
布尔参数¶
除了输入和输出文件名,我们希望编译器示例支持三个布尔标志:“-f
” 强制将二进制输出写入终端,“--quiet
” 启用静默模式,以及“-q
” 用于与我们部分用户的向后兼容。我们可以通过像这样声明布尔类型的选项来支持它们
cl::opt<bool> Force ("f", cl::desc("Enable binary output on terminals"));
cl::opt<bool> Quiet ("quiet", cl::desc("Don't print informational messages"));
cl::opt<bool> Quiet2("q", cl::desc("Don't print informational messages"), cl::Hidden);
这会如你所料地工作:它声明了三个布尔变量(“Force
”、“Quiet
” 和“Quiet2
”)来识别这些选项。请注意,“-q
” 选项是用“cl::Hidden” 标志指定的。此修饰符阻止它被标准的“-help
” 输出显示(请注意,它仍然显示在“-help-hidden
” 输出中)。
CommandLine 库对不同的数据类型使用不同的解析器。例如,在字符串情况下,传递给选项的参数会逐字复制到字符串变量的内容中……但是,我们显然不能在布尔情况下这样做,因此我们必须使用更智能的解析器。在布尔解析器的情况下,它不允许任何选项(在这种情况下,它将 true 的值分配给变量),或者它允许指定“true
” 或“false
” 值,允许以下任何输入
compiler -f # No value, 'Force' == true
compiler -f=true # Value specified, 'Force' == true
compiler -f=TRUE # Value specified, 'Force' == true
compiler -f=FALSE # Value specified, 'Force' == false
……你懂的。 bool 解析器 只是将字符串值转换为布尔值,并拒绝诸如“compiler -f=foo
” 之类的内容。类似地,float、double 和int 解析器的工作方式与您预期的一样,使用“strtol
” 和“strtod
” C 库调用将字符串值解析为指定的数据类型。
使用上面的声明,“compiler -help
” 会发出以下内容
USAGE: compiler [options] <input file>
OPTIONS:
-f - Enable binary output on terminals
-o - Override output filename
-quiet - Don't print informational messages
-help - display available options (-help-hidden for more)
而“compiler -help-hidden
” 会打印以下内容
USAGE: compiler [options] <input file>
OPTIONS:
-f - Enable binary output on terminals
-o - Override output filename
-q - Don't print informational messages
-quiet - Don't print informational messages
-help - display available options (-help-hidden for more)
这个简短的示例向您展示了如何使用“cl::opt” 类来解析简单的标量命令行参数。除了简单的标量参数外,CommandLine 库还提供了一些原语来支持命令行选项别名 和选项列表。
参数别名¶
到目前为止,该示例运行良好,除了我们现在需要像这样检查静默条件
...
if (!Quiet && !Quiet2) printInformationalMessage(...);
...
……这真是太麻烦了!与其为同一个条件定义两个值,不如使用“cl::alias” 类使“-q
” 选项成为“-quiet
” 选项的**别名**,而不是提供值本身
cl::opt<bool> Force ("f", cl::desc("Overwrite output files"));
cl::opt<bool> Quiet ("quiet", cl::desc("Don't print informational messages"));
cl::alias QuietA("q", cl::desc("Alias for -quiet"), cl::aliasopt(Quiet));
第三行(这是我们从上面修改的唯一一行)定义了一个“-q
” 别名,只要指定它,它就会更新“Quiet
” 变量(如 cl::aliasopt 修饰符所指定)。因为别名不保存状态,所以程序唯一需要查询的就是 Quiet
变量。别名的另一个不错的功能是它们会自动隐藏自己,不会出现在 -help
输出中(尽管它们仍然显示在 -help-hidden output
中)。
现在应用程序代码可以简单地使用
...
if (!Quiet) printInformationalMessage(...);
...
……这要好得多!“cl::alias” 可用于为任何变量类型指定替代名称,并且有很多用途。
从一组可能性中选择一个替代方案¶
到目前为止,我们已经了解了 CommandLine 库如何处理内置类型,例如 std::string
、bool
和 int
,但它如何处理它不知道的东西,例如枚举或“int*
”?
答案是它使用表驱动的通用解析器(除非您指定自己的解析器,如 扩展指南 中所述)。此解析器将文字字符串映射到所需的任何类型,并要求您告诉它此映射应该是什么。
假设我们希望使用标准标志“-g
”、“-O0
”、“-O1
” 和“-O2
” 向我们的优化器添加四个优化级别。我们可以像上面那样轻松地使用布尔选项来实现这一点,但这种策略存在一些问题
用户可以同时指定多个选项,例如“
compiler -O3 -O2
”。CommandLine 库无法为我们捕获此错误输入。我们将不得不测试 4 个不同的变量以查看哪些变量已设置。
这与我们想要的数字级别不匹配……因此我们无法轻松查看是否启用了某些级别 >= “
-O1
”。
为了解决这些问题,我们可以使用枚举值,并让 CommandLine 库直接用适当的级别填充它,使用方法如下
enum OptLevel {
g, O1, O2, O3
};
cl::opt<OptLevel> OptimizationLevel(cl::desc("Choose optimization level:"),
cl::values(
clEnumVal(g , "No optimizations, enable debugging"),
clEnumVal(O1, "Enable trivial optimizations"),
clEnumVal(O2, "Enable default optimizations"),
clEnumVal(O3, "Enable expensive optimizations")));
...
if (OptimizationLevel >= O2) doPartialRedundancyElimination(...);
...
此声明定义了一个“OptimizationLevel
” 变量,其类型为“OptLevel
” 枚举类型。此变量可以分配声明中列出的任何值。CommandLine 库强制用户只能指定其中一个选项,并确保只能指定有效的枚举值。“clEnumVal
” 宏确保命令行参数与枚举值匹配。添加此选项后,我们的帮助输出现在是
USAGE: compiler [options] <input file>
OPTIONS:
Choose optimization level:
-g - No optimizations, enable debugging
-O1 - Enable trivial optimizations
-O2 - Enable default optimizations
-O3 - Enable expensive optimizations
-f - Enable binary output on terminals
-help - display available options (-help-hidden for more)
-o <filename> - Specify output filename
-quiet - Don't print informational messages
在这种情况下,标志名称直接对应于枚举名称有点尴尬,因为我们可能不希望在程序中有一个名为“g
” 的枚举定义。因此,我们可以像这样替代地编写此示例
enum OptLevel {
Debug, O1, O2, O3
};
cl::opt<OptLevel> OptimizationLevel(cl::desc("Choose optimization level:"),
cl::values(
clEnumValN(Debug, "g", "No optimizations, enable debugging"),
clEnumVal(O1 , "Enable trivial optimizations"),
clEnumVal(O2 , "Enable default optimizations"),
clEnumVal(O3 , "Enable expensive optimizations")));
...
if (OptimizationLevel == Debug) outputDebugInfo(...);
...
通过使用“clEnumValN
” 宏而不是“clEnumVal
”,我们可以直接指定标志应获取的名称。通常,直接映射很好,但有时您无法或不想保留映射,这时您将使用它。
命名替代方案¶
另一种有用的参数形式是命名替代样式。我们将在我们的编译器中使用此样式来指定可以使用的不同调试级别。我们不希望每个调试级别都是它自己的开关,而是希望支持以下选项,其中一次只能指定一个:“--debug-level=none
”、“--debug-level=quick
”、“--debug-level=detailed
”。为此,我们使用与优化级别标志完全相同的格式,但我们还指定了一个选项名称。对于这种情况,代码如下所示
enum DebugLev {
nodebuginfo, quick, detailed
};
// Enable Debug Options to be specified on the command line
cl::opt<DebugLev> DebugLevel("debug_level", cl::desc("Set the debugging level:"),
cl::values(
clEnumValN(nodebuginfo, "none", "disable debug information"),
clEnumVal(quick, "enable quick debug information"),
clEnumVal(detailed, "enable detailed debug information")));
此定义定义了一个类型为“enum DebugLev
” 的枚举命令行变量,其工作方式与之前完全相同。这里唯一的区别只是向程序用户公开的界面以及“-help
” 选项输出的帮助。
USAGE: compiler [options] <input file>
OPTIONS:
Choose optimization level:
-g - No optimizations, enable debugging
-O1 - Enable trivial optimizations
-O2 - Enable default optimizations
-O3 - Enable expensive optimizations
-debug_level - Set the debugging level:
=none - disable debug information
=quick - enable quick debug information
=detailed - enable detailed debug information
-f - Enable binary output on terminals
-help - display available options (-help-hidden for more)
-o <filename> - Specify output filename
-quiet - Don't print informational messages
同样,调试级别声明和优化级别声明之间唯一的结构差异是调试级别声明包含一个选项名称("debug_level"
),它会自动更改库处理参数的方式。CommandLine 库支持这两种形式,以便您可以选择最适合您应用程序的形式。
解析选项列表¶
现在我们已经解决了标准的普通参数类型,让我们变得疯狂一点。假设我们希望我们的优化器接受要执行的**列表**优化,并允许重复。例如,我们可能希望运行:“compiler -dce -instsimplify -inline -dce -strip
”。在这种情况下,参数的顺序和出现的次数非常重要。这就是“cl::list
” 模板的用途。首先,从定义要执行的优化的枚举开始
enum Opts {
// 'inline' is a C++ keyword, so name it 'inlining'
dce, instsimplify, inlining, strip
};
然后定义您的“cl::list
” 变量
cl::list<Opts> OptimizationList(cl::desc("Available Optimizations:"),
cl::values(
clEnumVal(dce , "Dead Code Elimination"),
clEnumVal(instsimplify , "Instruction Simplification"),
clEnumValN(inlining, "inline", "Procedure Integration"),
clEnumVal(strip , "Strip Symbols")));
这定义了一个从概念上讲类型为“std::vector<enum Opts>
” 的变量。因此,您可以使用标准向量方法访问它
for (unsigned i = 0; i != OptimizationList.size(); ++i)
switch (OptimizationList[i])
...
……遍历指定的选项列表。
请注意,“cl::list
” 模板是完全通用的,可以与您可以与“cl::opt
” 模板一起使用的任何数据类型或其他参数一起使用。使用列表的一种特别有用的方法是,如果可能指定多个位置参数,则将所有位置参数捕获在一起。例如,在链接器的情况下,链接器获取多个“.o
” 文件,并且需要将它们捕获到一个列表中。这自然地指定为
...
cl::list<std::string> InputFilenames(cl::Positional, cl::desc("<Input files>"), cl::OneOrMore);
...
此变量的工作方式与“vector<string>
” 对象相同。因此,访问列表就像上面一样简单。在此示例中,我们使用了 cl::OneOrMore 修饰符来通知 CommandLine 库,如果用户在命令行上未指定任何“.o
” 文件,则为错误。同样,这只是减少了我们必须执行的检查量。
将选项收集为一组标志¶
除了将一组选项收集到列表中之外,还可以使用**位向量**来收集枚举值的信息。 cl::bits 类使用的表示形式是一个unsigned
整数。枚举值由枚举的序数值位位置上的 0/1 表示。1 表示指定了该枚举,0 则表示未指定。在解析每个指定的值时,选项的位向量中会设置结果枚举的位。
bits |= 1 << (unsigned)enum;
多次指定的选项是冗余的。第一个之后的任何实例都会被丢弃。
重新编写上面的列表示例,我们可以用 cl::bits 替换 cl::list
cl::bits<Opts> OptimizationBits(cl::desc("Available Optimizations:"),
cl::values(
clEnumVal(dce , "Dead Code Elimination"),
clEnumVal(instsimplify , "Instruction Simplification"),
clEnumValN(inlining, "inline", "Procedure Integration"),
clEnumVal(strip , "Strip Symbols")));
要测试是否指定了instsimplify
,我们可以使用cl:bits::isSet
函数。
if (OptimizationBits.isSet(instsimplify)) {
...
}
还可以使用cl::bits::getBits
函数获取原始位向量。
unsigned bits = OptimizationBits.getBits();
最后,如果使用外部存储,则指定的存储位置必须是unsigned
**类型**。在所有其他方面,cl::bits 选项等效于 cl::list 选项。
向帮助输出添加自由格式文本¶
随着程序的增长和成熟,我们可能决定将有关其功能的摘要信息放入帮助输出中。帮助输出的样式类似于 Unix man
页面,提供有关程序的简洁信息。但是,Unix man
页面通常包含有关程序功能的描述。要将其添加到您的 CommandLine 程序中,只需将第三个参数传递给 main 中的 cl::ParseCommandLineOptions 调用即可。然后,此附加参数将作为程序的概述信息打印出来,允许您包含任何所需的附加信息。例如
int main(int argc, char **argv) {
cl::ParseCommandLineOptions(argc, argv, " CommandLine compiler example\n\n"
" This program blah blah blah...\n");
...
}
将产生以下帮助输出
**OVERVIEW: CommandLine compiler example
This program blah blah blah...**
USAGE: compiler [options] <input file>
OPTIONS:
...
-help - display available options (-help-hidden for more)
-o <filename> - Specify output filename
将选项分组到类别中¶
如果我们的程序有很多选项,用户可能难以浏览 -help
的输出。为了缓解这个问题,我们可以将选项分成类别。这可以通过声明选项类别(cl::OptionCategory 对象),然后使用 cl::cat 选项属性将选项放入这些类别中来完成。例如
cl::OptionCategory StageSelectionCat("Stage Selection Options",
"These control which stages are run.");
cl::opt<bool> Preprocessor("E",cl::desc("Run preprocessor stage."),
cl::cat(StageSelectionCat));
cl::opt<bool> NoLink("c",cl::desc("Run all stages except linking."),
cl::cat(StageSelectionCat));
如果声明了选项类别,则 -help
的输出将被分类。输出看起来像这样
OVERVIEW: This is a small program to demo the LLVM CommandLine API
USAGE: Sample [options]
OPTIONS:
General options:
-help - Display available options (-help-hidden for more)
-help-list - Display list of available options (-help-list-hidden for more)
Stage Selection Options:
These control which stages are run.
-E - Run preprocessor stage.
-c - Run all stages except linking.
除了声明选项类别时 -help
的行为发生变化之外,命令行选项 -help-list
也会变得可见,它将打印命令行选项作为未分类的列表。
请注意,未明确分类的选项将被放置在 cl::getGeneralCategory()
类别中。
参考指南¶
现在您已经了解了如何使用 CommandLine 库的基本知识,本节将为您提供调整命令行选项工作方式所需的信息,以及有关更“高级”命令行选项处理功能的信息。
位置参数¶
位置参数是指那些没有命名并且没有使用连字符指定的参数。当选项仅由其位置指定时,应使用位置参数。例如,标准 Unix grep
工具采用正则表达式参数,以及一个可选的文件名进行搜索(如果未指定文件名,则默认为标准输入)。使用 CommandLine 库,这将指定为
cl::opt<string> Regex (cl::Positional, cl::desc("<regular expression>"), cl::Required);
cl::opt<string> Filename(cl::Positional, cl::desc("<input file>"), cl::init("-"));
鉴于这两个选项声明,我们的 grep 替换的 -help
输出将如下所示
USAGE: spiffygrep [options] <regular expression> <input file>
OPTIONS:
-help - display available options (-help-hidden for more)
…并且生成的程序可以使用起来就像标准的 grep
工具一样。
位置参数按其构造顺序排序。这意味着命令行选项将根据其在 .cpp 文件中的列出顺序进行排序,但如果位置参数在多个 .cpp 文件中定义,则不会定义排序顺序。解决此问题的方法是在一个 .cpp 文件中定义所有位置参数。
使用连字符指定位置选项¶
有时您可能希望为以连字符开头的位置参数指定一个值(例如,在文件中搜索“-foo
”)。最初,您将难以做到这一点,因为它会尝试查找名为“-foo
”的参数,并且会失败(并且单引号不会帮助您)。请注意,系统 grep
也有同样的问题。
$ spiffygrep '-foo' test.txt
Unknown command line argument '-foo'. Try: spiffygrep -help'
$ grep '-foo' test.txt
grep: illegal option -- f
grep: illegal option -- o
grep: illegal option -- o
Usage: grep -hblcnsviw pattern file . . .
此问题的解决方案对于您的工具和系统版本都是一样的:使用“--
”标记。当用户在命令行上指定“--
”时,它告诉程序“--
”之后的全部选项应被视为位置参数,而不是选项。因此,我们可以像这样使用它
$ spiffygrep -- -foo test.txt
...output...
使用 getPosition() 确定绝对位置¶
有时一个选项会影响或修改另一个选项的含义。例如,考虑 gcc
的 -x LANG
选项。这告诉 gcc
忽略后续位置参数的后缀,并强制将文件解释为包含语言 LANG
的源代码。为了正确处理这种情况,您需要了解每个参数的绝对位置,尤其是列表中的参数,以便可以正确应用其交互。这对于像 -llibname
这样的选项也很有用,它实际上是以连字符开头的的位置参数。
因此,通常情况下,问题是您有两个 cl::list
变量以某种方式交互。为了确保正确的交互,您可以使用 cl::list::getPosition(optnum)
方法。此方法返回 cl::list
中 optnum
项的绝对位置(在命令行中找到的位置)。
用法习惯用法如下
static cl::list<std::string> Files(cl::Positional, cl::OneOrMore);
static cl::list<std::string> Libraries("l");
int main(int argc, char**argv) {
// ...
std::vector<std::string>::iterator fileIt = Files.begin();
std::vector<std::string>::iterator libIt = Libraries.begin();
unsigned libPos = 0, filePos = 0;
while ( 1 ) {
if ( libIt != Libraries.end() )
libPos = Libraries.getPosition( libIt - Libraries.begin() );
else
libPos = 0;
if ( fileIt != Files.end() )
filePos = Files.getPosition( fileIt - Files.begin() );
else
filePos = 0;
if ( filePos != 0 && (libPos == 0 || filePos < libPos) ) {
// Source File Is next
++fileIt;
}
else if ( libPos != 0 && (filePos == 0 || libPos < filePos) ) {
// Library is next
++libIt;
}
else
break; // we're done with the list
}
}
请注意,出于兼容性原因,cl::opt
也支持 unsigned getPosition()
选项,它将提供该选项的绝对位置。您可以对 cl::opt
和 cl::list
选项应用与两个列表相同的方法。
cl::ConsumeAfter
修饰符¶
cl::ConsumeAfter
格式化选项 用于构造使用“解释器样式”选项处理的程序。使用这种样式的选项处理,最后一个位置参数之后指定的所有参数都被视为命令行参数未解释的特殊解释器参数。
作为一个具体的例子,假设我们正在开发标准 Unix Bourne shell(/bin/sh
)的替代品。要运行 /bin/sh
,首先您需要为 shell 本身指定选项(例如 -x
,它会打开跟踪输出),然后您指定要运行的脚本的名称,然后您为脚本指定参数。这些脚本参数由 Bourne shell 命令行选项处理器解析,但不会被解释为 shell 本身的选项。使用 CommandLine 库,我们将指定为
cl::opt<string> Script(cl::Positional, cl::desc("<input script>"), cl::init("-"));
cl::list<string> Argv(cl::ConsumeAfter, cl::desc("<program arguments>..."));
cl::opt<bool> Trace("x", cl::desc("Enable trace output"));
它会自动提供帮助输出
USAGE: spiffysh [options] <input script> <program arguments>...
OPTIONS:
-help - display available options (-help-hidden for more)
-x - Enable trace output
在运行时,如果我们像这样运行我们的新 shell 替代品 `spiffysh -x test.sh -a -x -y bar`
,则 Trace
变量将设置为 true,Script
变量将设置为“test.sh
”,并且 Argv
列表将包含 ["-a", "-x", "-y", "bar"]
,因为它们是在最后一个位置参数(即脚本名称)之后指定的。
对何时可以指定 cl::ConsumeAfter
选项有一些限制。例如,每个程序只能指定一个 cl::ConsumeAfter
,必须至少指定一个 位置参数,不能有任何 cl::list 位置参数,并且 cl::ConsumeAfter
选项应该是一个 cl::list 选项。
内部存储与外部存储¶
默认情况下,所有命令行选项都会自动保存它们从命令行解析的值。这在常见情况下非常方便,尤其是在结合定义使用它们的选项文件的能力时。这称为内部存储模型。
然而,有时将命令行选项处理代码与解析值的存储分离会很不错。例如,假设我们有一个“-debug
”选项,我们希望使用它来启用整个程序主体中的调试信息。在这种情况下,控制调试代码的布尔值应该全局可访问(例如,在头文件中),但命令行选项处理代码不应该向所有这些客户端公开(需要大量 .cpp 文件来 #include CommandLine.h
)。
要做到这一点,请使用您的选项设置您的 .h 文件,例如
// DebugFlag.h - Get access to the '-debug' command line option
//
// DebugFlag - This boolean is set to true if the '-debug' command line option
// is specified. This should probably not be referenced directly, instead, use
// the DEBUG macro below.
//
extern bool DebugFlag;
// DEBUG macro - This macro should be used by code to emit debug information.
// In the '-debug' option is specified on the command line, and if this is a
// debug build, then the code specified as the option to the macro will be
// executed. Otherwise it will not be.
#ifdef NDEBUG
#define LLVM_DEBUG(X)
#else
#define LLVM_DEBUG(X) do { if (DebugFlag) { X; } } while (0)
#endif
这允许客户端愉快地使用 LLVM_DEBUG()
宏,或者如果他们想要的话,显式使用 DebugFlag
。现在我们只需要能够在设置选项时设置 DebugFlag
布尔值。为此,我们将一个额外的参数传递给我们的命令行参数处理器,并指定使用 cl::location 属性填充的位置
bool DebugFlag; // the actual value
static cl::opt<bool, true> // The parser
Debug("debug", cl::desc("Enable debug output"), cl::Hidden, cl::location(DebugFlag));
在上面的示例中,我们将“true
”作为 cl::opt 模板的第二个参数,指示模板本身不应维护值的副本。除此之外,我们还指定了 cl::location 属性,以便自动设置 DebugFlag
。
选项属性¶
本节描述了您可以在选项上指定的基本属性。
选项名称属性(对于所有选项都是必需的,除了 位置选项)指定选项名称是什么。此选项以简单的双引号指定
cl::opt<bool> Quiet("quiet");
cl::desc 属性为选项指定一个描述,该描述将在程序的
-help
输出中显示。此属性支持多行描述,行以“n”分隔。
cl::value_desc 属性指定一个字符串,可用于微调命令行选项的
-help
输出。查看 此处 以获取示例。
cl::init 属性为 标量 选项指定初始值。如果未指定此属性,则命令行选项值默认为该类型默认构造函数创建的值。
警告
如果为一个选项同时指定了 cl::init 和 cl::location,则必须先指定 cl::location,以便当命令行解析器看到 cl::init 时,它知道将初始值放在哪里。(如果未按正确的顺序放置它们,您将在运行时收到错误。)
如果使用外部存储,则 cl::location 属性指定存储解析的命令行选项值的位置。有关更多信息,请参阅关于 内部存储与外部存储 的部分。
cl::aliasopt 属性指定 cl::alias 选项是哪个选项的别名。
cl::values 属性指定通用解析器要使用的字符串到值的映射。它采用 (选项、值、描述) 三元组列表,这些三元组指定选项名称、映射到的值以及在工具的
-help
中显示的描述。由于通用解析器最常与枚举值一起使用,因此两个宏通常很有用clEnumVal 宏用作指定枚举三元组的一种简单方法。此宏会自动使选项名称与枚举名称相同。宏的第一个选项是枚举,第二个是命令行选项的描述。
clEnumValN 宏用于指定选项名称不等于枚举名称的宏选项。对于此宏,第一个参数是枚举值,第二个是标志名称,第二个是描述。
如果您尝试将 cl::values 与不支持它的解析器一起使用,则会收到编译时错误。
cl::multi_val 属性指定此选项采用多个值(例如:
-sectalign segname sectname sectvalue
)。此属性采用一个无符号参数 - 选项的值数。此属性仅在cl::list
选项上有效(如果您尝试将其与其他选项类型一起使用,则会失败并出现编译错误)。允许在多值选项上使用所有常用的修饰符(除了cl::ValueDisallowed
,显然)。
cl::cat 属性指定选项所属的选项类别。类别应为 cl::OptionCategory 对象。
cl::callback 属性指定一个回调函数,当看到选项时调用该函数,可用于设置其他选项,如选项 B 意味着选项 A。如果选项是 cl::list,并且还指定了 cl::CommaSeparated,则回调将为每个值触发一次。这可用于验证组合或选择性地设置其他选项。
cl::opt<bool> OptA("a", cl::desc("option a")); cl::opt<bool> OptB( "b", cl::desc("option b -- This option turns on option a"), cl::callback([&](const bool &) { OptA = true; })); cl::list<std::string, cl::list<std::string>> List( "list", cl::desc("option list -- This option turns on options a when " "'foo' is included in list"), cl::CommaSeparated, cl::callback([&](const std::string &Str) { if (Str == "foo") OptA = true; }));
选项修饰符¶
选项修饰符是您传递给 cl::opt 和 cl::list 构造函数的标志和表达式。这些修饰符使您能够调整选项的解析方式以及 -help
输出的生成方式,以使其很好地适合您的应用程序。
这些选项分为五个主要类别
隐藏
-help
输出中的选项控制所需和允许出现的次数
控制是否必须指定值
控制其他格式选项
其他选项修饰符
不可能为单个选项指定来自同一类别的两个选项(您将收到运行时错误),除了杂项类别中的选项。CommandLine 库为所有这些设置指定了在实践中最有用和最常见的默认值,这意味着您通常不必担心这些。
隐藏 -help
输出中的选项¶
cl::NotHidden
、cl::Hidden
和 cl::ReallyHidden
修饰符用于控制选项是否出现在编译程序的 -help
和 -help-hidden
输出中
控制所需和允许的出现次数¶
此选项组用于控制在程序的命令行上允许(或要求)指定选项的次数。为该设置指定一个值允许 CommandLine 库为您执行错误检查。
此选项组的允许值为
cl::ZeroOrMore 修饰符(这是 cl::list 类的默认值)表示您的程序允许指定零次或多次选项。
cl::Required 修饰符表示必须精确指定一次指定的选项。
cl::OneOrMore 修饰符表示必须至少指定一次选项。
cl::ConsumeAfter 修饰符在 位置参数部分 中进行了描述。
如果未指定选项,则选项的值等于 cl::init 属性指定的值。如果未指定 cl::init
属性,则选项值将使用数据类型的默认构造函数进行初始化。
如果为 cl::opt 类类型的选项多次指定了一个选项,则只会保留最后一个值。
控制是否必须指定值¶
此选项组用于控制选项是否允许存在值。在 CommandLine 库的情况下,值要么使用等号指定(例如“-index-depth=17
”),要么作为尾随字符串指定(例如“-o a.out
”)。
此选项组的允许值为
cl::ValueOptional 修饰符(这是
bool
类型选项的默认值)指定存在或不存在值都是可以接受的。布尔参数可以通过出现在命令行中来启用,或者可以具有显式的“-foo=true
”。如果使用此模式指定选项,则不允许在没有等号的情况下提供值。因此,“-foo true
”是非法的。要获得此行为,必须使用 cl::ValueRequired 修饰符。
cl::ValueRequired 修饰符(这是除 使用通用解析器的未命名备选方案 外的所有其他类型的默认值)指定必须提供值。此模式通知命令行库,如果未为选项提供等号,则提供的下一个参数必须是值。这允许诸如“
-o a.out
”之类的东西正常工作。
cl::ValueDisallowed 修饰符(它是 使用通用解析器命名备选方案的默认值)表示用户指定值是运行时错误。这可以用来禁止用户为布尔选项提供值(例如 ‘
-foo=true
’)。
通常,此选项组的默认值的工作方式与您期望的一样。如上所述,您可以将 cl::ValueDisallowed 修饰符指定给布尔参数以限制您的命令行解析器。这些选项在 扩展库 时最有用。
控制其他格式选项¶
格式选项组用于指定命令行选项具有特殊功能,并且与其他命令行参数不同。像往常一样,您最多只能指定这些参数中的一个。
cl::NormalFormatting 修饰符(它是所有选项的默认值)指定此选项是“正常的”。
cl::Positional 修饰符指定这是一个位置参数,没有与之关联的命令行选项。有关更多信息,请参见 位置参数 部分。
cl::ConsumeAfter 修饰符指定此选项用于捕获“解释器样式”参数。请参阅 此部分以获取更多信息。
cl::Prefix 修饰符指定此选项为其值添加前缀。对于“Prefix”选项,等号不会将值与指定的选项名称分开。相反,值是前缀之后的所有内容,包括任何存在的等号。这对于处理链接器工具中的
-lmalloc
和-L/usr/lib
或编译器工具中的-DNAME=value
等奇数参数非常有用。在这里,'l
'、'D
' 和 'L
' 选项是普通的字符串(或列表)选项,添加了 cl::Prefix 修饰符以允许 CommandLine 库识别它们。请注意,cl::Prefix 选项不得指定 cl::ValueDisallowed 修饰符。
控制选项分组¶
cl::Grouping 修饰符可以与除 cl::Positional 之外的任何格式类型组合使用。它用于实现具有许多单字母参数但只需要单个破折号的 Unix 风格工具(如 ls
)。例如,' ls -labF
' 命令实际上启用了四个不同的选项,所有这些选项都是单字母的。
请注意,cl::Grouping 选项只有在单独使用或位于组的末尾时才能具有值。对于 cl::ValueRequired,如果此类选项在组中的其他位置使用,则会发生运行时错误。
CommandLine 库不限制您如何使用 cl::Prefix 或 cl::Grouping 修饰符,但可以指定模棱两可的参数设置。因此,可以有多个字母选项是前缀或分组选项,它们仍将按设计工作。
为此,CommandLine 库使用贪婪算法将输入选项解析为(可能是多个)前缀和分组选项。该策略基本上如下所示
parse(string OrigInput) {
1. string Input = OrigInput;
2. if (isOption(Input)) return getOption(Input).parse(); // Normal option
3. while (!Input.empty() && !isOption(Input)) Input.pop_back(); // Remove the last letter
4. while (!Input.empty()) {
string MaybeValue = OrigInput.substr(Input.length())
if (getOption(Input).isPrefix())
return getOption(Input).parse(MaybeValue)
if (!MaybeValue.empty() && MaybeValue[0] == '=')
return getOption(Input).parse(MaybeValue.substr(1))
if (!getOption(Input).isGrouping())
return error()
getOption(Input).parse()
Input = OrigInput = MaybeValue
while (!Input.empty() && !isOption(Input)) Input.pop_back();
if (!Input.empty() && !getOption(Input).isGrouping())
return error()
}
5. if (!OrigInput.empty()) error();
}
其他选项修饰符¶
其他选项修饰符是唯一可以指定多个标志的标志:它们不是互斥的。这些标志指定修改选项的布尔属性。
cl::CommaSeparated 修饰符表示为选项值指定的任何逗号都应用于将值拆分为选项的多个值。例如,当指定
cl::CommaSeparated
时,这两个选项是等效的:“-foo=a -foo=b -foo=c
” 和 “-foo=a,b,c
”。此选项仅在允许选项接受一个或多个值(即它是 cl::list 选项)的情况下才有意义。
cl::DefaultOption 修饰符用于指定该选项是默认选项,可以由应用程序特定的解析器覆盖。例如,
-help
别名-h
就是这样注册的,因此它可以被需要将-h
选项用于其他目的的应用程序覆盖,无论是作为常规选项还是其他选项的别名。
cl::PositionalEatsArgs 修饰符(仅适用于位置参数,并且仅对列表有意义)指示位置参数应消耗其后的任何字符串(包括以“ - ”开头的字符串),直到另一个识别的位置参数。例如,如果您有两个“吞噬”位置参数,“
pos1
” 和 “pos2
”,则字符串 “-pos1 -foo -bar baz -pos2 -bork
” 将导致字符串 “-foo -bar -baz
” 应用于 “-pos1
” 选项,而字符串 “-bork
” 应用于 “-pos2
” 选项。
cl::Sink 修饰符用于处理未知选项。如果至少有一个选项指定了
cl::Sink
修饰符,则解析器会将无法识别的选项字符串作为值传递给它,而不是发出错误信号。与cl::CommaSeparated
一样,此修饰符仅对 cl::list 选项有意义。
响应文件¶
某些系统(例如某些版本的 Microsoft Windows 和一些较旧的 Unix)对命令行长度有相对较低的限制。因此,通常使用所谓的“响应文件”来规避此限制。这些文件在命令行上提及(使用“@file”语法)。程序读取这些文件并将内容插入到 argv 中,从而绕过命令行长度限制。
顶级类和函数¶
尽管具有所有内置的灵活性,但 CommandLine 选项库实际上只包含一个函数 cl::ParseCommandLineOptions 和三个主要类:cl::opt、cl::list 和 cl::alias。本节详细描述了这三个类。
cl::getRegisteredOptions
函数¶
cl::getRegisteredOptions
函数旨在让程序员访问已声明的非位置命令行选项,以便在调用 cl::ParseCommandLineOptions 之前修改它们在 -help
中的显示方式。请注意,此方法不应在任何静态初始化期间调用,因为无法保证所有选项都已初始化。因此,应从 main
中调用它。
此函数可用于访问工具编写者可能无法直接访问的库中声明的选项。
该函数检索一个 StringMap,该映射将选项字符串(例如 -help
)映射到 Option*
。
以下是如何使用该函数的示例
using namespace llvm;
int main(int argc, char **argv) {
cl::OptionCategory AnotherCategory("Some options");
StringMap<cl::Option*> &Map = cl::getRegisteredOptions();
//Unhide useful option and put it in a different category
assert(Map.count("print-all-options") > 0);
Map["print-all-options"]->setHiddenFlag(cl::NotHidden);
Map["print-all-options"]->setCategory(AnotherCategory);
//Hide an option we don't want to see
assert(Map.count("enable-no-infs-fp-math") > 0);
Map["enable-no-infs-fp-math"]->setHiddenFlag(cl::Hidden);
//Change --version to --show-version
assert(Map.count("version") > 0);
Map["version"]->setArgStr("show-version");
//Change --help description
assert(Map.count("help") > 0);
Map["help"]->setDescription("Shows help");
cl::ParseCommandLineOptions(argc, argv, "This is a small program to demo the LLVM CommandLine API");
...
}
cl::ParseCommandLineOptions
函数¶
cl::ParseCommandLineOptions
函数旨在直接从 main
中调用,用于在 argc
和 argv
可用后填充所有命令行选项变量的值。
cl::ParseCommandLineOptions
函数需要两个参数(argc
和 argv
),但也可以采用可选的第三个参数,该参数保存 其他额外文本 以在调用 -help
选项时发出。
cl::SetVersionPrinter
函数¶
cl::SetVersionPrinter
函数旨在直接从 main
中调用,并且在 cl::ParseCommandLineOptions
之前调用。其使用是可选的。它只是安排一个函数响应 --version
选项被调用,而不是让 CommandLine
库打印出 LLVM 的常规版本字符串。这对于不是 LLVM 的一部分但希望使用 CommandLine
功能的程序很有用。此类程序应该只定义一个不带参数并返回 void
的小函数,并打印出适合该程序的任何版本信息。将该函数的地址传递给 cl::SetVersionPrinter
以安排在用户给出 --version
选项时调用它。
cl::opt
类¶
cl::opt
类用于表示标量命令行选项,并且是大多数时候使用的类。它是一个模板类,最多可以接受三个参数(除了第一个参数外,其他参数都有默认值)
namespace cl {
template <class DataType, bool ExternalStorage = false,
class ParserClass = parser<DataType> >
class opt;
}
第一个模板参数指定命令行参数的底层数据类型,并用于选择默认的解析器实现。第二个模板参数用于指定选项是否应包含选项的存储空间(默认值),或者是否应使用外部存储空间来保存为选项解析的值(有关更多信息,请参阅内部存储与外部存储)。
第三个模板参数指定要使用的解析器。默认值根据选项的底层数据类型选择parser
类的实例化。通常,此默认值适用于大多数应用程序,因此此选项仅在使用自定义解析器时使用。
cl::list
类¶
cl::list
类用于表示命令行选项列表。它也是一个模板类,最多可以接受三个参数。
namespace cl {
template <class DataType, class Storage = bool,
class ParserClass = parser<DataType> >
class list;
}
此类的工作方式与cl::opt类完全相同,只是第二个参数是外部存储的**类型**,而不是布尔值。对于此类,标记类型“bool
”用于指示应使用内部存储。
cl::bits
类¶
cl::bits
类用于表示以位向量形式表示的命令行选项列表。它也是一个模板类,最多可以接受三个参数。
namespace cl {
template <class DataType, class Storage = bool,
class ParserClass = parser<DataType> >
class bits;
}
此类的工作方式与cl::list类完全相同,只是如果使用外部存储,则第二个参数必须为**类型**unsigned
。
cl::alias
类¶
cl::alias
类是一个非模板类,用于为其他参数创建别名。
namespace cl {
class alias;
}
cl::aliasopt属性应用于指定此别名指向哪个选项。别名参数默认为cl::Hidden,并使用被别名化的选项解析器将字符串转换为数据。
cl::extrahelp
类¶
cl::extrahelp
类是一个非模板类,允许为-help
选项打印额外的帮助文本。
namespace cl {
struct extrahelp;
}
要使用extrahelp,只需使用const char*
参数构造一个对象即可。传递给构造函数的文本将在帮助消息的底部逐字打印。请注意,可以多次使用cl::extrahelp
,但不鼓励这样做。如果您的工具需要打印其他帮助信息,请将所有这些帮助信息放入单个cl::extrahelp
实例中。
例如
cl::extrahelp("\nADDITIONAL HELP:\n\n This is the extra help\n");
cl::OptionCategory
类¶
cl::OptionCategory
类是一个用于声明选项类别的简单类。
namespace cl {
class OptionCategory;
}
选项类别必须具有名称,并且可以选择具有描述,这些名称作为const char*
传递给构造函数。
请注意,在解析选项之前(例如,静态地)声明选项类别并将其与选项关联将更改-help
的输出,从未分类更改为已分类。如果声明了选项类别但未将其与选项关联,则它将从-help
的输出中隐藏。
内置解析器¶
解析器控制如何将从命令行获取的字符串值转换为类型化值,以便在C++程序中使用。默认情况下,CommandLine库使用parser<type>
的实例,如果命令行选项指定它使用“type
”类型的值。因此,自定义选项处理是通过“parser
”类的专门化来指定的。
CommandLine库提供了以下内置解析器专门化,这对于大多数应用程序来说已经足够了。但是,它也可以扩展到与新的数据类型和解释相同数据的新方法一起使用。有关此类库扩展的更多详细信息,请参阅编写自定义解析器。
通用
parser<t>
解析器可用于通过使用cl::values属性(指定映射信息)将字符串值映射到任何数据类型。此解析器的最常见用途是解析枚举值,这允许您将CommandLine库用于所有错误检查,以确保仅指定有效的枚举值(而不是接受任意字符串)。尽管如此,通用解析器类仍可用于任何数据类型。
**parser<bool>专门化**用于将布尔字符串转换为布尔值。当前接受的字符串为“
true
”、“TRUE
”、“True
”、“1
”、“false
”、“FALSE
”、“False
”和“0
”。**parser<boolOrDefault>专门化**用于值是布尔值,但我们也需要知道选项是否被指定的情况。boolOrDefault是一个具有3个值的枚举,BOU_UNSET、BOU_TRUE和BOU_FALSE。此解析器接受与**``parser<bool>``**相同的字符串。
**parser<string>专门化**仅将解析的字符串存储到指定的字符串值中。不执行数据的转换或修改。
**parser<int>专门化**使用C
strtol
函数来解析字符串输入。因此,它将接受十进制数(带有可选的“+”或“-”前缀),该数必须以非零数字开头。它接受八进制数,八进制数以“0
”前缀数字标识,并接受以“0x
”或“0X
”为前缀的十六进制数。
**parser<double>**和**parser<float>专门化**使用标准C
strtod
函数将浮点数字符串转换为浮点值。因此,支持各种字符串格式,包括指数表示法(例如:1.7e15
)并正确支持区域设置。
扩展指南¶
尽管CommandLine库已经内置了许多功能(如前所述),但它的真正优势在于其可扩展性。本节讨论了CommandLine库在幕后如何工作,并说明了如何进行一些简单、常见、扩展。
编写自定义解析器¶
最简单和最常见的扩展之一是使用自定义解析器。如前面所述,解析器是CommandLine库的一部分,它将用户的字符串输入转换为特定的解析数据类型,并在过程中验证输入。
有两种方法可以使用新解析器
为您的自定义数据类型专门化cl::parser模板。
这种方法的优点是,每当用户使用您的自定义数据类型定义选项时,他们都会自动使用您的自定义解析器。这种方法的缺点是,如果您的基本数据类型是已经支持的类型,则它不起作用。
编写一个独立的类,并从需要它的选项中显式使用它。
这种方法适用于您希望使用特殊语法为不太特殊的类型解析选项的情况。这种方法的缺点是,解析器的用户必须知道他们正在使用您的解析器而不是内置解析器。
为了指导讨论,我们将讨论一个接受文件大小的自定义解析器,并在数字大小之后使用可选的单位指定。例如,我们希望将“102kb”、“41M”、“1G”解析为相应的整数值。在这种情况下,我们想要解析到的底层数据类型是“unsigned
”。我们选择上述方法#2,因为我们不希望将其设为所有unsigned
选项的默认值。
首先,我们声明新的FileSizeParser
类
struct FileSizeParser : public cl::parser<unsigned> {
// parse - Return true on error.
bool parse(cl::Option &O, StringRef ArgName, const std::string &ArgValue,
unsigned &Val);
};
我们的新类继承自cl::parser
模板类,以便为我们填充默认的样板代码。我们为它提供要解析到的数据类型,即parse
方法的最后一个参数,以便我们的自定义解析器的客户端知道要将什么对象类型传递到parse方法中。(在这里,我们声明我们将解析到“unsigned
”变量中。)
对于大多数目的,自定义解析器中必须实现的唯一方法是parse
方法。parse
方法在每次调用选项时都会被调用,并将选项本身、选项名称、要解析的字符串以及对返回值的引用作为参数传递。如果要解析的字符串格式不正确,则解析器应输出错误消息并返回true。否则,它应返回false并将“Val
”设置为解析的值。在我们的示例中,我们实现parse
如下
bool FileSizeParser::parse(cl::Option &O, StringRef ArgName,
const std::string &Arg, unsigned &Val) {
const char *ArgStart = Arg.c_str();
char *End;
// Parse integer part, leaving 'End' pointing to the first non-integer char
Val = (unsigned)strtol(ArgStart, &End, 0);
while (1) {
switch (*End++) {
case 0: return false; // No error
case 'i': // Ignore the 'i' in KiB if people use that
case 'b': case 'B': // Ignore B suffix
break;
case 'g': case 'G': Val *= 1024*1024*1024; break;
case 'm': case 'M': Val *= 1024*1024; break;
case 'k': case 'K': Val *= 1024; break;
default:
// Print an error message if unrecognized character!
return O.error("'" + Arg + "' value invalid for file size argument!");
}
}
}
此函数实现了一个非常简单的解析器,用于我们感兴趣的字符串类型。虽然它有一些漏洞(例如,它允许“123KKK
”),但对于这个例子来说已经足够了。请注意,我们使用选项本身来打印错误消息(error
方法始终返回 true),以便获得友好的错误消息(如下所示)。现在我们有了解析器类,我们可以像这样使用它
static cl::opt<unsigned, false, FileSizeParser>
MFS("max-file-size", cl::desc("Maximum file size to accept"),
cl::value_desc("size"));
这会将以下内容添加到我们程序的输出中
OPTIONS:
-help - display available options (-help-hidden for more)
...
-max-file-size=<size> - Maximum file size to accept
现在我们可以测试我们的解析是否正常工作(测试程序只打印出 max-file-size 参数值)
$ ./test
MFS: 0
$ ./test -max-file-size=123MB
MFS: 128974848
$ ./test -max-file-size=3G
MFS: 3221225472
$ ./test -max-file-size=dog
-max-file-size option: 'dog' value invalid for file size argument!
看起来它工作正常。我们得到的错误消息很友好且有帮助,并且我们似乎接受了合理的的文件大小。这结束了“自定义解析器”教程。
利用外部存储¶
几个 LLVM 库定义了静态的 cl::opt
实例,这些实例将自动包含在链接到该库的任何程序中。这是一个特性。但是,有时需要在库外部知道命令行选项的值。在这些情况下,库会或应该提供一个外部存储位置,供库的用户访问。这方面的例子包括由 lib/Support/Debug.cpp
文件导出的 llvm::DebugFlag
和由 lib/IR/PassManager.cpp
文件导出的 llvm::TimePassesIsEnabled
标志。