属性几乎影响构建过程的所有方面,从源文件如何编译成目标文件,一直到构建的二进制文件在打包安装程序中的安装位置。它们总是附加到特定的实体上,无论是目录、目标、源文件、测试用例、缓存变量,还是整个构建过程本身。属性不像变量那样持有独立的值,它提供特定于它所附加的实体的信息。
对于那些刚接触CMake的人来说,属性有时会和变量混淆。虽然在功能和特征方面,这两者最初看起来很相似,但属性的作用却截然不同。变量不附加到任何特定的实体,项目定义和使用自己的变量是很常见的。与此相比,CMake通常很好地定义和记录了属性,并且总是适用于特定的实体。造成两者之间混淆的一个可能原因是,属性的默认值有时是由变量提供的。CMake用于相关属性和变量的名称通常遵循相同的模式,即变量名是带有CMAKE_前缀的属性名。
CMake提供了许多用于操作属性的命令。其中最通用的set_property()和get_property()允许在任何类型的实体上设置和获取任何属性。这些命令要求将实体的类型指定为命令参数以及一些特定于实体的信息。
set_property(entitySpecific
[APPEND] [APPEND_STRING]
PROPERTY propName [value1 [value2 [...]]])
entitySpecific定义被设置属性的实体。它必须是以下其中之一:
GLOBAL
DIRECTORY [dir]
TARGET [target1 [target2 [...]]]
SOURCE [source1 [source2 [...]]]
INSTALL [file1 [file2 [...]]]
TEST [test1 [test2 [...]]]
CACHE [var1 [var2 [...]]]
上面的每个词的第一个词定义了要设置其属性的实体的类型。GLOBAL表示构建本身,因此不需要特定的实体名称。对于DIRECTORY,如果没有指定dir,则使用当前源目录。对于所有其他类型的实体,可以列出该类型的任意数量的项目。
PROPERTY关键字将所有剩余参数标记为定义属性名称及其值。propName通常会匹配CMake文档中定义的一个属性。值的含义是特定于属性的。除了CMake已经定义的属性外,项目还可以创建新的属性。这些特定于项目的属性意味着什么,以及它们如何影响构建,都取决于项目。如果选择这样做,对于项目来说,在属性名上使用一些特定于项目的前缀将是明智的,以避免与CMake或其他第三方包定义的属性的名称冲突。
APPEND和APPEND_STRING关键字可用于控制已命名属性(如果它已经有值)如何更新。在没有指定关键字的情况下,给定的值(s)将替换之前的任何值。添加关键字改变了行为(s)值附加到现有的一个,形成一个列表,而APPEND_STRING关键词需要现有价值和附加新的价值(s),连接两个字符串而不是作为一个列表(参见下面还特别注意继承属性进一步)。下表展示了它们之间的区别。
Previous Value(s) | Previous Value(s) | No Keyword | APPEND | APPEND_STRING |
---|---|---|---|---|
foo | bar | bar | foo;bar | foobar |
a;b | c;d | c;d | a;b;c;d | a;bc;d |
get_property()命令遵循类似的形式:
get_property(resultVar entitySpecific
PROPERTY propName
[DEFINED | SET | BRIEF_DOCS | FULL_DOCS])
PROPERTY关键字始终是必需的,并且始终后面跟着要检索的属性的名称。检索的结果存储在一个变量中,该变量的名称由resultVar给出。entitySpecific部分类似于set_property(),必须是以下内容之一:
GLOBAL
DIRECTORY [dir]
TARGET target
SOURCE source
INSTALL file
TEST test
CACHE var
VARIABLE
与前面一样,GLOBAL将构建作为一个整体来引用,因此不需要命名特定的实体。DIRECTORY可以带指定或不指定特定目录使用,如果没有提供目录,则假定当前源目录。对于大多数其他作用域,必须命名该作用域中的特定实体,并检索附加到该实体的请求属性。
VARIABLE类型有点不同,变量名被指定为propName,而不是附加到VARIABLE关键字。这看起来有点不直观,但是考虑一下如果变量与VARIABLE关键字一起被命名为实体的情况,就像其他实体类型关键字一样。在这种情况下,没有任何东西可以指定属性名。将VARIABLE看作指定当前作用域可能会有帮助,那么感兴趣的属性是由propName命名的变量。以这种方式理解时,VARIABLE与其他实体类型的处理方式是一致的。
如果没有给出任何可选关键字,则检索命名属性的值。这是get_property()命令的典型用法。注意,在实践中,在get_property()中使用VARIABLE作用域是相对不常见的。变量值可以通过${}语法直接获得,这比使用get_property()更清晰、更简单。
可选关键字可以用来检索属性的详细信息,而不仅仅是它的值:
DEFINED
检索的结果将是一个布尔值,指示是否定义了命名的属性。在VARIABLE范围查询的情况下,只有使用define_property()命令显式定义了指定的变量(见下面),结果才为真。
SET
检索的结果将是一个布尔值,指示是否设置了命名的属性。这是一个比define更强的测试,因为它测试命名属性是否实际给定了一个值(值本身是无关的)。属性可以为DEFINED返回TRUE,为SET返回FALSE,反之亦然。
BRIEF_DOCS
检索命名属性的简要文档字符串。如果没有为该属性定义简短的文档,那么结果将是字符串NOTFOUND。
FULL_DOCS
检索命名属性的完整文档。如果没有为该属性定义简短的文档,那么结果将是字符串NOTFOUND。
在可选关键字中,除非项目显式地调用define_property()来填充特定实体的请求信息,否则除了SET之外的所有关键字都没有什么价值。这个很少使用的命令有如下形式:
define_property(entityType
PROPERTY propName [INHERITED]
BRIEF_DOCS briefDoc [moreBriefDocs...]
FULL_DOCS fullDoc [moreFullDocs...])
重要的是,这个命令不设置属性的值,只设置它的文档,以及如果没有设置,是否从其他地方继承它的值。entityType必须是GLOBAL、DIRECTORY、TARGET、SOURCE、TEST、VARIABLE或CACHED_VARIABLE中的一个,而propName指定了正在定义的属性。没有指定实体,尽管像get_property()命令一样,在VARIABLE的情况下,变量名被指定为propName。简短的文档通常应该保持在一个相对较短的行,而完整的文档可以更长,如果需要可以跨多行。
如果在定义属性时使用INHERITED选项,如果该属性没有在指定作用域中设置,则get_property()命令将链接到父作用域。例如,如果请求了一个DIRECTORY属性,但没有为指定的目录设置该属性,那么它的父目录作用域的属性将沿着目录作用域层次结构递归地向上查询,直到找到该属性或到达源树的顶层。如果在顶级目录中仍然没有找到,那么将搜索GLOBAL作用域。类似地,如果请求了TARGET、SOURCE或TEST属性,但没有指定实体设置属性,则将搜索DIRECTORY作用域(包括递归地向上搜索目录层次结构,并在必要时最终搜索到GLOBAL作用域)。没有为VARIABLE或CACHE提供这样的链接功能,因为它们已经被设计成链接到父变量作用域。
INHERITED属性的继承行为只适用于特定属性类型的get_property()命令及其类似的get_…函数(将在下面的小节中介绍)。当使用APPEND或APPEND_STRING选项调用set_property()时,只考虑属性的立即值(即在计算附加值时不发生继承)。
CMake每种类型都有大量预定义的属性。开发人员应该查阅CMake参考文档,了解可用的属性及其预期用途。在后面的章节中,我们将讨论这些属性,并探讨它们与其他CMake命令、变量和特性的关系。
除了通用的set_property()和get_property()命令外,CMake还提供了get_cmake_property()来查询全局实体。它不仅仅是get_property()的缩写,尽管它可以简单地用于检索任何全局属性的值。
get_cmake_property(resultVar property)
就像get_property()一样,resultVar是一个变量的名称,当命令返回时,请求属性的值将存储在这个变量中。property参数可以是任何全局属性的名称,也可以是以下伪属性之一:
VARIABLES
返回所有常规(即非缓存)变量的列表。
CACHE_VARIABLES
返回所有缓存变量的列表。
COMMANDS
返回所有已定义命令、函数和宏的列表。命令是由CMake预先定义的,而函数和宏可以由CMake(通常通过模块)或项目本身定义。一些返回的名称可能对应于没有文档记录的或内部实体,不打算用于项目直接使用。这些名称的返回大小写可能与它们最初定义的方式不同。
MACROS
返回一个仅包含已定义宏的列表。这将是COMMANDS 伪属性将返回的内容的一个子集,但请注意,名称的大小写可能与COMMANDS 伪属性报告的内容不同。
COMPONENTS
返回由install()命令定义的所有组件的列表
这些只读的伪属性在技术上不是全局属性(例如,不能使用get_property()检索它们),但它们在概念上非常相似。它们只能通过get_cmake_property()检索。
目录还支持它们自己的属性集。从逻辑上讲,目录属性介于全局属性(适用于任何地方)和目标属性(只影响单个目标)之间。因此,目录属性主要集中于为目标属性设置默认值,并覆盖当前目录的全局属性或默认值。一些只读目录属性也提供了一定程度的内省,保存了关于构建如何到达目录、在此点上定义了什么内容等信息。
为了方便起见,CMake提供了专门的命令来设置和获取目录属性,这比通用的同类命令更简洁。setter命令的定义如下:
set_directory_properties(PROPERTIES prop1 val1 [prop2 val2 ...])
为了更简洁,这个特定于目录的setter命令没有任何APPEND或APPEND_STRING选项。这意味着它只能用于设置或替换属性,不能用于直接添加到现有属性。与更通用的set_property()相比,这个命令的另一个限制是它总是应用于当前目录。项目可以选择在方便的地方使用这种更具体的形式,并在其他地方使用通用的形式,或者为了一致性,更通用的形式可以在任何地方使用。这两种方法都不正确,这更多的是一个偏好问题。
特定于目录的getter命令有两种形式:
get_directory_property(resultVar [DIRECTORY dir] property)
get_directory_property(resultVar [DIRECTORY dir] DEFINITION varName)
第一种形式用于从特定目录获取属性的值,如果directory参数未被使用,则从当前目录获取属性的值。第二种形式获取变量的值,这可能看起来不是很有用,但它提供了一种从当前目录范围以外的其他目录范围获取变量值的方法(当使用directory参数时)。在实践中,第二种形式应该很少需要,应该避免在除调试构建或类似的临时任务之外的场景中使用它。
对于get_directory_property()命令的任何一种形式,如果使用了DIRECTORY参数,则命名的目录必须已经被CMake处理过。CMake不可能知道它还没有遇到的目录作用域的属性。
在CMake中,很少有东西能对如何将目标构建为目标属性产生如此强大而直接的影响。它们控制并提供关于从用于编译源文件的标志到构建的二进制文件和中间文件的类型和位置的所有信息。一些目标属性影响在开发人员的IDE项目中显示目标的方式,而另一些则影响编译/链接时使用的工具。简而言之,目标属性是收集和应用关于如何将源文件实际转换为二进制文件的大部分细节的地方。
为了操作目标属性,CMake中已经发展了许多方法。除了通用的set_property()和get_property()命令外,CMake还提供了一些特定于目标的等价命令:
set_target_properties(target1 [target2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ... )
get_target_property(resultVar target propertyName)
至于set_directory_properties()命令,set_target_properties()缺乏set_property()的全部灵活性,但为常见情况提供了更简单的语法。set_target_properties()命令不支持附加到现有的属性值,如果需要为给定的属性提供一个列表值,set_target_properties()命令要求该值以字符串形式指定,例如:"this;is;a;list"列表。
get_target_property()命令是get_property()的简化版本。它只关注于提供一种获取目标属性值的简单方法,基本上只是通用命令的简写版本。
除了通用的和特定于目标的属性getter和setter之外,CMake还有许多其他命令可以修改目标属性。特别是,target_…()命令家族是CMake的关键部分,除了最琐碎的CMake项目外,所有的CMake项目通常都会使用它们。这些命令不仅定义了特定目标的属性,还定义了如何将该信息传播到链接到该目标的其他目标
CMake还支持单个源文件的属性。这些支持基于文件对编译器标志的细粒度操作,而不是针对目标的所有源。他们也允许额外的信息提供源文件修改CMake或构建工具如何对待这个文件,如指示是否生成作为构建的一部分,用什么编译器,选择与编译器无关的工具处理文件等等。
项目应该很少需要查询或修改源文件属性,但是对于那些需要查询或修改源文件属性的情况,CMake提供了专门的setter和getter命令,使任务更容易。这些命令遵循与其他特定于属性的setter和getter命令类似的模式:
set_source_files_properties(file1 [file2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ... )
get_source_file_property(resultVar sourceFile propertyName)
同样,没有为setter提供APPEND功能,而getter实际上只是通用的get_property()命令的语法简写,没有提供任何新功能。
项目应该记住,源属性只对定义在相同目录范围内的目标可见。如果源属性的设置发生在不同的目录范围内,目标将不会看到该属性的更改,因此源文件的编译等也不会受到影响。还要记住,可以将一个源文件编译成多个目标,因此设置的任何源属性应该对添加源的所有目标都有意义。
在匆忙开始使用源代码属性之前,开发人员应该了解一个实现细节,这可能会在某些情况下对它们的使用产生强烈的阻碍。当使用一些CMake生成器(特别是Unix Makefiles生成器)时,源和源属性之间的依赖性比人们想象的要强。特别是,如果源属性用于修改特定源文件的编译器标志,而不是整个目标,更改源的编译器标志仍将导致重建所有目标的源文件,而不仅仅是受影响的源文件。这是在Makefile设置中如何处理依赖细节的一个限制,测试每个源文件的编译器标记是否改变会带来非常大的性能影响,所以CMake开发人员选择在目标级别实现依赖。项目可能会倾向于采用这种方法的一个典型场景是,将版本细节作为编译器定义传递给一个或两个源代码,当然,“源代码访问版本细节”,有更好的替代来源属性不遭受同样的副作用。
缓存变量上的属性与其他属性类型略有不同。在大多数情况下,缓存变量属性更多的是针对如何在CMake GUI和基于控制台的ccmake工具中处理缓存变量,而不是以任何有形的方式影响构建。也没有提供用于操作它们的额外命令,因此通用的set_property()和get_property()命令必须与CACHE关键字一起使用。
CMake还支持在单独的测试中使用属性,它提供了通常的测试特定版本的属性setter和getter命令:
set_tests_properties(test1 [test2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ... )
get_test_property(resultVar test propertyName)
与它们的对等版本一样,这些只是通用命令的更简洁的版本,它们缺乏APPEND功能,但在某些情况下可能更方便。
CMake支持的另一种属性类型是已安装文件。这些属性特定于正在使用的打包类型,大多数项目通常不需要这些属性。
相关代码:https://gitee.com/jiangli01/cmake-learning
更多请关注微信公众号【Hope Hut】: