cmake的属性

文章目录

  • cmake通用属性
    • set_property()命令 (设置属性)
    • get_property()命令 (获取属性)
    • define_property()签名:
  • cmake的全局属性
  • 目录属性
  • cmake Target属性
    • Target 属性的作用
    • Target属性的设置和获取
  • 源文件属性
  • cmake其他属性
    • cmake缓存变量属性
    • cmake单元测试属性

cmake通用属性

属性是cmake的关键概念,属性会影响构建过程的方方面面,从源文件如何编译成对象文件,到二进制的安装位置,再到打包安装程序的目录等.
为了方便理解cmake中的属性,举个例子:我们可以把人当作一个对象,人有名字,性别,年龄,身高等等.这些就可以成为人这个对象的属性.
在cmake中,我们可以把各种cmake实体看作一个对象,这些对象都可以有自己的属性.cmake中的实体有目录,target,SOURCE,TEST,缓存变量等等.我们甚至可以把整个构建过程本身看作是一种cmake的实体.
有时候我们很容易把属性和变量搞混淆,属性不像变量那样持有独立性值,它提供特定于其附加实体的信息,这是属性和变量的根本区别.

set_property()命令 (设置属性)

签名如下:

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.

  • GLOBAL 实体代表全局属性
  • DIRECTORY 实体后面可以跟一个目录(这个目录必须是cmake已知的),如果没有明确指出,则是当前目录.
    • 相关特定的与设定目录属性的命令:set_directory_properties()
  • TARGET 实体代表cmake已知的target
    • 相关特定与设置target目标属性的命令:set_target_properties()
  • SOURCE 实体代表和源码文件相关的属性.如果没有跟子命令,那源码的属性只对当前目录中定义的target可见.
    • DIRECTORY 子命令用于指定目录,在这些指定的目录中定义的target均对设置的源码属性可见.
    • TARGET_DIRETORY 子命令使得其指定的target 目录对设置的源码属性可见.
    • 相关的特定于设置源码文件属性的命令:set_source_files_properties()
  • INSTALL实体指定安装的文件路径
  • TEST实体指定单元测试
    • 相关特定于设置单元测试属性的命令:set_tests_properties()
  • CHCHE 实体指定缓存变量

APPENDAPPEND_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()命令 (获取属性)

签名如下:

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()签名:

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的官方文档中,我们可以看到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()命令获取伪属性的值:

  • VARIABLES
    • 返回所有常规(非缓存)变量的列表.因为常规变量很多,我们可以使用一个foreach() 命令将获取到的属性值一个一个打印出来.在这里我们可以看到以CMAKE_ 开头的cmake预定义变量,以PROJECT_ 开头的当调用project() 命令后自动生成的变量,以_ 开头的cmake内部变量,以及以项目名字projectname 加下划线开头的和项目相关的变量.我们可以直接打印这些变量的值.
  • CHACHE_VARIABLES
    • 返回所有缓存变量的列表.同样也有和项目相关的缓存变量.
  • COMMANDS
    • 返回所有已定义的命令,函数和宏列表.命令由cmake预先定义,而函数和宏可以由(通常通过模块)或项目本身定义.一些返回的名称可能对应未记录或不打算供项目直接使用的内部实体.这些名称可能从不同于最初定义的大小写形式返回
  • MAROS
    • 仅返回已定义宏的列表.这将是COMMANDS 伪属性的子集,但请注意名称的大小写可能与COMMANDS伪属性报告的不同.
  • COMPONENTS
    • 返回由install() 命令定义的所有组件的列表.
  • CMAKE_C_KNOWN_FEATURES
    • 获取当前cmake版本已知的C标准和特性.
  • FIND_LIBRARY_USE_LIB64_PATHS
    • 获取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 Target属性

cmake是一个强大的跨平台构建系统,他的主要作用就是生成Makefile 或其他构建系统所需的文件,以便于将源代码转化为可执行文件,库等.
cmake重要的概念就是Target(目标),它主要构建的文件的抽象,可以是可执行文件,库或其他类型的文件.每个Target都可以有一些属性,这些属性控制着它们的编译和链接方式,输出类型和路径等.

Target 属性的作用

在cmake中,Target属性对于控制如何将源代码转化为目标文件(可执行文件,库文件或其他类型的文件)至关重要.Target属性控制了从源代码到最终输出的所有步骤,包括源代码的编译,链接器的选择和输出文件的路径等.
在cmake中,Target属性具有以下作用:

  • 控制如何编译源代码,例如使用那些编译器选项,预处理器定义,包含路径等.
  • 控制如何链接目标文件,例如使用那些库,库的搜索路径,连接器选项等.
  • 控制输出文件的类型和路径,例如是否为库,可执行文件或其他类型文件,以及输出路径.

此外,Target属性还可以影响如何在开发人员的IDE项目中呈现目标,包括如何组织和显示文件,图标和其他属性.
总之,Target属性是将源代码转换为目标文件的关键,几乎涵盖了所有将源代码转化为目标文件所需的详细信息.

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 ...)

其中target1target2 是Target的名称,prop1prop2 是Target的属性,value1value2 是相应属性的值.
例如可以通过以下命令来设置目标的输出名称和输出路径:

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缓存变量是用在存储用户定义的变量,可以在多次构建过程中保持不变的一种变量.cmake的缓存变量属性是针对缓存变量的属性,主要是为了影响cmake GUI和ccmake工具的使用体验,而不会直接影响构建过程.

  • 变量类型(TYPE)
    • 每个缓存变量都有一个类型,类型必须是BOOL,FILEPATH,PATH,STRING或INTERNAL的其中一种,使用get_property()命令和属性名TYPE可以获取变量的类型.变量的类型会影响CMake GUIccmake工具的变量呈现方式以及用于编辑其值的小部件.类型为INTERNAL的变量将不会在GUI中显示.
  • 高级变量(ADVANCED)
    • 使用mark_as_advanced()命令可以将缓存变量标记为高级变量.实际上,这只是,设置布尔类型的ADVANCED缓存变量的属性.CMake GUIccmake工具都提供一个选项用于显示或隐藏高级缓存变量.用户还可以选择关注基本变量还是查看完整列表.
  • 帮助字符串(HELPSTRING)
    • 缓存变量的帮助字符串通常作为set()命令的一部分进行设置,但是也可以使用HELPSTRING缓存变量属性进行修改或读取.帮助字符串用作cmake GUI中的工具提示以及ccmake工具的单行帮助提示.
  • 变量值列表(STRING)
    • 如果缓存变量的类型是STRING,则 cmake GUI将查找名为STRINGS的缓存变量属性.如果不为空,则该属性应为该变量的有效值列表,并且cmake GUI将该列表中的值作为下拉框而不是任意文本输入小部件呈现该变量.在ccmake中,按下该缓存变量上的enter键将循环显示所提供的值,注意,cmake并不强制缓存变量必须是STRINGS属性中的值,这只是为了方便cmake GUIccmake工具的使用.

总之cmake缓存变量属性是为了影响cmake GUIccmake工具的使用体验而设置的属性,它们不会直接影响构建过程,而是为了用户更方便的使用cmake.

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生成器还有限制.无法支持特定于配置的源文件属性.

你可能感兴趣的:(cmake,cmake)