第四章
4.2目标的概念
创建目标的三种指令
add_executable(app1 a.cpp b.cpp c.cpp)
add_library()
add_custom_target()
add_custom_target(Name [ALL] [command1 [args1...]]
[COMMAND command2 [args2...] ...]
[DEPENDS depend depend depend ... ]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[JOB_POOL job_pool] [VERBATIM]
[USES_TERMINAL]
[COMMAND_EXPAND_LISTS]
[SOURCES src1 [src2...]])
使用例子,删除构建树中的文件
add_custom_target(clean_stale_coverage_files COMMAND find . -name "*.gcda" -type f -delete)
不同于可执行目标和库目标,自定义目标只有 添加到依赖关系图 中才会构建。
4.2.1依赖图
指项目中组件之间的依赖关系图。
CMake在配置项目时,收集关于目标的信息构建一个依赖图,一个有向无环图。cmake的构建系统会从被依赖项开始构建,逐渐完成整体构建。 比如下面这个示例:
cmake_minimum_required(VERSION 3.19.2)
project(BankApp CXX)
add_executable(terminal_app terminal_app.cpp)
add_executable(gui_app gui_app.cpp)
target_link_libraries(terminal_app calculations)
target_link_libraries(gui_app calculations drawing)
add_library(calculations calculations.cpp)
add_library(drawing drawing.cpp)
add_custom_target(checksum ALL
COMMAND sh -c "cksum terminal_app>terminal.ck"
COMMAND sh -c "cksum gui_app>gui.ck"
BYPRODUCTS terminal.ck gui.ck
COMMENT "Checking the sums..."
)
示例中不能保证custom_target的构建是在可执行目标之后,需要通过add_dependencies来保证。
add_dependencies(checksum terminal_app gui_app)
给目标连接库
target_link_libraries(t1 lib1)
4.2.2 将依赖关系可视化
cmake --graphviz=test.dot ..
cmake --graphviz=可视化文件名 \[CmakeLists.txt]文件所在目录会将可视化文件直接生成在执行命令的目录下。
4.2.3 目标属性
通过get_target_property 和 set_target_properties来获取或者设置目标属性。
语法为
get_target_property(<var> <target> <property-name>)
set_target_properties(<target1> <target2> ... PROPERTIES <prop1-name> <value1> <prop2-name> <value2> ...)
还可以使用set_property()在其他作用的作用域上设置属性,比如在GLOBAL,DIRECTORY,SOURCE,INSTALL,TEST,CACHE。
4.2.4 可传递使用需求
target_compile_definitions(<source> <INTERFACE|PUBLIC|PRIVATE> [items1...])
target_link_options() 和 set_target_properties()能够管理如下属性,可以在
cmake.org/cmake/help/… 中找到相关描述。
• AUTOUIC_OPTIONS • COMPILE_DEFINITIONS • COMPILE_FEATURES • COMPILE_OPTIONS • INCLUDE_DIRECTORIES • LINK_DEPENDS • LINK_DIRECTORIES • LINK_LIBRARIES • LINK_OPTIONS • POSITION_INDEPENDENT_CODE • PRECOMPILE_HEADERS • SOURCES
target_link_libraries的传播属性?? INTERFACE PUBLIC PRIVATE
4.2.5 处理冲突的传播属性
有时目标的属性会发生冲突,为了确保目标间的兼容性,可以创建自定义接口属性然后存储版本信息。然后将自定义属性信息存储到目标自带的兼容属性列表中。
每个目标都有4个列表:
COMPATIBLE_INTERFACE_BOOL COMPATIBLE_INTERFACE_STRING COMPATIBLE_INTERFACE_NUMBER_MAX COMPPATIBLE_INTERFACE_NUMBER_MIN 将自定义属性添加到其中会触发传播和兼容性检查。
示例如下:
cmake_minimum_required(VERSION 3.20)
project(cmake LANGUAGES C CXX)
add_executable(main empty.cpp)
set_property(TARGET main PROPERTY INTERFACE_LIB_VERSION 4)
set_property(TARGET main APPEND PROPERTY COMPATIBLE_INTERFACE_STRING LIB_VERSION)
# 可以只添加一次
add_executable(main1 empty.cpp)
set_property(TARGET main1 PROPERTY INTERFACE_LIB_VERSION 4)
#set_property(TARGET main1 APPEND PROPERTY COMPATIBLE_INTERFACE_STRING LIB_VERSION)
add_library(des empty.cpp)
target_link_libraries(des main main1)
4.2.6 实现伪目标
`
别名目标 能干啥?
add_executable( ALIAS ) add_library( ALIAS ) `
接口库,不编译任何东西,只是作为一个实用工具目标。围绕传播属性构建的。。? `
接口库
add_library(Eigen INTERFACE src/eigen.h src/vector.h src/matrix.h) target_include_directories(Eigen INTERFACE $<BU..>)...
使用时
target_link_libraries(executable Eigen)
target_link_libraries没有实际的链接,只是将INTERFACE属性传播到可执行目标。
使用接口库和属性传播机制创建一个逻辑目标
add_library(warning_props INTERFACE) target_compile_options(warning_props INTERFACE -Wall -Wextra -Wpedantic) target_link_libraries(executable warning_props) ` 使用这个库时,会在executable目标上设置第二个命令target_compile_options设置的编译属性。
4.2.7 构建目标
ADD是构建的默认目标 不包括EXCLUDE_FORM_ALL关键字限定的目标,除非显示指定
4.3编写自定义命令
add_custom_command(OUTPUT output1 \[output2 ...]
COMMAND command1 \[ARGS] \[args1...]
\[COMMAND command2 \[ARGS] \[args2...] ...]
\[MAIN_DEPENDENCY depend]
\[DEPENDS \[depends...]]
\[BYPRODUCTS \[files...]]
\[IMPLICIT_DEPENDS depend1 \[ depend2] ...]
\[WORKING_DIRECTORY dir]
\[COMMENT comment]
\[DEPFILE depfile]
\[JOB_POOL job_pool]
\[VERBATIM] \[APPEND] \[USES_TERMINAL]
\[COMMAND_EXPAND_LISTS])
不创建逻辑目标,必须添加到依赖关系图中?
4.3.1使用自定义命令作为生成器
例子一
跨平台的protobuf文件的编译。编译后将生成person.pb.h和person.pb.cc,以下是一个自定义命令编译protobuf文件。假设我们知道protobuf文件所在。
add_custom_command(OUTPUT person.pb.h person.pb.cc
COMMAND protoc ARGS person.proto
DEPENDs person.proto
)
然后
add_executable(serializer serizlizer.cpp person.pb.cc)
例子二
复制生成必要的头文件。
add_executable(main main.cpp constants.h)
target_include_directories(main PRIVATE
${CMAKE_BINARY_DIR}
)
add_custom_command(OUTPUT constants.h
COMMAND cp
ARGS "${CMAKE_SOURCE_dIR}/templates.xyz" constants.h
)
拷贝头文件到构建目录
4.3.2 使用自定义命令作为目标钩子
add_custom_command(TARGET <target>
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[VERBATIM] [USES_TERMINAL]
[COMMAND_EXPAND_LISTS]
)
··· PRE_BUILD在此目标的任何其他规则运行之前运行(仅Visual Studio生成器) ··· PRE_LINK在编译所有源之后但是在链接(或存档)目标之前运行。(不适用于自定义目标) ··· POST_BUILD在为该目标执行所有其他规则之后运行。
4.4生成器表达式
配置、生成和构建 配置阶段容易遇到依赖问题,比如需要知道另一个可执行文件的工作路径,但是又必须等配置完成后才能知道。因为先使用一个占位符代表工作路径完成项目配置,在生成阶段再进行项目构建。
由于生成器表达式在生成阶段才会计算,因此调试的时候应该使用一下方法调试:
` file(GENERATE OUTPUT filename CONTENT "$<...>")
或者
add_custom_target(gendbg COMMAND <...>") `
4.4.1 一般语法
target_compile_definitions(foo PUBLIC BAR=$<TARGET_FILE:foo>)
生成器表达式语法: $<EXPRESSION:arg1,arg2,arg3>
生成器表达式嵌套: <PLATFORM_ID>> 或者 嵌套一个变量展开 {my_variable}>
条件表达式
$<IF:condition, true_string, false_string>
4.4.2 计算类型
生成器表达式只计算字符串和布尔。布尔值由0和1表示。其他都是字符串。
有三种类型的表达式的值为布尔值。
1. 逻辑运算符
$<NOT:arg> $<AND:arg1,arg2,arg3...> $<OR:arg1,arg2,arg3...> $<BOOL:string_arg> # 将参数从字符串转换为布尔类型
若这些条件都不满足,字符串转换将计算为 1:
• 字符串为空。
• 字符串不区分大小写,相当于 0、FALSE、OFF、N、NO、IGNORE 或 NOTFOUND。
• 字符串以-NOTFOUND 后缀结尾 (区分大小写)。
2. 字符串比较
$<STREQUAL:arg1,arg2> 区分大小写的字符串比较
$<EQUAL:arg1,arg2> 将字符串转换为数字并比较是否相等
$<IN_LIST:arg,list> 检查arg是否在列表中,区分大小写
$<VERSION_EQUAL:v1,v2> 版本比较
$<VERSION_LESS:v1,v2> 版本比较
$<VERSION_GREATER:v1,v2> 版本比较
$<VERSION_LESS_EQUAL:v1,v2> 版本比较
$<VERSION_GREATER_EQUAL:v1,v2> 版本比较
3. 查询变量
$<TARGET_EXISTS:arg> 目标是否存在
有多个查询扫描传递的参数以获取特定值
$<CONFIG:args> 是args中的当前配置
$<PLATFORM_ID:args> 是args中的当前平台ID
$<LANG_COMPILER_ID:args> LANG编译器ID在args中,LANG指C CXX CUDA OBJC OBJCXX Fortran ISPC
$<LANG_COMPILER_VERSION:args> LANG编译器版本在args中。
$<COMPILER_FEATURES:features> 如果编译器支持此目标的特性
$<COMPILER_LANG_AND_ID:lang,compiler_id1,compiler_id2...>
根据编译器的不同设置不同的标志,例子如下:
CMake target_compile_definitions(myapp PRIVATE $<$CXX_CLANG_AND_ID:CXX,AppleClang,Clang>:CXX_CLANG> $<$CXX_CLANG_AND_ID:CXX,Intel>:CXX_INTEL> $<$CXX_CLANG_AND_ID:C,Clang>:C_Clang> )
如果使用了AppleClang、CXX或Clang编译器,则会设置-DC_CLANG定义。
$<COMPILE_LANGUAGES:args>
根据编译器编译设置编译标志:
target_compile_options(myapp PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>)
当编译的项目语言为CXX时,设置编译标志-fno-exceptions
同样的,
$<LINK_LANG_AND_ID:lang,compiler_id1,compiler_id2...>
会根据连接步骤的语言,借助表达式指定特定语言的链接库、连接选项、链接目录和连接依赖项,以及目标中的链接器组合。
$<LINK_LANG:args>
和$<COMPILER_LANG:CXX>类似。
3. 字符串求值
$<CONFIG>
在生成阶段求配置的名称Debug或Release
$<PLATFORM_ID>
当前系统的平台
$<LANG_COMPILER_ID>
使用的语言
$<LANG_COMPILER_VERSION>
使用的LANG编译器的CMake编译器版本
$<COMPILER_LANGUAGE>
使用的语言
$<LINK_LANGUAGE>
链接时的语言
查询依赖的目标
$<TARGET_NAME_IF_EXISTS:target> 如果目标存在在位目标的目标名称
$<TARGET_FILE:target> 目标二进制文件的完整路径
$<TARGET_FILE_NAME:target> 目标文件名
$<TARGET_FILE_BASE_NAME:target> 目标的libmylib.so的mylib字段
$<TARGET_FILE_PREFIX:target> 目标文件的前缀 lib
$<TARGET_FILE_SUFFIX:target> 目标文件的后缀 .so .exe
$<TARGET_FILE_DIR:target> 目标文件的目录
连接到目标时使用的文件,通常指库 .a .lib .so
$<TARGET_LINKER_FILE:target> 链接到目标时使用的文件。
$<TARGET_LINKER_FILE:target> 目标二进制文件的完成路径
$<TARGET_LINKER_FILE_NAME:target> 目标文件名
$<TARGET_LINKER_FILE_BASE_NAME:target> 目标的libmylib.so的mylib字段
$<TARGET_LINKER_FILE_PREFIX:target> 目标文件的前缀 lib
$<TARGET_LINKER_FILE_SUFFIX:target> 目标文件的后缀 .so .exe
$<TARGET_LINKER_FILE_DIR:target> 目标文件的目录
。。。。
4.4.3 可以尝试的例子
例一
target_compile_options(tgt PRIVATE $<IF:$<CONFIG:DEBUG>:-ginlinepoints>)
例二
if表达式可以用生成表达式进行替换,比如
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_compile_definitions(myProject PRIVATE LINUX=1)
endif()
target_compile_definitions(myProject PRIVATE $<$<CMAKE_SYSTEM_NAME:LINUX>:LINUX=1>)
带编译器特定标志的接口库
add_library(enable_rtti INTERFACE)
target_compile_options(enable_rtti INTERFACE
$<$<OR:$<COMPILER_ID:GNU>, $<COMPILER_ID:Clang>>:-rrti>
)
接口库可以用来提供编译器编译标志,通过链接enable_rtti目标来设置库或者可执行程序的编译标志。
file(GENERATE OUTPUT boolean CONTENT
"1 $<0:TRUE>
2 $<0:TRUE,FALSE> (won't work)
3 $<1:TRUE,FALSE>
4 $<IF:0,TRUE,FALSE>
5 $<IF:0,TRUE,>
")