前面展示了如何定义基本目标和生成构建输出。就其本身而言,这已经很有用了,但CMake还附带了一大堆其他特性,这些特性带来了极大的灵活性和便利性。本章涵盖了CMake最基本的部分之一,即变量的使用。
4.1 基本变量
像任何计算语言一样,变量是CMake中完成事情的基石。定义变量的最基本方法是使用set()命令。普通变量可以在CMakeLists.txt文件中定义如下:
set(varName value... [PARENT_SCOPE])
变量的名称varName可以包含字母、数字和下划线,字母区分大小写。该名称也可能包含字符。/-+,但在实践中很少见到。其他字符也可以通过间接方式使用,但这些字符在正常使用中并不常见。
在CMake中,一个变量有一个特定的作用域,就像其他语言中的变量有一个特定的函数、文件等的作用域一样。变量不能在其作用域之外读取或修改。与其他语言相比,变量作用域在CMake中更灵活一些,但现在,在本章的简单示例中,将变量的作用域看作是全局的。
CMake将所有变量视为字符串。在不同的上下文中,变量可能被解释为不同的类型,但最终,它们只是字符串。当设置一个变量的值时,CMake不要求这些值被引号括起来,除非值中包含空格。如果给出了多个值,值将用分号分隔每个值连接在一起——生成的字符串是CMake如何表示列表的。下面的内容应该有助于演示该行为。
set(myVar a b c) # myVar = "a;b;c"
set(myVar a;b;c) # myVar = "a;b;c"
set(myVar "a b c") # myVar = "a b c"
set(myVar a b;c) # myVar = "a;b;c"
set(myVar a "b c") # myVar = "a;b c"
变量的值是通过${myVar}获得的,它可以在任何需要字符串或变量的地方使用。CMake特别灵活,它还可以递归地使用这个表单,或者指定要设置的另一个变量的名称。另外,CMake不需要在使用变量之前定义它们。使用未定义的变量只会导致替换空字符串,没有错误或警告,很像Unix shell脚本。
set(foo ab) # foo = "ab"
set(bar ${foo}cd) # bar = "abcd"
set(baz ${foo} cd) # baz = "ab;cd"
set(myVar ba) # myVar = "ba"
set(big "${${myVar}r}ef") # big = "${bar}ef" = "abcdef"
set(${foo} xyz) # ab = "xyz"
set(bar ${notSetVar}) # bar = ""
字符串不限于单行,它们可以包含内嵌的换行符。它们还可以包含引号,这需要用反斜杠转义。
set(myVar "goes here")
set(multiLine "First line ${myVar}
Second line with a \"quoted\" word")
结果:
如果使用CMake 3.0或更高版本,一个替代引号的方法是使用lua风格的括号语法,内容的开头用[=[,结尾用]=]标记。方括号中可以出现任意数量的=字符,甚至完全不包含=字符,但在开始和结束时必须使用相同数量的=字符。如果左括号后面紧跟着换行符,第一个换行符将被忽略,但后面的换行符不会被忽略。此外,括号内的内容没有进行进一步的转换(即没有变量替换或转义)。
# Simple multi-line content with bracket syntax,
# no = needed between the square bracket markers
set(multiLine [[
First line
Second line
]])
# Bracket syntax prevents unwanted substitution
set(shellScript [=[
#!/bin/bash
[[ -n "${USER}" ]] && echo "Have USER"
]=])
# Equivalent code without bracket syntax
set(shellScript
"#!/bin/bash
[[ -n \"\${USER}\" ]] && echo \"Have USER\"
")
如上例所示,括号语法特别适合定义Unix shell脚本之类的内容。这样的内容使用${…}语法用于自己的目的,并且经常包含引号,但是使用括号语法意味着这些东西不必转义,不像定义CMake内容的传统引号风格。在[和]标记之间使用任意数量的=字符的灵活性也意味着嵌入的方括号不会被误解为标记。
可以通过调用unset()或调用没有指定变量值的set()来取消变量的设置。以下是等价的,如果myVar不存在,则没有错误或警告:
set(myVar)
unset(myVar)
除了项目定义的供自己使用的变量之外,许多CMake命令的行为还会受到命令被调用时特定变量的值的影响。这是CMake用来定制命令行为或修改默认值的常用模式,这样它们就不必对每个命令、目标定义等都重复。每个命令的CMake参考文档通常会列出任何可能影响该命令行为的变量。
4.2 环境变量
CMake还允许使用普通变量表示法的修改形式来检索和设置环境变量的值。环境变量的值是使用特殊形式$ENV{varName}获得的,可以在任何可以使用常规 ${varName}形式的地方使用它。设置环境变量的方法与设置普通变量相同,不同之处是使用ENV{varName}而不是只使用varName作为要设置的变量。例如:
set(ENV{PATH} "$ENV{PATH}:/opt/myDir")
但是,请注意,像这样设置环境变量只会影响当前运行的CMake实例。一旦CMake运行完成,对环境变量的更改就会丢失。特别是,对环境变量的更改在构建时是不可见的。因此,像这样在CMakeLists.txt文件中设置环境变量很少有用。
4.3 缓存变量
除了上面讨论的普通变量之外,CMake还支持缓存变量。不像普通的变量,它们的生命周期被限制在CMakeLists.txt文件的处理中,缓存变量被存储在构建目录中名为CMakeCache.txt的特殊文件中,并且它们在CMake运行之间持续存在。一旦设置,缓存变量将保持设置,直到有东西显式地从缓存中删除它们。缓存变量的值是以与普通变量完全相同的方式获取的(即使用${myVar}形式),但是set()命令在设置缓存变量时是不同的:
set(varName value... CACHE type "docstring" [FORCE])
-
BOOL
缓存变量是一个布尔开关值。GUI工具使用复选框或类似的东西来表示变量。变量持有的底层字符串值将遵循CMake将布尔值表示为字符串的一种方式(ON/OFF, TRUE/FALSE, 1/0,等等)。
-
FILEPATH
缓存变量表示磁盘上文件的路径。GUI工具向用户显示一个文件对话框,用于修改变量的值。
-
PATH
与FILEPATH类似,但是GUI工具提供了一个对话框来选择目录而不是文件。
-
STRING
变量被视为任意字符串。默认情况下,GUI工具使用单行文本编辑小部件来操作变量的值。项目可以使用缓存变量属性为GUI工具提供一组预定义的值,以组合框或类似的形式呈现.
-
INTERNAL
这个变量不打算让用户使用。内部缓存变量有时用于持久地记录项目的内部信息,例如缓存密集查询或计算的结果。GUI工具不显示内部变量。
GUI工具通常使用文档字符串作为缓存变量的工具提示,或者在选择变量时作为简短的一行描述。文档字符串应该很短,由纯文本组成(即没有HTML标记等)。
设置布尔缓存变量是非常常见的需要,因此CMake专门为此目的提供了一个单独的命令。开发人员可以使用option()来代替有些冗长的set()命令:
option(optVar helpString [initialValue])
如果省略initialValue,将使用默认值OFF。如果提供,initialValue必须符合set()命令所接受的布尔值之一。为供参考,上述内容相当于:
set(optVar initialValue CACHE BOOL helpString)
与set()相比,option()命令更清楚地表达布尔缓存变量的行为,因此它通常是首选的命令。
普通变量和缓存变量之间的一个重要区别是,如果存在FORCE关键字,set()命令将只覆盖缓存变量,而普通变量的set()命令将总是覆盖预先存在的值。当用于定义缓存变量时,set()命令的行为更像set-if-notset,与option()命令一样(它没有FORCE功能)。这样做的主要原因是,缓存变量主要是作为开发人员的定制点。与其将CMakeLists.txt文件中的值硬编码为一个普通变量,不如使用一个缓存变量,这样开发者就可以在不编辑CMakeLists.txt文件的情况下重写该值。变量可以通过交互式GUI工具或脚本进行修改,而不需要更改项目本身的任何内容。
一个经常不被很好理解的观点是,普通变量和缓存变量是两个独立的东西。可以有一个普通变量和一个缓存变量具有相同的名称,但包含不同的值。在这种情况下,当使用${myVar}时,CMake将检索普通变量的值,而不是缓存变量的值,或者换句话说,普通变量优先于缓存变量。例外情况是,当设置一个缓存变量的值时,如果在调用set()之前该缓存变量不存在,或者使用了FORCE选项,那么当前范围内的任何普通变量都会被有效地移除。注意,不幸的是,这意味着在第一次和随后的CMake运行中可能会得到不同的行为,因为在第一次运行中,缓存变量将不存在,但在随后的运行中它将存在。因此,在第一次运行中,一个普通变量将被隐藏,但在随后的运行中不会。举个例子应该有助于说明这个问题。
set(myVar foo) # Local myVar
set(result ${myVar}) # result = foo
set(myVar bar CACHE STRING“”) # Cache myVar
set(result ${myVar}) # First run: result = bar
# Subsequent runs: result = foo
set(myVar fred)
set(result ${myVar}) # result = fred
第一次运行结果
第二次运行结果
粗略地说,结果的行为是${myVar}将检索分配给myVar的最后一个值,无论它是一个普通变量还是一个缓存变量。
4.4 操纵缓存变量
使用set()和option(),项目可以为开发人员构建一组有用的定制点。可以打开或关闭构建的不同部分,可以设置到外部包的路径,可以修改编译器和链接器的标志,等等。
首先需要了解操作这些变量的方法。开发人员有两种主要的方法可以实现这一点,从cmake命令行或使用GUI工具。
(1)通过命令行设置缓存值
CMake允许通过传递给CMake的命令行选项直接操作缓存变量。主要的工具是-D选项,它用于定义缓存变量的值。
cmake -D myVar:type=someValue ...
someValue将替换myVar缓存变量之前的任何值。这种行为本质上就像使用带有CACHE和FORCE选项的set()命令分配变量一样。命令行选项只需要提供一次,因为它存储在缓存中以供后续运行,因此不需要在每次运行cmake时都提供它。可以提供多个-D选项,在cmake命令行上一次设置多个变量。
当以这种方式定义缓存变量时,它们不需要在CMakeLists.txt文件中设置(即不需要相应的set()命令)。在命令行上定义的缓存变量有一个空的文档字符串。该类型也可以省略,在这种情况下,会给变量一个类似于INTERNAL的特殊类型。下面展示了通过命令行设置缓存变量的各种示例。
cmake -D foo:BOOL=ON ...
cmake -D "bar:STRING=This contains spaces" ...
cmake -D hideMe=mysteryValue ...
cmake -D helpers:FILEPATH=subdir/helpers.txt ...
cmake -D helpDir:PATH=/opt/helpThings ...
如果设置一个包含空格的缓存变量,请注意-D选项给出的整个值应该用引号括起来。
在cmake命令行中,有一种特殊情况可以处理初始声明的值,而不需要类型。如果项目的CMakeLists.txt文件然后试图设置相同的缓存变量指定一个类型的FILEPATH或路径,如果缓存变量的值是一个相对路径,CMake将把它作为相对于调用CMake的目录,并自动把它转换成一个绝对路径。这并不是特别健壮,因为cmake可以从任何目录调用,而不仅仅是构建目录。因此,建议开发人员在cmake命令行中为表示某种路径的变量指定一个变量时,始终包含一个类型。无论如何,总是在命令行上指定变量的类型是一个好习惯,这样它就可能以最合适的形式显示在GUI应用程序中。
还可以使用-U选项从缓存中删除变量,必要时可以重复该选项以删除多个变量。注意-U选项支持*和?通配符,但需要注意避免删除超出预期的内容,并使缓存处于不可构建状态。一般情况下,除非绝对确定使用的通配符是安全的,否则建议只删除不使用通配符的特定条目。
cmake -U 'help*' -U foo ...
(2)CMake GUI Tools
通过命令行设置缓存变量是自动化构建脚本和其他任何通过CMake命令驱动CMake的重要部分。然而,对于日常开发,CMake提供的GUI工具通常会提供更好的用户体验。CMake提供了两个等价的GUI工具,CMake - GUI和ccmake,它们允许开发人员交互地操作缓存变量。cmake-gui是一个功能齐全的GUI应用程序,支持所有主要的桌面平台,而ccmake使用了一个基于curses-based 的界面,它可以在纯文本环境中使用,比如通过ssh连接。它们都包含在所有平台的官方CMake发布包中。如果在Linux上使用系统提供的包,而不是官方发行版,请注意,许多发行版将cmake-gui拆分为自己的包。
用户界面如下图所示。顶部部分允许定义项目的源目录和构建目录,中间部分是可以查看和编辑缓存变量的地方,而底部是Configure和Generate按钮及其相关的日志区域。
源目录必须设置为项目源树顶部包含CMakeLists.txt文件的目录。构建目录是CMake生成所有构建输出的地方(推荐的目录布局在第二章,建立一个项目中讨论过)。对于新项目,两者都必须设置,但是对于现有项目,设置构建目录也将更新源目录,因为源位置存储在构建目录的缓存中。
在第一章“生成项目文件”中介绍了CMake的两阶段设置过程:配置和十生成。
- 在第一阶段,读取CMakeLists.txt文件,并在内存中构建项目的表示。这称为配置阶段。
- 如果配置阶段成功,那么可以执行生成阶段,在构建目录中创建构建工具的项目文件。
当从命令行运行cmake时,这两个阶段都是自动执行的,但在GUI应用程序中,它们是用Configure和Generate按钮分别触发的。每次启动配置步骤时,显示在UI中间的缓存变量都会被更新。任何新添加的变量或上次运行时更改的值都将以红色突出显示(当项目首次加载时,所有变量都将突出显示)。良好的实践是重新运行配置阶段,直到没有更改,因为这确保了更复杂的项目的健壮行为,启用一些选项可能会添加更多的选项,而这些选项可能需要另一个配置通过。一旦所有的缓存变量都显示出来,而不突出显示红色,就可以运行生成阶段了。前面的屏幕截图中的示例显示了在配置阶段运行后的典型日志输出,并且没有对任何缓存变量进行更改。
将鼠标悬停在任何缓存变量上都会显示一个工具提示,其中包含该变量的文档字符串。还可以使用Add Entry按钮手动添加新的缓存变量,这相当于使用空文档字符串发出set()命令。缓存变量可以通过Remove Entry按钮删除,尽管CMake很可能会在下次运行时重新创建该变量。
单击一个变量,就可以在一个根据变量类型定制的小部件中编辑它的值。布尔值显示为复选框,文件和路径有一个浏览文件系统按钮,字符串通常显示为文本行编辑。作为一种特殊情况,STRING类型的缓存变量可以被赋予一组值来显示在CMake GUI的组合框中,而不是显示一个简单的文本输入部件。这是通过设置一个缓存变量的string属性来实现的。
set(trafficLight Green CACHE STRING "Status of something")
set_property(CACHE trafficLight PROPERTY STRINGS Red Orange Green)
在上面的例子中,trafficLight缓存变量最初的值是Green。当用户试图修改cmake-gui中的trafficLight时,他们会看到一个包含红、橙和绿三个值的组合框,而不是一个简单的行编辑小部件,否则就会允许他们输入任意文本。请注意,在变量上设置STRINGS属性并不会阻止该变量被赋值,它只会影响cmake-gui在编辑它时使用的小部件。这个变量仍然可以通过CMakeLists.txt文件中的set()命令或其他方式(例如手动编辑CMakeCache.txt文件)来赋值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xM6UA0M0-1641806503511)(https://gitee.com/jjjstephen/myPicGo/raw/master/img/image-20220110153035666.png)]
缓存变量也可以有一个属性将其标记为高级或非高级。这也只会影响变量在CMake -gui中的显示方式,它不会以任何方式影响CMake在处理过程中如何使用这个变量。默认情况下,cmake-gui只显示非高级变量,通常只显示开发人员想查看或修改的主要变量。启用Advanced选项会显示除标记为INTERNAL之外的所有缓存变量(查看INTERNAL变量的唯一方法是使用文本编辑器编辑CMakeCache.txt文件,因为它们不打算被开发人员直接操作)。在CMakeLists.txt文件中,可以使用mark_as_advanced()命令将变量标记为高级变量:
mark_as_advanced([CLEAR|FORCE] var1 [var2...])
CLEAR关键字确保变量不被标记为高级,而FORCE关键字则确保变量被标记为高级。如果没有这两个关键字,只有在没有高级/非高级状态集的情况下,变量才会被标记为高级。
选择Grouped选项可以更容易地查看高级变量,方法是根据变量名的开头直到第一个下划线将变量分组在一起。筛选显示的变量列表的另一种方法是在Search区域中输入文本,这将导致只显示名称或值中包含指定文本的变量。
当configure阶段在一个新项目中第一次运行时,开发人员会看到一个类似于下面截图所示的对话框:
这个对话框是指定CMake生成器和工具链的地方。生成器的选择通常取决于开发人员的个人偏好,在组合框中提供了可用的选项。根据项目的不同,生成器的选择可能比组合框选项所允许的更受限制,比如项目是否依赖于生成器特定的功能。一个常见的例子是,由于苹果平台的独特功能,如代码签名和iOS/tvOS/watchOS支持,项目需要Xcode生成器。一旦为某个项目选择了某个生成器,就不能在不删除缓存并再次启动的情况下更改它,如果需要,可以从File菜单中进行此操作。
对于所呈现的工具链选项,每个选项都需要开发人员提供更多的信息。使用默认的本机编译器是普通桌面开发的常用选择,选择该选项无需进一步说明。如果需要更多的控制,开发人员可以替代原生编译器,在后续对话框中给出编译器的路径。如果有一个单独的工具链文件可用,它不仅可以用于定制编译器,还可以用于定制目标环境、编译器标志和其他各种东西。最后,为了实现最终的控制,开发人员可以指定交叉编译的全部选项,但在正常使用时不建议这样做。工具链文件可以提供相同的信息,但其优点是可以在需要时重用。
ccmake工具提供了所有与cmake-gui应用程序相同的功能,但它是通过基于文本的界面来实现的。
不像cmake-gui那样选择源文件和构建目录,源文件或构建目录必须在ccmake命令行中指定,就像cmake命令一样。
ccmake接口的一个主要缺点是,日志输出没有cmake-gui版本那么方便地捕获。它也没有提供过滤显示的变量的功能,而且编辑变量的方法也没有cmake-gui那么丰富。除此之外,当完整的cmake-gui应用程序不实用或不可用时,例如通过不支持UI转发的终端连接时,ccmake工具是一种有用的替代方法。
4.5 调试变量和诊断
当项目变得更复杂或者在调查意外行为时,在CMake运行期间打印出诊断消息和变量值会很有用。这通常使用message()命令来实现。
message([mode] msg1 [msg2]...)
如果指定了多个msg,它们将被连接到一个字符串中,没有任何分隔符。这通常不是开发人员想要的,所以更常见的用法是用引号包围消息以保留空格。可以使用变量值,并将在打印结果之前求值。例如:
set(myVar HiThere)
message("The value of myVar = ${myVar}")
这将给出以下输出:
message()命令接受一个可选的mode关键字,该关键字会影响消息的输出方式,在某些情况下还会终止构建并出现错误。可识别的模式值有:
-
STATUS
附带的信息。消息之前通常有两个连字符。
-
WARNING
CMake警告,通常在受支持的地方用红色高亮显示(CMake命令行控制台或CMake -gui日志区域)。处理将继续下去。
-
AUTHOR_WARNING
类似于WARNING,但仅在启用开发人员警告时才显示(需要在cmake命令行上使用-Wdev选项)。项目不经常使用这种特定类型的消息。
-
SEND_ERROR
指示错误消息,在受支持的地方将以红色高亮显示。处理将继续,直到配置阶段完成,但不会执行生成。这类似于允许尝试进一步处理的错误,但最终仍然表示失败。
-
FATAL_ERROR
表示硬错误。该消息将被打印出来,处理将立即停止。日志通常也会记录fatal message()命令的位置。
-
DEPRECATION
用于记录弃用消息的特殊类别。如果CMAKE_ERROR_DEPRECATED变量被定义为布尔值true,则该消息将被视为错误。如果CMAKE_WARN_DEPRECATED被定义为布尔值true,则该消息将被视为警告。如果两个变量都没有定义,则不会显示消息。
如果没有提供mode关键字,则认为该消息是重要的信息,不做任何修改就将其记录下来。但是,应该注意的是,使用STATUS模式记录日志与完全不使用mode关键字记录消息是不同的。当使用 STATUS模式下,消息将被正确打印命令与其他CMake消息之前,然而,如果没有任何mode关键字,没有前置的连字符,这是不寻常的消息出现顺序相对于其他消息,包括一个mode关键字。
CMake提供的另一个帮助调试变量使用的机制是variable_watch()命令。这适用于更复杂的项目,在这些项目中,可能不清楚变量是如何得到特定值的。当一个变量被监视时,所有读取或修改它的尝试都会被记录下来。
variable_watch(myVar [command])
对于绝大多数情况,只列出要监视的变量,而不使用可选命令就足够了,因为它会记录对指定变量的所有访问。然而,对于最终的控制,可以给出一个命令,在每次读取或修改变量时执行该命令。这个命令应该是一个CMake函数或宏的名称,它将被传递以下参数:变量名,访问类型,变量的值,当前列表文件的名称和列表文件堆栈。不过,用variable_watch()指定命令是非常少见的。
4.6 字符串Handling
随着项目复杂性的增加,在许多情况下,也需要为如何管理变量实现更复杂的逻辑。CMake的一个核心工具是string()命令,它提供了广泛的有用的字符串处理功能。该命令允许项目执行查找和替换操作、正则表达式匹配、大小写转换、条带空格和其他常见任务。下面介绍了一些更常用的功能,但应该将CMake参考文档视为所有可用操作及其行为的规范来源。
string()的第一个参数定义要执行的操作,随后的参数取决于所请求的操作。这些参数通常需要至少一个输入字符串,因为CMake命令不能返回值,即操作结果的输出变量。在下面的材料中,这个输出变量通常被命名为outVar。
string(FIND inputString subString outVar [REVERSE])
FIND在inputString中搜索subString,并将找到的subString的索引存储在outVar中(第一个字符是索引0)。除非指定了REVERSE,否则第一个匹配项将被找到,在这种情况下,最后一个匹配项将被找到。如果subString没有出现在inputString中,那么outVar将被赋值为-1。
set(longStr abcdefabcdef)
set(shortBit def)
string(FIND ${longStr} ${shortBit} fwdIndex)
string(FIND ${longStr} ${shortBit} revIndex REVERSE)
message("fwdIndex = ${fwdIndex}, revIndex = ${revIndex}")
替换一个简单的子字符串遵循类似的模式:
string(REPLACE matchString replaceWith outVar input [input...])
REPLACE操作将用replaceWith替换输入字符串中每次出现的matchString,并将结果存储在outVar中。当给出多个输入字符串时,在搜索替换之前,它们将被连接在一起,每个字符串之间不需要任何分隔符。这有时会导致意想不到的匹配,在大多数情况下,开发人员通常只提供一个输入字符串。
正则表达式也被REGEX操作很好地支持,由第二个参数决定有几个不同的变量可用:
string(REGEX MATCH regex outVar input [input...])
string(REGEX MATCHALL regex outVar input [input...])
string(REGEX REPLACE regex replaceWith outVar input [input...])
要匹配的正则表达式regex可以使用典型的基本正则表达式语法(完整规范请参阅CMake参考文档),尽管不支持一些常见的特性,如negation。在替换之前,输入字符串被连接起来。MATCH操作只找到第一个匹配项,并将其存储在outVar中。MATCHALL查找所有匹配,并将它们存储在outVar的列表中。REPLACE将返回整个输入字符串,每个匹配项都被替换为replaceWith。匹配可以在replaceWith中引用,使用通常的符号\1,\2等,但请注意,在CMake中反斜杠本身必须转义。下面的例子演示了所需的语法:
set(longStr abcdefabcdef)
string(REGEX MATCHALL "[ace]" matchVar ${longStr})
string(REGEX REPLACE "([de])" "X\\1Y" replVar ${longStr})
message("matchVar = ${matchVar}")
message("replVar = ${replVar}")
提取子字符串也是可能的:
string(SUBSTRING input index length outVar)
索引是一个整数,定义了要从输入中提取的子字符串的开始。将提取最多长度的字符,或者如果length为-1,则返回的子字符串将包含输入字符串结束之前的所有字符。请注意,在CMake 3.1和更早的版本中,如果长度指向字符串的末尾,则会报错。
字符串长度可以很容易地获得,字符串可以很容易地转换为大写或小写。从字符串的开头和结尾去掉空格也很简单。这些操作的语法都具有相同的形式:
string(LENGTH input outVar)
string(TOLOWER input outVar)
string(TOUPPER input outVar)
string(STRIP input outVar)
CMake提供了其他操作,如字符串比较、哈希、时间戳等,但它们在日常CMake项目中的使用并不常见。
4.7 Lists
列表在CMake中被大量使用。最终,列表只是一个由分号分隔的列表项的字符串,这使得操作单个列表项不太方便。CMake提供了list()命令来简化这些任务。像string()命令一样,list()期望操作作为它的第一个参数执行。第二个参数总是要操作的列表,它必须是一个变量(例如,不允许传递一个原始列表,如a;b;c)。
最基本的列表操作是计算条目的数量,并从列表中检索一个或多个条目:
list(LENGTH listVar outVar)
list(GET listVar index [index...] outVar)
list(LENGTH listVar outVar)
list(GET listVar index [index...] outVar)
追加和插入项也是一个常见的任务:
list(APPEND listVar item [item...])
list(INSERT listVar index item [item...])
与LENGTH和GET不同,APPEND和INSERT直接作用于listVar并就地修改它,如下例所示:
set(myList a b c)
list(APPEND myList d e f)
message("myList (first) = ${myList}")
list(INSERT myList 2 X Y Z)
message("myList (second) = ${myList}")
在列表中找到一个特定的项目遵循预期的模式:
list(FIND myList value outVar)
# Example
set(myList a b c d e)
list(FIND myList d index)
message("index = ${index}")
提供了三种移除项的操作,它们都直接修改列表:
list(REMOVE_ITEM myList value [value...])
list(REMOVE_AT myList index [index...])
list(REMOVE_DUPLICATES myList)
REMOVE_ITEM操作可用于从列表中删除一个或多个项目。如果该项不在列表中,则不是错误。另一方面,REMOVE_AT指定一个或多个要删除的索引,如果指定的任何一个索引超过了列表的末尾,CMake将会停止并出错。REMOVE_DUPLICATES将确保列表中只包含唯一的项。
列表项也可以通过REVERSE或SORT操作重新排序(排序按字母顺序):
list(REVERSE myList)
list(SORT myList)
对于所有接受索引作为输入的列表操作,索引可以为负值,表示从列表的末尾开始计数,而不是从开始计数。当使用这种方式时,列表中的最后一项的索引是-1,倒数第二项的索引是-2,以此类推。
上面描述了大多数可用的list()子命令。上面提到的这些都至少从CMake 3.0开始就被支持了,所以项目通常应该能够期望它们可用。对于支持的子命令的完整列表,包括那些在以后的CMake版本中添加的,读者应该查阅CMake文档。
4.8 Math
变量操作的另一种常见形式是数学计算。CMake提供了math()命令来执行基本的数学计算:
math(EXPR outVar mathExpr)
第一个参数必须由关键字EXPR指定,而mathxpr定义要求值的表达式,结果将存储在outVar中。该表达式可以使用以下任何操作符,它们与C代码中的含义相同:+ - * / % | & ^ ~ << >> * / %。括号也被支持,并具有通常的数学含义。在mathxpr中,变量可以用通常的${myVar}符号来引用。
set(x 3)
set(y 7)
math(EXPR z "(${x}+${y}) / 2")
message("result = ${z}")
文章涉及的代码,请参考:https://gitee.com/jiangli01/cmake-learning
更多请关注微信公众号【Hope Hut】: