cmake 使用笔记
CMake(Cross-Platform Make)是一个跨平台的构建系统,它可以根据用户编写CMakeLists.txt文件生成适用于不同编译器和操作系统的构建文件。 如生成 Visual Studio 17 的 .sln、XCode 的 .xcodeproj、Ninja 的 .ninja、make 的 Makefile文件。 进而生成可执行文件、静态库、动态库等目标文件。
关键字:跨编译器、跨平台
相关信息
关于cmake版本:
- 在cmake的早期版本(2.xx)中是使用
directory-oriented的方式来管理“属性”的传递性的。当你定义了一个属性,就意味着当前文件夹和子文件夹会使用这些属性。这使得你必须按照实际目录的方式来管理cmake的依赖关系。 - 在cmake的新版本(2015年左右,称“modern cmake”)中使用
target-oriented的方式来管理“属性”的传递性。对属性的传递性做了抽象,剥离了与文件实际目录的耦合。
本文不做说明则以新版本管理方式记录cmake的使用。 上述概念区别在后面有章节分析。
$ cmake --version
cmake version 3.31.6
CMake suite maintained and supported by Kitware (kitware.com/cmake).文档:
- CMake菜谱(CMake Cookbook中文版) https://www.bookstack.cn/read/CMake-Cookbook/content-preface-preface-chinese.md
- 现代CMake教程 https://crossroadw.github.io/ModernCMake/
- 官方文档
https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html(CN)
Getting Start
相关信息
前提:需要知道c/c++编译是怎么一回事。可以回看gcc内容。
配置文件: CMakeLists.txt
代码仓:
https://github.com/LawssssCat/blog/tree/master/code/code/demo-c-base/demo-03-cmake/01-gettingstart/CmakeLists.txt最简例子:
# 生成Makefile
$ cmake -S . -B build
-- The CXX compiler identification is GNU 15.2.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - failed
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ - works
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.9s)
-- Generating done (0.1s)
-- Build files have been written to: /home/vagrant/wk2
# 生成目标文件
$ cmake --build build
gmake: Warning: File 'Makefile' has modification time 1.9 s in the future
gmake[1]: Warning: File 'CMakeFiles/Makefile2' has modification time 1.8 s in the future
gmake[2]: Warning: File 'CMakeFiles/hello.dir/build.make' has modification time 1.8 s in the future
gmake[2]: warning: Clock skew detected. Your build may be incomplete.
gmake[2]: Warning: File 'CMakeFiles/hello.dir/build.make' has modification time 1.7 s in the future
[ 50%] Building CXX object CMakeFiles/hello.dir/hello.cc.o
[100%] Linking CXX executable hello
gmake[2]: warning: Clock skew detected. Your build may be incomplete.
[100%] Built target hello
gmake[1]: warning: Clock skew detected. Your build may be incomplete.
gmake: warning: Clock skew detected. Your build may be incomplete.# 最小要求CMake版本
# cmake_minimum_required(VERSION major.minor[.patch])
cmake_minimum_required(VERSION 3.10)
# 项目信息
# project(项目名
# [VERSION 版本号]
# [LANGUAGES 编程语言 ...] # 不填写,默认 LANGUAGES C CXX
# )
# tip: 该命令设置后,会设置如下变量。这些变量可以通过message打印。
# PROJECT_NAME
# PROJECT_SOURCE_DIR / <PROJECT_NAME>_SOURCE_DIR
# PROJECT_BINARY_DIR / <PROJECT_NAME>_BINARY_DIR
project(Helloworld VERSION 0.1 LANGUAGES CXX)
# add_executable(目标名称 源文件1 [源文件2...])
add_executable(helloworld hello.cpp)#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}常用参数:
| 参数名 | 作用 |
|---|---|
--target <target> | 执行目标,如Makefile中的目标 |
--config <profile> | todo |
常见指令
-DCMAKE_VERBOSE_MAKEFILE=ON—— 环境变量,打印详细日志cmake—— 构建脚本生成工具ccmake—— 命令行UIcmake_minimum_required—— 检查cmake版本,小于指定版本退出执行project—— 设置项目名,随后会生成相关变量供开发者使用add_executable/add_library—— 添加构建目标,指定可执行程序(exe/elf)或者库(lib/so/a)的名称、依赖文件set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY {PROJECT_BINARY_DIR})—— 编译“静态库”输出位置set(CMAKE_LIBRARY_OUTPUT_DIRECTORY {PROJECT_BINARY_DIR})—— 编译“动态库”输出位置set(CMAKE_RUNTIME_OUTPUT_DIRECTORY {PROJECT_BINARY_DIR})—— 编译“可执行文件”输出位置set_target_properties(SqrtLibrary PROPERTIES POSITION_INDEPENDENT_CODE {BUILD_SHARED_LIBS})—— 声明共享库位置无关(PIC)target_compile_definitions—— 为目标添加宏定义(e.g.-D ENABLE_HTTPS=1)target_compile_optionstarget_compile_featurestarget_include_directories—— 为目标添加头文件的查找路径target_link_libraries—— 为目标添加静态库/动态库的名称add_subdirectory—— 将子目录中的CMakeLists.txt文件添加进来,参与构建脚本的生成find_package—— 查找第三方库ctest—— 测试用例执行工具enable_testing—— 开启测试add_test—— 添加测试用例命令-B build—— 指定生成文件位置--build build—— 运行编译脚本,生成可执行程序--install build—— 运行安装脚本,将可执行程序及其配置移动到指定位置-L—— (调试)列出变量名-H—— (调试)列出变量名解释
基础概念
参考:
属性(Properties)
参考:
- 彻底弄懂cmake中的 INTEFACE 可见性/传递性 问题 https://chunleili.github.io/cmake/understanding-INTERFACE
从 modern cmake(>=3.0) 开始,使用的范式从 director-oriented 转换到了 target-oriented。 这其中最重要的有三个概念:
- target —— 编译的目标,一般就三种:
- 静态库: 使用
add_library() - 动态库: 使用
add_library()指定SHARED关键字 - 可执行文件: 使用
add_executable()
- 静态库: 使用
- properties —— “编译目标”的“属性”
- 编译标志:使用
target_compile_optionCOMPILE_DEFINITIONS
- 预处理宏标志:使用
target_compile_definitionsCOMPILE_OPTIONS
- 头文件目录:使用
target_include_directoriesINCLUDE_DIRECTORIESSOURCES
- 链接库:使用
target_link_librariesLINK_LIBRARIESINTERFACE_LINK_LIBRARIESSTATIC_LIBRARY_OPTIONS
- 链接标志:使用
target_link_optionsLINK_OPTIONSSTATIC_LIBRARY_FLAGS
- 编译标志:使用
- 可见性 —— “编译目标属性”的在不同“编译目标”之间的“传递性”
PRIVATE = 依赖自己使用,但不向上传递 (实现:
INCLUDE_DIRECTORIES)PUBLIC = 依赖自己使用,且向上传递 (实现:
INCLUDE_DIRECTORIES+ 下游可见)INTERFACE = 依赖自己不使用,但向上传递 (实现:
INTERFACE_INCLUDE_DIRECTORIES+ 下游可见)# 使用Interface的例子: add_library(Eigen INTERFACE) # Engine是header-only库,只有头文件和依赖关系,没有代码实现。 target_sources(Eigen INTERFACE # 由于自身没有代码实现,需要使用的依赖文件,但是需要将依赖向下传递,因此使用Interface可见性。 FILE_SET HEADERS BASE_DIRS src FILES src/eigen.h src/vector.h src/matrix.h ) add_executable(exe1 exe1.cpp) target_link_libraries(exe1 Eigen)提示
配置
target_include_directories有骚操作
变量(Variables)
普通变量
命令:
- 定义变量值
set(varName value... [PARENT_SCOPE])
- 取消变量值
set(varName)—— 注意:这种方式和set(varName "")有区别,参考“缓存变量”unset(varName)
特点:
- 所有变量值都是“字符窜”!
- 区分大小写
规范:
- 变量名建议只使用字母、数字、
_、- - 不要用
CMAKE_(cmake变量前缀)开头定义自己的变量
数据类型: cmake中只有字符串一种数据类型,但是表现形式有字符串、数组两种。 其中数组是用;符号分割的字符串,本质上还是字符串。
| 数据类型 | 声明方式 |
|---|---|
| 字符串 | set(myVar hello) set(myVar "hello\;world") |
| 数组/列表 | set(myVar hello world) set(myVar hello;world) set(myVar "hello;world") |
变量打印:
${...}—— 这种方式会递归解析内部变量名。 e.g.message("myVar = ${myVar}")、set(myVar2 ${myVar}2)、message(hello ${myVar}) # hello xxx[[...]]—— 这种方式不会解析内部变量名。 e.g.message([[myVar]])、message([=[myVar]=])、message([[hello ${myVar}]]) # 'hello ${myVar}'
环境变量(Env Variable)
只改变cmake文件内命令的环境变量,不会改变执行会话的环境变量
命令:
- 定义环境变量
set(ENV{varName} value)—— e.g.set(ENV{PATH} "$ENV{PATH}:/opt/myDir")
缓存变量(Cache Variable)
缓存变量会存放在CMakeCache.txt文件中
提示
对比普通变量和缓存变量:
- 缓存变量优先级比较低。 如果同时定义了相同名称的普通变量、缓存变量,则使用普通变量。
- 缓存变量的清空需使用
set(myVar "")而非普通变量的unset(myVar) - 作用域区别:
- 缓存变量作用域是“全局”
- 普通变量作用域是“函数”或“子目录” (具体区别在作用域章节详细分析。)
规范:
应避免使用相同名称的普通变量和缓存变量。 可以通过命名规则加以区分: 如缓存变量可以使用全大写字母(e.g. MY_CACHE_VARIABLE); 普通变量使用驼峰命名(e.g. normalVariable)
命令:
定义缓存变量
- 命令行声明
cmake -D "MY_VAR:STRING=hello world" .- 文档内部声明
set( varName value... CACHE type # 实际都是字符串,但在图形界面(cmake-gui)有区分:BOOL、FILEPATH、PATH、STRING、INTERNAL "helpstring" [FORCE] # 当false时,缓存中已有值,则新值不写入 )e.g.
set(DEMO_IS_PARALLEL off CACHE BOOL "Is using parallel algorithm")相关信息
BOOL取值支持多种格式:
- 真:true/yes/y/1 (及其大写)
- 假:false/no/n/0
另外,BOOL类型支持
option(optVal helpString [初始值])方式定义删除缓存变量
- 命令行声明
cmake -U "MY*" .
流程控制
注意
需要注意的是BOOL值的判断:
- 当取值为 1/非零数值/ON/YES/Y/TRUE (不区分大小写) 时,均判断为真
- 当取值为 0/OFF/NO/N/FALSE/IGNORE/NOTFOUND/
*-NOTFOUND/""(不区分大小写) 时,判断为假
规范:
更值得注意的是:(历史问题)当判断条件为字符串或变量时,判断结果会不一样
- 字符串 —— 不为
"TRUE"均为假 - 变量值 —— 取值不为
FALSE均为真
e.g.
set(v "hello")
if(v)
message("${v} is true") # here,原因:变量值,非空
else()
endif()
if(${v})
else()
message("'${v}' is false") # here,原因:字符串,非TRUE
endif()应对这种问题,建议人为规定统一用哪种写法。
if/elseif/else/endif
略
e.g.
if(expr)
# 1
elseif(expr2)
# 2
else()
# 3
endif()option
略
e.g.
option(BUILD_MYLIB "构建MyLib目标")
if(BUILD_MYLIB)
add_library(MyLib mylib.cpp) # here if "cmake . -DBUILD_MYLIB=on"
else()
message("忽略构建MyLib目标")
endif()foreach/IN/ITEMS/LISTS/ZIP_LISTS/RANGE
foreach(<loop_var> <items>)
<commands>
endforeach()e.g.
IN ITEMS
# foreach(v IN ITEMS a b c)
foreach(v a b c) # 简写
message("v:${v}")
endforeach()
# 打印:
# v:a
# v:b
# v:cIN LISTS
set(list1 2 4 6 8)
set(list2 1 3 5 7)
foreach(v IN LISTS list1 list2)
message("v=${v}")
endforeach()IN ZIP_LISTS
set(list1 2 4 6 8)
set(list2 1 3 5 7)
foreach(v IN ZIP_LISTS list1 list2)
message("v:(${v_0},${v_1})")
endforeach()
# 打印
# v:(2,1)
# v:(4,3)
# v:(6,5)
# v:(8,7)RANGE
foreach(v RANGE 1 8 2) # start end step
message("v=${v}")
endforeach()while
while(<condition>)
<commands>
endwhile()break/continue
略
关键字
AND/OR/NOT
略
比较运算符
| 数值 | 字符串 | 版本号 | 路径 |
|---|---|---|---|
| LESS | STRLESS | VERSION_LESS | |
| GREATER | STRGREATER | VERSION_GREATER | |
| EQUAL | STREQUAL | VERSION_EQUAL | PATH_EQUAL |
| LESS_EQUAL | STRLESS_EQUAL | VERSION_LESS_EQUAL | |
| GREATER_EQUAL | STRGREATER_EQUAL | VERSION_GREATER_EQUAL | |
| MATCHS |
e.g.
if("/path//to/myfile" PATH_EQUAL "/path/to/myfile")
# here,原因:路径相同
else()
endif()
if("/path//to/myfile" STREQUAL "/path/to/myfile")
else()
# here,原因:字符串内容不一样
endif()
# 正则
if("My phone number is 12345" MATCHES "My phone number is ([0-9]+)")
# 注意,匹配上可以用CMAKE_MATCH_<N>调用(作用域:当前分支)
message("Phone number:${CMAKE_MATCH_1}") # 输出: Phone number:12345
endif()文件操作
- EXISTS
- IS_DIRECTORY
- IS_SYMLINK
- IS_ABSOLUTE
- IS_READABLE
- IS_WRITABLE
- IS_EXECUTABLE
- IS_NEWER_THAN
存在性测试
- COMMAND 是否可调用
- POLICY 是否存在策略
- TARGET 是否存在目标
- TEST 是否存在测试
- DEFINED 是否定义变量
- IN_LIST 是否在列表中
e.g.
set(list1 a b c d e f g)
set(a "f")
if (a IN_LIST list1)
message("a is in list1")
endif()预定义变量
if(MSVC)
message("build with MSVC")
elseif(MINGW)
message("build with MINGW")
elseif(XCODE)
message("build with XCODE")
else()
message("build with other compiler")
endif()下面语句常用于CMakeLists.txt文件开头
if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(FATAL_ERROR # 在打印提示信息后终止后面命令
"错误:禁止源码内编译。
请为构建文件创建一个单独的目录。
")
endif()函数(Function)
function(function_name args...)
# do something
endfunction()命名参数、未命名参数
关键字:
- ARGC 参数数量
- ARGV 参数列表
- ARGN 未命名参数列表
- ARGV
<N>位置N上的参数值
e.g.
function(my_function1 a b)
message("a:${a},b:${b}") # a:1,b:2
message("argc:${ARGC}") # argc:5
message("argv:${ARGV}") # argv:1;2;3;4;5
message("argv2:${ARGV2}") # argv2:3
message("argn:${ARGN}") # argn:3;4;5
endfunction()
my_function1(1 2 3 4 5)关键字参数
function(my_function2 targetName)
message("targetName:${targetName}") # targetName:myTarget
# cmake_parse_arguments(
# <prefix>
# <options:选项关键字列表>
# <one_value_keywords:单值关键字列表>
# <multi_value_keywords:多值关键字列表>
# <args>...)
cmake_parse_arguments(arg_myfunction2 "USE_MYLIB" "MYLIB_PATH" "SOURCES;INCLUDES" ${ARGN})
message("USE_MYLIB:${arg_myfunction2_USE_MYLIB}") # USE_MYLIB:FALSE
message("MYLIB_PATH:${arg_myfunction2_MYLIB_PATH}") # MYLIB_PATH:/usr/local/lib
message("SOURCES:${arg_myfunction2_SOURCES}") # SOURCES:main.cpp
message("INCLUDES:${arg_myfunction2_INCLUDES}") # INCLUDES:include;include2
# <prefix>_UNPARSED_ARGUMENTS # 未解析的关键字变量
# <prefix>_KEYWORDS_MISSING_VALUES # 调用时,未提供值的关键字变量
endfunction()
my_function2(myTarget
USE_MYLIB # 选项关键字(Option Keywords):传入值则为TRUE
MYLIB_PATH "/usr/local/lib" # 单值关键字:后面跟一个值
SOURCES "main.cpp"
INCLUDES "include" "include2" # 多值关键字:后面跟多个值
)返回值
有两种方式:
1、
function(my_function3 returnValue)
set(${returnValue} "hello world" PARENT_SCOPE)
endfunction()
my_function3(result)
message("result:${result}") # result:hello world22、
function(my_function4 returnValue)
set(${returnValue} "hello world2")
return(PROPAGATE ${returnValue})
endfunction()
my_function4(result)
message("result:${result}") # result:hello world2内置函数
list命令
list(操作关键字 <listvar> [其他参数...])
e.g.
LENGTH:获取数组长度
set(listVal a b c)
list(LENGTH listVar listLength)
message("listLength: ${listLength}") # listLength: 3GET:获取指定位置
set(listVar a b c)
list(GET listVar 0 firstItem)
message("firstItem: ${firstItem}") # firstItem: aJOIN、APPEND
set(listVar a b c)
list(APPEND listVar d)
list(JOIN listVar "-" outStr)
message("outStr: ${outStr}") # outStr: a-b-c-dstring命令
string(操作关键字 <list> [其他参数...])
e.g.
FIND
string(FIND "interesting" "in" fIndex)
string(FIND "interesting" "in" rIndex REVERSE)
message("fIndex = ${fIndex}") # 0
message("rIndex = ${rIndex}") # 8math命令
math(EXPR <variable> "<expr>" [OUTPUT_FORMAT <format:HEXADECIMAL/DECIMAL>])
运算符: +-*/%|&^~. <<>>()
e.g.
set(myVar 1+2*6/2)
message("myVar = ${myVar}") # myVar = 1+2*6/2
math(EXPR a 1+2*6/2)
math(EXPR b ${a}*3)
message("a = ${a}") # a = 7
message("b = ${b}") # b = 21file命令
file(操作关键字 <var> [其他参数...])
- 读
- READ
- STRINGS
- HASH
- TIMESTAMP
- 写
- WRITE
- TOUCH
- GENERATE
- GONFIGURE
- 文件系统
- GLOB —— 获取文件列表
- MAKE_DIRECTORY
- REMOVE
- COPY
- COPY_FILE
- CHMOD
- 路径转换
- REAL_PATH
- RELATIVE_PATH
- 传输
- DOWNLOAD
- UPLOAD
e.g.
GLOB:列出目录文件 💡自动添加文件
file(GLOB varFileList
LIST_DIRECTORIES false
CONFIGURE_DEPENDS RELATIVE ${CMAKE_SOURCE_DIR} "*.cpp")
message("varFileList: ${varFileList}") # varFileList: main.cpp;shape.cpp
add_executable(MyApp ${varFileList})file(GENERATE
OUTPUT generatorExample.txt
CONTENT "platform = $<PLATFORM_ID>"
TARGET myApp)宏(Macro)
macro(myMacro arg...)
# command
endmacro()提示
宏和函数类似,但是函数有自己的栈(Stack),而宏相当于将内容插入到上下文。
宏和函数还有其他区别,如:
宏的参数是值,函数的参数是变量,这在逻辑判断上会有分歧
function(my_function a) # 这里a是变量 if(a) # 变量判断非"",结果为真 message("true") endif() endfunction() macro(my_macro a) # 这里a是字符串"hello" if(a) # 字符串判断非"TRUE",结果为假 else() message("false") end() endmacro() my_function(hello) # true my_macro(hello) # false宏用return命令会导致调用者的上下文退出
简单来说: 优先使用函数,而不是使用宏。
作用域(Scope)
# 默认范围
set(myVar cat)
block()
set(myVar dog)
message("${myVar}") # dog
endblock()
message("${myVar}") # cat
# PARENT_SCOPE
set(myVar cat)
block()
set(myVar dog PARENT_SCOPE) # 修改外层变量值
message("${myVar}") # cat
endblock()
message("${myVar}") # dog
# PROPAGATE —— 传递
set(myVar cat)
block(SCOPE_FOR VARIABLES PROPAGATE myVar) # 内部的修改可直接传递到外面
set(myVar dog)
message("${myVar}") # dog
endblock()
message("${myVar}") # dog生成器表达式(Generator Expression)
格式: $<...>
取值时机: 编译时(cmake --build .)才确定取值
add_custom_target(MyTarget
ALL
COMMAND ${CMAKE_COMMAND} -E echo "platform = $<PLATFORM_ID>")常见:
$<PLATFORM_ID>—— 生成的平台,如windows、linux、macos$<CXX_COMPILER_ID>—— 所使用的编译器编号,如GNU、MSVC、$<CXX_COMPILER_VERSION>—— 所使用的编译版本号$<condition:trueValue>—— 判断$<IF:condition,trueValue,falseValue>—— 三元运算符$<BOOL:value>—— 将Y/off/no/...等转换为0/1$<AND:...>$<OR:...>$<NOT:value>
e.g.
set(myVar 0)
file(GENERATE OUTPUT generatorExample1.txt CONTENT "res = $<IF:${myVar},hello,bye>" TARGET myApp) # res = byetarget_compile_options(MyApp
PRIVATE
$<$<AND:$<CXX_COMPILER_ID:GNU>,$<CONFIG:Debug>>:-Og>)
$<$<AND:$<CXX_COMPILER_ID:GNU>,$<CONFIG:Release>>:-O2>)
$<$<AND:$<CXX_COMPILER_ID:MSVC>,$<CONFIG:Debug>>:/Od>)
$<$<AND:$<CXX_COMPILER_ID:MSVC>,$<CONFIG:Release>>:/O2>)进阶概念
包含(include)
include(xx) 作用单纯是把指定位置的内容引入到当前文件。
它并不会改变相对路径的锚点,比如在 /CMakeLists.txt 中使用 include(some/x.cmake) 引入 add_subdirectory(x.cpp) 命令会失败,因为要改成引入 add_subdirectory(some/cpp) 命令。
提示
因为include有不改变相对路径的锚点的问题(特性?),所以一般在其中写与路径无关的内容,如下载等功能性的函数。
include(FetchContent) # 引入函数
# https://github.com/google/googletest
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip
)模块(Module)
相关信息
参考:
在CMake里,模块(Module)是一个包含可重用代码的CMake文件。
可以通过find_packages命令找到这些模块,然后通过target_link_libraries命令将。
e.g.
find_package(OpenSSL REQUIRED)
if (NOT OPENSSL_FOUND)
message(SEND_ERROR "OpenSSL not found")
endif()
target_link_libraries(MyTarget INTERFACE OpenSSL::SSL OpenSSL::Crypto)Cmake维护一些常用的三方库的模块列表: https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html
一、查找模块
cmake查找模块有两种模式:
通过查找
Find<PackageName>.cmake文件,查找模块的引入方式注意
这种形式虽然方便,但是因为模块和三方库是分开维护的,所以会有第三方库过时的情况。
通过配置文件配置形式查找模块的引入方式
通过FindXxx.cmake文件查找三方包
cmake到下面两个目录查找名为Find<PackageName>.cmake的“查找模块”:
在CMake安装目录的modules目录中 (e.g.
/usr/share/cmake/Modules)$ rpm -qa | grep cmake $ rpm -ql cmake-filesystem-3.31.6-2.fc42.x86_64 | grep -i module $ rpm -ql cmake-data-3.31.6-2.fc42.noarch | grep -i module | grep -i openssl /usr/share/cmake/Help/module/FindOpenSSL.rst /usr/share/cmake/Modules/FindOpenSSL.cmake # ————————————— openssl模块💡在
CMAKE_MODULE_PATH列表目录中(自定义模块可以添加到这里,find_packages会从中优先查找模块)
以OpenSSL为例: 由find_package(OpenSSL REQUIRED)命令开始,cmake会找到FindOpenSSL.cmake文件。 通过读取这个文件,可以找到OpenSSL的依赖项(相关的头文件、库文件,并且对不同操作系统做了区分),进而对依赖项进行关联。
通过配置文件形式查找三方包
参考: https://cmake.com.cn/cmake/help/latest/manual/cmake-packages.7.html#package-configuration-file
二、导入目标(Imported Targets)
find_packages找到三方包后,会定义“导入目标(Imported Targets)”。 如对于OpenSSL,会定义 OpenSSL::SSL/OpenSSL::Crypto/OpenSSL::applink/... 等导入目标。 其中 OpenSSL 是名空间,后面 SSL/Crypto/applink/... 等是库的名称,与C++的命名空间类似。
生成器(Generator)
生成如 makefile、VS2017 等不同构建工具需要的文件的生成器。
# 查看生成器
$ cmake -G
CMake Error: No generator specified for -G
Generators
Green Hills MULTI = Generates Green Hills MULTI files
(experimental, work-in-progress).
* Unix Makefiles = Generates standard UNIX makefiles. # --- 💡前面带*号,默认生成器
Ninja = Generates build.ninja files.
Ninja Multi-Config = Generates build-<Config>.ninja files.
Watcom WMake = Generates Watcom WMake makefiles.
CodeBlocks - Ninja = Generates CodeBlocks project files
(deprecated).
CodeBlocks - Unix Makefiles = Generates CodeBlocks project files
(deprecated).
CodeLite - Ninja = Generates CodeLite project files
(deprecated).
CodeLite - Unix Makefiles = Generates CodeLite project files
(deprecated).
Eclipse CDT4 - Ninja = Generates Eclipse CDT 4.0 project files
(deprecated).
Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files
(deprecated).
Kate - Ninja = Generates Kate project files (deprecated).
Kate - Ninja Multi-Config = Generates Kate project files (deprecated).
Kate - Unix Makefiles = Generates Kate project files (deprecated).
Sublime Text 2 - Ninja = Generates Sublime Text 2 project files
(deprecated).
Sublime Text 2 - Unix Makefiles
= Generates Sublime Text 2 project files
(deprecated).Preset
通过json配置指定cmake工具链的位置。
cmake --preset
- CMakePresets.json: 用于指定项目范围内的构建详细信息。通常包含通用的配置选项,适用于所有开发者。
- CMakeUserPresets.json: 用于开发者指定自己的本地构建详细信息。通常包含特定于开发者的配置选项。 (加入
.gitignore文件)
{
"version": 8, // 整数,支持的版本
"cmakeMinimumRequired": { // 构建此项目所需的最低CMake版本 (可选)
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "vcpkg-preset",
"displayName": "vcpkg-preset",
"description": "Using compilers: C = /usr/bin/clang, CXX = /usr/bin/clang++",
"hidden": false, // (可选)当为true时,cmake不会直接调用该配置。一般用在被继承(inherits)的配置上
// "inherits": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}", // 使用 sourceDir 和 presetName 宏
"installDir": "/path/to/install",
"environment": {},
"cacheVariables": { // 【重要】相当于 cmake -D....
"CMAKE_VERBOSE_MAKEFILE": "ON",
"FETCHCONTENT_QUIET": "OFF",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}",
"CMAKE_C_COMPILER": "/usr/bin/clang",
"CMAKE_CXX_COMPILER": "/usr/bin/clang++",
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
},
"vendor": { // 包含供应商特定的信息。供更上层ide调用,CMake不对此做任何解析 (可选)
"example.com/ExampleIDE/1.0": {
"customSetting1": "value1",
"customSetting2": {
"subSetting1": "subValue1",
"subSetting2": "subValue2"
}
},
},
"toolset": // 工具集的配置
}
],
"buildPresets": [ // 构建预设
{
"name": "build-debug",
"configurePreset": "vcpkg-preset",
"configuration": "Debug",
"jobs": 10,
"verbose": true
}
],
"testPresets": [ // 测试预设
]
}构建缓存(加速)
ccache/sccache
CC="ccache gcc" CXX="ccache g++" cmake ..
ccache
ccache(compiler cache,编译器缓存)是一个开源的编译加速工具,主要用于C/C++编译过程。 通过缓存编译结果来减少重复编译的时间,从而显著加快**增量编译(如修改少量代码后重新编译)**的速度。
提示
通常缓存位于~/.cache/cache隐藏目录中,并在多个项目之间共享。
安装
参考:
- https://github.com/ccache/ccache/blob/master/doc/install.md
- https://www.cnblogs.com/sinferwu/p/15507638.html
gcc/clangcp ccache /usr/local/bin/
ln -s ccache /usr/local/bin/gcc
ln -s ccache /usr/local/bin/g++编译时,会查找
PATH中首个发现的gcc/clang作为实际的编译工具。 当然,可以通过配置指定实际的构建工具。
$ yum install ccache
$ cmake -D CMAKE_C_COMPILER_LAUNCHER=ccache -D CMAKE_CXX_COMPILER_LAUNCHER=ccache ...常用命令
参考:
ccache [ccache options]
ccache [KEY=VALUE …] compiler [compiler options]
compiler [compiler options]# 查看版本
ccache --version
# 查看缓存统计
ccache -s
# 清理缓存
ccache -C配置
参考: https://ccache.dev/manual/4.9.html#_configuration
export CCACHE_DIR=~/.cache/ccachesccache
todo 测试远程缓存特性
测试(CTest)
cmake提供ctest可执行程序来拉起CMakeList.txt中配置的测试用例。
在CMakeList.txt中配置测试用例需要如下步骤:
- 添加
enable_test指令,生成测试用例入口,如makefile的make test目标 - 添加
add_test指令,生成测试用例的入口,如makefile的make test_myadd_usecase目标
e.g.
enable_testing()
# does the application run
add_test(NAME Runs COMMAND Tutorial 25)
# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_test_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION 'Usage:.*.number')
# define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp {arg} COMMAND {target} {arg})
set_test_properties(Comp {arg} PROPERTIES_PASS_REGULAR_EXPRESSION {result})
endfunction(do_test)
# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
# ...单元构建(Unity Builds)
todo set_target_properties(myTarget PROPERTIES UNITY_BUILD ON)
todo 可能遇到的ODR问题介绍、处理
预编译头文件(PCH)
像string等大的头文件会被重复处理影响构建速度。 预编译头文件(PCH)可以将上述头文件一次引入重复使用。 PCH头文件一般包含常用的标准头文件和项目头文件。
todo target_precompile_headers(myTarget PRIVATE "pch.h")
统计(CDash)
将项目的测试结果在面板(dashboard)中显式
# enable testing
enable_testing()
# enable dashboard script
include(CTest)
set(CTEST_PROJECT_NAME 'CMakeTutorial')
set(CTEST_NIGHTLY_START_TIME '00:00:00 EST')
set(CTEST_DROP_METHOD 'http')
set(CTEST_DROP_SITE 'my.cdash.org')
set(CTEST_DROP_LOCATION '/submit.php?=project=CMakeTutorial')
set(CTEST_DROP_SITE_CDASH TRUE)安装(Install)
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)打包(CPack)
todo 为打包需求提供了DSL
制作安装包
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE '{CMAKE_CURRENT_SOURCE_DIR}/License.txt')
set(CPACK_PACKAGE_VERSION_MAJOR '{Tutorial_VERSION_MAJOR}')
set(CPACK_PACKAGE_VERSION_MINOR '{Tutorial_VERSION_MINOR}')
include(CPack)# 生成二进制
# -G generator
# -C Configuration
cpack -G ZIP -C Debug
# 生成源文件
cpack --config CPackSourceConfig.cmake参考: https://www.bilibili.com/video/BV15q4y1Z7EP
# 安装结果
${INSTALL_PATH}/bin/Tutorial
${INSTALL_PATH}/include/MathFunctions.h
${INSTALL_PATH}/include/TutorialConfig.h
${INSTALL_PATH}/lib/cmake/MathFunctions/MathFunctionsConfig.cmake
${INSTALL_PATH}/lib/cmake/MathFunctions/MathFunctionsConfigVersion.cmake
${INSTALL_PATH}/lib/cmake/MathFunctions/MathFunctionsTargets-noconfig.cmake
${INSTALL_PATH}/lib/cmake/MathFunctions/MathFunctionsTargets.cmake
${INSTALL_PATH}/lib/libMathFunctions.so
${INSTALL_PATH}/lib/libSqrtLibrary.a案例
分层目录Demo
代码仓:
https://github.com/LawssssCat/blog/tree/master/code/code/demo-c-base/demo-03-cmake/02-directory/CmakeLists.txt包含:
- [x] 分层目录组织方式
- [ ] 定义接口库(只有接口,没有实现)
- [x] 配置和使用第三方库(with
source codefor cpp-httplib) - [x] 配置和使用第三方库(with
xxx-develfor openssl) - [x] 配置和使用第三方库(with
FetchContentfor googletest) - [x] 配置和使用第三方库(with
vcpkgfor zlib) - [x] 集成Google测试框架
- [ ] 包的安装(参考:https://www.bilibili.com/video/BV1ZboeYgErH/)
- [ ] 配置和使用自定义包