CMAKE第二部分第4章 使用目标

281 阅读4分钟

第四章

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_propertyset_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 CMAKECOMMANDEecho"{CMAKE_COMMAND} -E echo "<...>") `

4.4.1 一般语法

target_compile_definitions(foo PUBLIC BAR=$<TARGET_FILE:foo>)

生成器表达式语法: $<EXPRESSION:arg1,arg2,arg3>

生成器表达式嵌套: <UPPERCASE:<UPPER_CASE:<PLATFORM_ID>> 或者 嵌套一个变量展开 <UPPERCASE:<UPPER_CASE:{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,>
")