属性是cmake的关键概念,属性会影响构建过程的方方面面,从源文件如何编译成对象文件,到二进制的安装位置,再到打包安装程序的目录等.
为了方便理解cmake中的属性,举个例子:我们可以把人当作一个对象,人有名字,性别,年龄,身高等等.这些就可以成为人这个对象的属性.
在cmake中,我们可以把各种cmake实体看作一个对象,这些对象都可以有自己的属性.cmake中的实体有目录,target,SOURCE,TEST,缓存变量等等.我们甚至可以把整个构建过程本身看作是一种cmake的实体.
有时候我们很容易把属性和变量搞混淆,属性不像变量那样持有独立性值,它提供特定于其附加实体的信息,这是属性和变量的根本区别.
签名如下:
set_property(<GLOBAL |
DIRECTORY [<dir>] |
TARGET [<target1> ...] |
SOURCE [<src1> ...]
[DIRECTORY <dirs> ...]
[TARGET_DIRECTORY <targets> ...] |
INSTALL [<file1> ...] |
TEST [<test1> ...] |
CACHE [<entry1> ...] >
[APPEND] [APPEND_STRING]
PROPERTY <name> [<value1> ...])
set_property()
命令是设置属性的通用命令,第一个参数代表的是需要设置属性的实体,从上述命令的签名可以看出,可以设置属性的实体有:
GLOBAL,DIRECTORY ,TARGET,SOURCE,INSTALL,TEST,CACHE
.
目录属性
的命令:set_directory_properties()
target
目标属性的命令:set_target_properties()
源码文件
相关的属性.如果没有跟子命令,那源码的属性只对当前目录中定义的target
可见.
target
均对设置的源码属性可见.target
目录对设置的源码属性可见.源码文件
属性的命令:set_source_files_properties()
单元测试
属性的命令:set_tests_properties()
缓存变量
APPEND
和 APPEND_STRING
这两个可选的参数会影响到属性值追加时候的行为,通过一个表格来理解.
PROPERTY
参数是必须的,用于制定需要设置的属性的名字,名字后面需要跟相应的值.
原始值 | 新的值 | 没有关键字 | APPEND | APPEND_STRING |
---|---|---|---|---|
foo | bar | bar | foo; bar | foobar |
a;b | c;d | c;d | a;b;c;d | a;b;c;d |
签名如下:
get_property(<variable>
<GLOBAL |
DIRECTORY [<dir>] |
TARGET <target> |
SOURCE <source>
[DIRECTORY <dir> | TARGET_DIRECTORY <target>] |
INSTALL <file> |
TEST <test> |
CACHE <entry> |
VARIABLE >
PROPERTY <name>
[SET | DEFINED | BRIEF_DOCS | FULL_DOCS])
可以获取的属性的实体有:
GLOBAL,DIRECTORY ,TARGET,SOURCE,INSTALL,TEST,CACHE,VARIABLE
.
除了VARIABLE
外,其他实体的含义在set_property()
命令中已经讲解. VARIABLE
实体顾名思义,就是过去附着在变量上的属性.
SET
选项,如果给定,那就是要获取的属性如果没有设置过值,则variable
被设置为真.DEFINED
选项如果给定,那要获取的属性如果没有设置过值,则variable
被设置为真.BRIEF_DOCS | FULL_DOCS
选项如果给定,那VARIABLE
被设置为要获取的属性的文档字符串.上述四个可选关键字,只有SET在实际项目中用到,其他三个几乎不用,除非明确使用define_property()
命令为某个属性设置过信息.
define_property(<GLOBAL | DIRECTORY | TARGET | SOURCE |
TEST | VARIABLE | CACHED_VARIABLE>
PROPERTY <name> [INHERITED]
[BRIEF_DOCS <brief-doc> [docs...]]
[FULL_DOCS <full-doc> [docs...]]
[INITIALIZE_FROM_VARIABLE <variable>])
全局属性与整个项目的构建相关.它们通常用于修改构建工具的启动方式或其他行为,定义项目文件的结构方式以及提供一定程度的构建信息.
在cmake的官方文档中,我们可以看到cmake预定了大量的全局属性.这些属性我们大多数情况下不会通过set_property()
命令去更改它们的值,但是在项目中有时我们会使用get_property()
命令去获取这些属性的值,帮助我们对cmake项目进行更好的组织和管理.
对于获取cmake全局属性,除了get_property()
命令,cmake还为我们提供了一个简化版本的命令:get_cmake_property()
在项目中也建议使用这个命令.
get_cmake_property(<var> <property>)
get_cmake_property()
命令可以获取任何类型的全局属性的值,同时它也能获取一些行为和全局属性类型的属性值,这些属性称为伪属性,并且伪属性的值只能通过get_cmake_property()
命令来获取.
如何使用get_cmake_property()
命令获取伪属性的值:
foreach()
命令将获取到的属性值一个一个打印出来.在这里我们可以看到以CMAKE_
开头的cmake预定义变量,以PROJECT_
开头的当调用project()
命令后自动生成的变量,以_
开头的cmake内部变量,以及以项目名字projectname
加下划线开头的和项目相关的变量.我们可以直接打印这些变量的值.COMMANDS
伪属性的子集,但请注意名称的大小写可能与COMMANDS
伪属性报告的不同.install()
命令定义的所有组件的列表.find_library()
命令是否应该自动搜索lib64目录.在cmake项目中,除了项目的顶级目录,我们还可能添加一些子目录,这些目录也是可以设置属性和获取属性的.
通过对目录设置和获取属性,可以方便我们对项目的组织和管理.
除了属性通用性的命令可以设置和获取目录属性外,cmake为我们提供了专门用于设置和获取目录属性的命令.
set_directory_properties(PROPERTIES prop1 value1 [prop2 value2] ...)
cmake设置目录属性的通用命令如下:
set_property(DIRECTORY [<dir>]
[APPEND] [APPEND_STRING]
PROPERTY <name> [value1 ...]
)
这两个命令用于设置一个目录的属性,影响该目录下的所有子目录和目标.该命令可以设置一些常用的目录属性,如编译器选项,编译器特性,链接选项等等.
例如:
set_directory_properties(PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
COMPILE_OPTIONS "-Wall;-Wextra;-pedantic")
set_directory_properties(PROPERTIES
PROPERTY CXX_STANDARD 17)
有设置目录属性就有获取目录属性的命令
get_directory_property(<variable> [DIRECTORY <dir>] <prop-name>)
get_directory_property(<variable> [DIRECTORY <dir>] DEFINITION <prop-name>)
第一种形式非常简单,对应获取由设置目录属性的命令设置的目录属性,例如:
get_directory_property(CXX_STANDAR_USED DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} CXX_STANDARD)
message("CXX_STANDARD_USED: ${CXX_STANDARD_USED}")
对于第二种形式,看上去和目录属性没太大关系,但是非常有用,比如我们在lib目录设置了一个变量,然后在app目录库使用这个变量的值,就可以使用第二种形式获取.
lib目录
cmake_minimum_required(VERSION 3.26 FATAL_ERROR)
set(LIB_VERSION "1.0.0")
app目录
cmake_minimum_required(VERSION 3.26 FATAL_ERROR)
get_directory_property(LIB_VERSION_DEFINITION DIRECTORY "${CMAKE_SOURCE_DIR}/lib" DEFINITION LIBVERSION)
mssage("Library version: ${LIB_VERSION_DEFINITION}")
cmake是一个强大的跨平台构建系统,他的主要作用就是生成Makefile 或其他构建系统所需的文件,以便于将源代码转化为可执行文件,库等.
cmake重要的概念就是Target(目标),它主要构建的文件的抽象,可以是可执行文件,库或其他类型的文件.每个Target都可以有一些属性,这些属性控制着它们的编译和链接方式,输出类型和路径等.
在cmake中,Target属性对于控制如何将源代码转化为目标文件(可执行文件,库文件或其他类型的文件)至关重要.Target属性控制了从源代码到最终输出的所有步骤,包括源代码的编译,链接器的选择和输出文件的路径等.
在cmake中,Target属性具有以下作用:
此外,Target属性还可以影响如何在开发人员的IDE项目中呈现目标,包括如何组织和显示文件,图标和其他属性.
总之,Target属性是将源代码转换为目标文件的关键,几乎涵盖了所有将源代码转化为目标文件所需的详细信息.
在cmake中,设置Target属性有多种方法.除了通用的set_property()
和 get_property()
命令之外.还提供了一些针对Target的命令,如set_target_properties()
和 get_target_properties()
.
set_target_properties()
命令用于设置Target属性,其语法:
set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
其中target1
和target2
是Target的名称,prop1
和 prop2
是Target的属性,value1
和 value2
是相应属性的值.
例如可以通过以下命令来设置目标的输出名称和输出路径:
set_target_properties(MyTarget
PROPERTIES
OUTPUT_NAME "mylib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
该命令将MyTarget目标的输出名称设置为mylib,输出路径设置为${CMAKE_BINARY_DIR}/bin
.
get_target_properties()
命令用于获取一个目标的属性值,其语法:
get_target_property(<VAR> target property)
其中,VAR
表示获取到的属性值就会存储在该变量中,target
表示需要获取属性的目标,property
表示需要获取的属性名.
例如:
可以通过下列命令获取一个目标的输出路径.
get_target_property(output_path MyTarget RUNTIME_OUTPUT_DIRECTORY)
该命令将会把MyTarget
目标的RUNTIME_OUTPUT_DIRECTORY
属性值存储到output_path
变量中.
总之,目标属性是cmake中非常重要的一部分,它们控制着目标的编译,链接,输出路径等各个方面.在编写CMakeLists.txt
文件时,需要对目标属性有一定了解,并且可以使用相关命令来获取和设置它们.
cmake支持更细颗粒度的对单个源文件进行属性设置,这些属性可以在源文件级别对编译器标志进行精细度调整,而不是对整个目标的所有源文件进行设置.
此外,属性还可以提供有关源文件的其他信息,以修改cmake或构建工具如何处理该文件.
例如:它们可以指示该文件是否作为构建的一部分生成,使用哪个编译器,与文件一起使用的非编译器工具选项等.
开发者可以使用set_source_files_properties()
和 get_source_file_property()
命令来设置和获取源文件属性.
例如:可以使用下面命令设置源文件属性,以防止将其与其他源文件组合在一个Unity Build
中.
add_executable(MyApp small.cpp tall.cpp thin.cpp)
set_source_files_properties(big.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION YES)
其中SKIP_UNITY_BUILD_INCLUSION
属性用于防止该文件被包含到Unity Build
(统一构建) 中.
此外,cmake3.18版本开始,还提供其他选项来指定应该在那个目录范围搜索或应用源文件属性.
例如:DIRECTPORY
选项可以用于指定应该设置源文件属性的一个或者多个目标,TARGET_DIRECTORY
选项可以指定要设置源文件属性的目标的名称.
set_property(<GLOBAL |
DIRECTORY [<dir>] |
TARGET [<target1> ...] |
SOURCE [<src1> ...]
[DIRECTORY <dirs> ...]
[TARGET_DIRECTORY <targets> ...] |
INSTALL [<file1> ...] |
TEST [<test1> ...] |
CACHE [<entry1> ...] >
[APPEND] [APPEND_STRING]
PROPERTY <name> [<value1> ...])
set_source_files_properties(<files> ...
[DIRECTORY <dirs> ...]
[TARGET_DIRECTORY <targets> ...]
PROPERTIES <prop1> <value1>
[<prop2> <value2>] ...)
get_property(<variable>
<GLOBAL |
DIRECTORY [<dir>] |
TARGET <target> |
SOURCE <source>
[DIRECTORY <dir> | TARGET_DIRECTORY <target>] |
INSTALL <file> |
TEST <test> |
CACHE <entry> |
VARIABLE >
PROPERTY <name>
[SET | DEFINED | BRIEF_DOCS | FULL_DOCS])
get_source_file_property(<variable> <file>
[DIRECTORY <dir> | TARGET_DIRECTORY <target>]
<property>)
需要注意的是,对于一些cmake生成器(特别是Unix Makefiles生成器),源文件和源文件属性之间的依赖关系比较强.如果使用源文件属性来修改特定源文件的编译器标志而不是整个目标的标志,那么更改源文件的编译器标志将导致所有目标源文件被重新构建,而不仅仅是受影响的源文件.
这是由于Makefile中,测试每个单独源文件的编译器标志是否发生了变化会带来很大的性能损失.因此相关的Makefile依赖关系是在目标级别实现的.
通常形况下,开发者可能会使用源文件属性将版本信息传递给一个或者两个源文件作为编译器定义.但是如上所述,使用源文件属性也可能会降低构建性能,因为这些文件不会参与Unity Build
.因此在使用源文件的时候,开发者需要注意潜在的性能问题,并考虑代替方案.
例子:
假设有一个cmake项目,其中一个src目录,其中包含一下源文件:
src/
|--main.cpp
|--helper.cpp
|--helper.h
|--utils.cpp
|utils.h
现在,我们希望在编译main.cpp时使用一个特定的编译器选项.而在编译helper.cpp时不使用该选项,在编译utils.cpp时禁止Unity构建(即不讲其与其他文件合并到一个单独的源文件中).
为了实现这些要求,我们可以在CMakeLists.txt 文件中添加一下代码:
cmake_minimum_required(VERSION 3.26 FATAL_ERROR)
peoject(MyProject)
# 添加可执行文件
add_executable(MyApp src/main.cpp src/helper.cpp src/utils.cpp)
# 为main.cpp添加一个编译器选项
set_source_files_properties(src/main.cpp PROPERTIES COMPILE_FLAGS "-o2")
# 禁用utils.cpp的Unity构建
set_source_files_properties(src/utils.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION YES)
这样,cmake将为src/main.cpp
添加 -O2
选项,而对于 src/helper.cpp
cmake将使用默认选项.而src/utils.cpp
将被标记为不参与Unity
构建.
需要注意的是,如果在项目中定义了多个目标,那么SKIP_UNITY_BUILD_INCLUSION
属性可能会影响所有使用了该文件的目标,而不仅仅是与当前目标相关的文件. 因此,需要谨慎地使用该属性.
对于大多数项目,我们都不需要这么细颗粒度的控制,所以几乎不会用到源码属性.
cmake缓存变量是用在存储用户定义的变量,可以在多次构建过程中保持不变的一种变量.cmake的缓存变量属性是针对缓存变量的属性,主要是为了影响cmake GUI和ccmake工具的使用体验,而不会直接影响构建过程.
BOOL,FILEPATH,PATH,STRING或INTERNAL
的其中一种,使用get_property()
命令和属性名TYPE可以获取变量的类型.变量的类型会影响CMake GUI
和ccmake
工具的变量呈现方式以及用于编辑其值的小部件.类型为INTERNAL
的变量将不会在GUI中显示.mark_as_advanced()
命令可以将缓存变量标记为高级变量.实际上,这只是,设置布尔类型的ADVANCED
缓存变量的属性.CMake GUI
和ccmake
工具都提供一个选项用于显示或隐藏高级缓存变量.用户还可以选择关注基本变量还是查看完整列表.set()
命令的一部分进行设置,但是也可以使用HELPSTRING
缓存变量属性进行修改或读取.帮助字符串用作cmake GUI
中的工具提示以及ccmake
工具的单行帮助提示.STRING
,则 cmake GUI
将查找名为STRINGS
的缓存变量属性.如果不为空,则该属性应为该变量的有效值列表,并且cmake GUI
将该列表中的值作为下拉框而不是任意文本输入小部件呈现该变量.在ccmake
中,按下该缓存变量上的enter键将循环显示所提供的值,注意,cmake并不强制缓存变量必须是STRINGS
属性中的值,这只是为了方便cmake GUI
和ccmake
工具的使用.总之cmake缓存变量属性是为了影响cmake GUI
和ccmake
工具的使用体验而设置的属性,它们不会直接影响构建过程,而是为了用户更方便的使用cmake.
cmake为单元测试提供了简化版本的设置和获取属性的命令,如下:
set_tests_properties(test1 [test2...]
PROPERTIES
prop1 value1
prop2 value2)
get_test_property(test property VAR)
签名和cmake target属性类似,使用方法也类似.
属性是cmake的关键部分.许多命令都有能力设置,修改或查询各种类型的属性,其中一些对项目之间的依赖关系有进一步的影响.
一般情况下,使用通用的set_property()
命令可以完全操作除了特殊的全局伪属性,这使得开发人员能够预测属性的行为.并在需要时提供灵活的APPEND功能.尽管属性特定的设置器在某些情况下更为方便.例如,允许一次设置多个属性,但是它们缺乏APPEND功能可能会导致一些项目选择仅使用set_property()
.使用属性特定命令替换属性值,而不是附加到属性值上是一个常见的错误.
对于目标属性,强烈建议使用各种target...()
命令而不是直接操作关联的目标属性.这些命令不仅操作特定的目标属性,还设置目标之间的依赖关系,以便cmake可以自动传播一些属性.
源文件属性提供; 对编程器选项等级别的细粒度控制.然而,这些属性可能会对项目的构建行为产出不良影响.特别是,当仅有少数源文件的编译选项发生更改时,一些cmake生成器可能会重构建比必须构建更多的内容.xcode生成器还有限制.无法支持特定于配置的源文件属性.