文章目录
CMake从入门到实践
CMake 是什么?
-
全称Cross Platform Make,起初为了跨平台需求,而后不断完善并广泛使用
-
一门语言
-
一款优秀的工程构建工具
特点及优势
-
开放源代码,具有BSD许可
-
跨平台,支持Linux, Mac和Windows等不同操作系统
-
编译语言简单,易用,简化编译构建过程和编译过程
-
编程高效,可扩展
CMake基本语法
语法规则
-
变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名
-
指令(参数 1 参数 2…) 参数使用括弧括起,参数之间使用空格或分号分开。
-
指令是大小写无关的,参数和变量是大小写相关的。
-
SET(SRC_LIST main.c)也可以写成 SET(SRC_LIST “main.c”)。
文件名中间包含了空格 SET(SRC_LIST “fun nc.c”)
-
可以忽略掉 source 列表中的源文件后缀
-
工程管理的CMake文件名必须是CMakeLists.txt
变量
-
变量引用:使用${}进行变量值的引用。
- 但在 IF 等语句中,是直接使用变量名而不通过${}取值
-
自定义变量: 两种方式
- 隐式定义:PROJECT 指令隐式的定义project_BINARY_DIR 和project_SOURCE_DIR 两个变量
- 显式定义:使用 SET 指令 比如: SET(HELLO_SOURCE_PATH main. c)
常见变量
序号 | 语句 | 注释 |
---|---|---|
1 | PROJECT_BINARY_DIR、PROJECT_SOURCE_DIR CMAKE_BINARY_DIR、CMAKE_SOURCE_DIR | 工程目标文件目录 工程源文件目录 |
2 | CMAKE_CURRENT_BINARY_DIR CMAKE_CURRENT_SOURCE_DIR | 指当前处理的 CMakeLists.txt所在的路径。 |
3 | CMAKE_CURRENT_LIST_FILE CMAKE_CURRENT_LIST_LINE | 输出调用这个变量的 CMakeLists.txt 的路径及行号 |
4 | <project name>_BINARY_DIR <project name>_SOURCE_DIR | project name 工程目标文件project name 源目标文件 |
5 | EXECUTABLE_OUTPUT_PATH | 最终目标二进制文件存放目录 |
6 | LIBRARY_OUT_PATH | 最终目标库文件存放目录 |
7 | CMAKE_INSTALL_PREFIX | 目标文件安装目录,默认目录为 /usr/local/bin |
8 | CMAKE_MODULE_PATH | 定义自己的 CMake 模块所在路径 |
9 | PROJECT_NAME | 返回通过 PROJECT 指令定义的值 |
10 | CMAKE_INCLUDE_CURRENT_DIR | 自动添加CMAKE_CURRENT_BINARY_DIR 和CMAKE_CURRENT_SOURCE_DIR添 加到当前 CMakeLists.txt 处理。 |
11 | CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE | 将工程提供的头文件目录始终至于 系统头文件目录前面 |
12 | CMAKE_MAJOR_VERSION CMAKE_MINOR_VERSION CMAKE_PATCH_VERSION | CMake主版本号,2.4.6中的2 CMake次版本号,2.4.6中的4 CMake的补丁等级,2.4.6中的 6 |
13 | CMAKE_SYSTEM CMAKE_SYSTEM_NAME CMAKE_SYSTEM_VERSION CMAKE_SYSTEM_PROCESSOR | 系统名称,如 Linux-2.6.26 Linux 2.6.26 I386 |
14 | UNIX WIN32 | 在所有的类 UNIX 平台值为 **TRUE,**包括 MacOS 和 Cygwin 在所有的 WIN32 平台值为 TRUE,包括Cygwin |
15 | CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS | 开关选项,用来控制 if else 的书写方式 |
16 | BUILD_SHARED_LIBS | 开关,默认为静态库 |
17 | CMAKE_C_FLAGS CMAKE_CXX_FLAGS | 设置 C 编译选项 设置 C++ 编译选项 |
控制语句
if语句
#IF 指令,基本语法为:
IF(expression)
COMMAND1(ARGS)
ELSE(expression)
COMMAND2(ARGS)
ENDIF(expression)
# 另外一个指令是 ELSEIF,总体把握一个原则,凡是出现 IF 的地方一定要有对应的ENDIF.出现 ELSEIF 的地方,ENDIF 是可选的。
IF(var) # 如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或<var>_NOTFOUND 时,表达式为真。
IF(NOT var ) # 与上述条件相反。
IF(var1 AND var2) # 当两个变量都为真是为真。
IF(var1 OR var2) # 当两个变量其中一个为真时为真。
IF(COMMAND cmd) # 当给定的 cmd 确实是命令并可以调用是为真。
IF(EXISTS dir) # 当目录名存在时为真
IF(EXISTS file) # 当文件名存在时为真。
IF(file1 IS_NEWER_THAN file2) # 当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真,文件名请使用完整路径。
IF(IS_DIRECTORY dirname) # 当 dirname 是目录时,为真。
IF(variable MATCHES regex)
IF(string MATCHES regex) # 当给定的变量或者字符串能够匹配正则表达式 regex 时为真。
while语句
#WHILE 指令的语法是:
WHILE(condition)
COMMAND1(ARGS)
ENDWHILE(condition)
foreach语句
#FOREACH 指令的使用方法有三种形式:
#1,列表
FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS)
COMMAND2(ARGS)
ENDFOREACH(loop_var)
#举例如下:
FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)
#2,范围 从 0 到 total 以1为步进
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
# 举例如下:
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
ENDFOREACH(VAR) # 0 1 2 3 4 5 6 7 8 9 10
#3,范围和步进 从 start 开始到 stop 结束,以 step 为步进,
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
# 举例如下
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A) #5 8 11 14
代码块
- macro/function语法
macro(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(arg1)
...
endmacro([name])
function(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(arg1)
...
function([name])
- macro/function内部可用变量
变量 | 说明 |
---|---|
ARGV# | ARGV0为第一个参数,ARGV1为第二个参数,依次类推 |
ARGV | 定义宏(函数)时参数为2个,实际传了4个,则ARGV代表实际传入的两个 |
ARGN | 定义宏(函数)时参数为2个,实际传了4个,则ARGN代表剩下的两个 |
ARGC | 实际传入的参数的个数 |
- 两者的区别
宏的ARGN、ARGV等内部变量不能直接在if语句和foreach(…IN LISTS…)语句中使用
- include
可以包含*.cmake文件(广义的代码块)
include(cmake/version.cmake)
注释:
- CMakeLists.txt一般为工程管理的主文件, *.cmake文件一般为独立的代码块文件
- CMakeLists.txt一般采用add_subdirectory方式,*.cmake文件一般采用include
常用指令
序号 | 指令 | 注释 |
---|---|---|
1 | PROJECT(project name [CXX][C][Java]) | 定义工程名称(工程名与生成的目标文件名称是没有任何关系的)。此条指令隐含了两个变量 _BINARY_DIR _SOURCE_DIR |
2 | SET(var [value] [cache type docstring[force]]) | 自定义变量指令 set( SRC_LIST main.cpp hello.cpp) set(SRC_LIST “main.cpp” “test.cpp”) |
3 | MASSEGE([SEND_ERROR|STATUS|FATAL_ERROR] “massage to display” …) | SEND_ERROR:产生错误,生成过程被跳过 STATUS:输出前缀为—的信息FATAL_ERROR:立即终止所有 CMake过程 |
4 | $ENV{NAME} SET(ENV{变量名}值) | 调用系统环境变量 设置环境变量值 |
5 | INCLUDE(file [OPTIONAL]) INCLUDE(module [OPTIONAL]) | 用来载入 CMakeLists.txt 文件或者 CMake 模块 |
6 | CMAKE_MINIMUM_REQUIRED(VERSION version_num [FATAL_ERROR]) | 检查 CMake 版本,若不满足,产生 错误提示或退出 |
7 | SET_TARGET_PROPERTIES(target1 target2 … PROPERTIES prop1 value prop2 value2 …) | 设置目标输出的名字及属性 |
8 | GET_TARGET_PROPERTIES(VAR target property) | 获取目标的属性 |
9 | ADD_EXECUTABLE(target source_file…) | 增加可执行目标文件, target 由 source_file 生成 |
10 | ADD_LIBRARY(libname [SHARED|STATIC| MODULE] [EXCLUDE_FROM_ALL] source1 source2 … sourceN) | STATIC 为静态链接库,SHARED 为动态链接库,而 MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。 |
11 | ADD_DEFINITIONS | 向编译器添加-D 定义 |
12 | ADD_DEPENDENCIES(target_name depend_target1 depend_target) | 定义 target 依赖的其他 target |
13 | ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE FROM ALL]) | 增加子目录 |
14 | INCLUDE_DIRECTORIES/TARGET_INCLUDE_DIRECTORIES | 包含目录 |
15 | LINK_DIRECTORIES/TARGET_LINK_DIRECTORIES | 链接目录 |
16 | LINK_LIBRARIES /TARGET_LINK_LIBRARIES | 库文件 |
17 | INSTALL(TARGETS targets [ [ARCHIVE|LIBRARY|RUNTIME] [DESTINATION
| 安装目标文件 ARCHIVE 静态库文件LIBRARY 动态库文件RUNTIME 可执行文件 DESTINATION 定义安装路径,如果是绝对路径则覆盖了CMAKE_INSTALL_PREFIX,否则是指相对 CMAKE_INSTALL_PREFIX 的相对路径 |
18 | INSTALL(FILES files [ [DESTINATION
| 安装普通文件 可以指定权限,如果不指定,则默认是 644 权限 |
19 | INSTALL(DIRECTORY dirs [ [DESTINATION
| 安装目录 可以指定权限,如果不指定,则默认是 644 权限 |
20 | EXEC_PROGRAM(program [ARGS args] [OUTPUT_VARIABLE var] [RETURN_VALUE value]) | ARGS 用于添加参数 OUTPUT_VARIABLE 用于获取命令输出 RETURN_VALUE 用于获取返回值 |
21 | FILE 指令 FILE(WRITE filename“message” …) FILE(APPEND filename “message” …) FILE(READ filename variable) FILE(GLOB variable [RELATIVE path] [globing expressions]…) FILE(GLOB_RECURSE variable [RELATIVE path] [globing expressions]…) FILE(REMOVE [directory]…) FILE(REMOVE_RECURSE [directory]…) FILE(MAKE_DIRECTORY [directory]…) FILE(RELATIVE_PATH variable directory file) FILE(TO_CMAKE_PATH path result) FILE(TO_NATIVE_PATH path result) | 写文件 添加内容到文件 读文件 移除目录 递归移除目录创建目录 |
22 | FIND指令 FIND_FILE(name1 path1 path2 …) FIND_LIBRARY(name1 path1 path2 …) FIND_PATH(name1 path1 path2 …) FIND_PROGRAM(name1 path1 path2 …) FIND_PACKAGE( [major.minor] [QUITE] [NO_MODULE][[REQUIRED|COMPONENTS] [components…]]) | VAR 变量name1 代表找到的文件全路径,包含文件名 VAR 变量name2 代表找到的文件全路径,包含库文件名 VAR 变量代表包含这个文件的路径VAR 变量代表包含这个程序的全路径 |
更多信息
去CMake官网https://cmake.org/cmake/help/v3.22/查找。如list。
工程实践
了解CMake
- 内部构建与外部构建
- 内部构建:源文件与生成的临时文件混在一起
- 外部构建:源文件与生成的临时文件分开
-
CMake、 configure与generate
- configure配置CMake里面的变量
- generate 产生具体的文件
- CMake 上面两个命令依次执行
-
CMake产生了什么?
- makefile
- .sln
从程序的工作过程说起
我们知道从我们的源代码到最终的二进制可执行文件(可执行程序,动态库,静态库),需要四个大的步骤:
- 预编译
- 编译
- 汇编
- 链接
具体细节可以参考《程序员的自我修养》,大体流程如下图:
静态库、动态库的区别来自链接阶段。如何处理库,链接成可执行的程序?这是链接器的工作,但CMake要为链接器提供库的信息,如何哪些信息,如何提供是我们要思考的。我们先来了解一下静态库和动态库吧。
静态库
- 定义:
链接阶段,库中目标文件所含的所有将被程序使用的函数的机器码,被复制到最终的可执行文件中。因此对应的链接方式称为静态链接。
- 特点:
- 静态库对函数库的链接是放在编译时期完成的;
- 程序在运行时与函数库再无瓜葛,移植方便;
- 运行效率相对快;
- 占用磁盘和内存空间,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
- 局限性
- 空间浪费是静态库的一个问题;
- 静态库对程序的更新、部署和发布页会带来 麻烦;
- 如果静态库lib更新了,所以使用它的应用程 序都需要重新编译、发布给用户;
- 对于玩家来说,可能是一个很小的改动,却 导致整个程序重新下载,全量更新。
动态库
- 定义:
程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入
- 特点:
- 可执行文件只包含它需要的函数的引用表,而不是所有的函数代码;
- 只有在程序执行时, 那些需要的函数代码才被拷贝到内存中;
- 动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
- 优缺点:
-
对库函数的链接载入推迟到程序运行的时期;
-
实现进程之间的资源共享,也称为共享库;
-
一些程序升级变得简单,增量更新;
-
但依赖的模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出 错误信息,依赖性强。
总结
静态库是牺牲了空间效率,换取了时间效率;
动态库是牺牲了时间效率,换取了空间效率。
工程应用
- 一般在比较固定参数的工程项目中,如底层驱动逻辑比较稳定,会考虑静态库的使用;
- 在需要多次调试优化版本中,比如PUBG(吃鸡)的游戏平衡体验更新,会考虑动态库的使用。
工程实践
如何生成可执行文件,动态库,静态库?
- 可执行文件=> add_executable
- 库文件=>add_library
示例
cmake_minimum_required(VERSION 3.1.6)
project(test)
include_directories(include)
add_executable(test main.cpp) # test.exe
add_library(test_lib STATIC main.cpp) # .lib/.a
add_library(test_lib_dll SHARED main.cpp) # .dll/.so
# target 不能同名 但是需要静态库和动态库同名
set_target_properties(test_lib PROPERTIES OUT_PUT "test")
set_target_properties(test_lib_dll PROPERTIES OUT_PUT "test")
如果可执行文件依赖源码生成的库文件呢?即内部库的依赖
- 先add_lib在target_link_library
如何链接一个外部的库呢?
- 我们需要什么?
- 头文件
- 库文件
- 链接库的名字
- 库的接口文件即头文件
- 库所在位置
- 导入的方法有哪几种?
- 绝对路径
- find_library
- find_package
利用绝对路径的方式导入
- include目录
- link目录
- link库文件
cmake_minimum_required(VERSION 3.1.6)
project(test)
set(d_lib D:/lib/)
include(${d_lib}/include)
add_executable(test main.cpp)
target_link_libraries(test ${d_lib}/lib/d_lib.lib)
依赖路径必须是绝对路径,可移植性和通用性不强; 导入方法太笨,有没有更有效的方式来管理库的依赖 ??
用find_library的方式导入
- Include目录
- find_library
cmake_minimum_required(VERSION 3.1.6)
project(test)
set(d_lib ../lib/)
include(${d_lib}/include)
find_library(test_lib ${d_lib}/lib/)
add_executable(test main.cpp)
target_link_libraries(test ${test_lib})
搜索路径:
- 名为
<PackageName>_ROOT
的cmake变量或环境变量。
- CMake3.12新增。设定CMP0074 Policy来关闭。
- 如果定义了<PackageName>_DIR变量,那么<PackageName>_ROOT 不起作用。
- cmake特定的缓存变量:
- CMAKE_PREFIX_PATH
- CMAKE_FRAMEWORK_PATH
- CMAKE_APPBUNDLE_PATH
- 可以通过设定
NO_CMAKE_PATH
来关闭这一查找顺序
- cmake特定的环境变量
- <PackageName>_DIR
- CMAKE_PREFIX_PATH
- CMAKE_FRAMEWORK_PATH
- CMAKE_APPBUNDLE_PATH
- 可以通过
NO_CMAKE_ENVIRONMENT_PATH
来跳过。
-
HINT字段指定的路径
-
搜索标准的系统环境变量PATH。
- 其中如果是以/bin或者/sbin结尾的,会自动转化为其父目录。
- 通过指定
NO_SYSTEM_ENVIRONMENT_PATH
来跳过。
- 设定为当前系统定义的cmake变量:
- CMAKE_SYSTEM_PREFIX_PATH
- CMAKE_SYSTEM_FRAMEWORK_PATH
- CMAKE_SYSTEM_APPBUNDLE_PATH
- 通过设定
NO_CMAKE_SYSTEM_PATH
来跳过。
- 从
PATHS
字段指定的路径中查找。
用find_package的方式导入
- CMAKE_MOUDLE_PATH/CMAKE_INSTALL_PREFIX
- find_package
cmake_minimum_required(VERSION 3.1.6)
project(test)
set(CMAKE_MODULE_PATH ../lib/)
#set(CMAKE_INSTALL_PREFIX ../lib/)
find_package(test_lib REQUIRED)
add_executable(test main.cpp)
target_link_libraries(test ${test_lib})
find_package的原理
前提知识:
- 由于cmake不提供搜索库的方法,不会对库本身的环境变量进行设置。
- 两种模式:模块模式和配置模式,分为module和config;
- 指令按照优先级顺序在指定路径查找Findxxx.cmake和xxxConfig.cmake;
- cmake能够找到这两个文件中的任何一个,我们都能成功使用该库。
搜索路径:
- Moudle:
- CMAKE_MODULE_PATH(自己指定)
- cmake module directory(cmake安装时的Modules目录
- Config:
https://cmake.org/cmake/help/v3.22/command/find_package.html?highlight=find_package
- 名为
<PackageName>_ROOT
的cmake变量或环境变量。
- CMake3.12新增。设定CMP0074 Policy来关闭。
- 如果定义了<PackageName>_DIR变量,那么<PackageName>_ROOT 不起作用。
- cmake特定的缓存变量:
- CMAKE_PREFIX_PATH
- CMAKE_FRAMEWORK_PATH
- CMAKE_APPBUNDLE_PATH
- 可以通过设定
NO_CMAKE_PATH
来关闭这一查找顺序
- cmake特定的环境变量
- <PackageName>_DIR
- CMAKE_PREFIX_PATH
- CMAKE_FRAMEWORK_PATH
- CMAKE_APPBUNDLE_PATH
- 可以通过
NO_CMAKE_ENVIRONMENT_PATH
来跳过。
-
HINT字段指定的路径
-
搜索标准的系统环境变量PATH。
- 其中如果是以/bin或者/sbin结尾的,会自动转化为其父目录。
- 通过指定
NO_SYSTEM_ENVIRONMENT_PATH
来跳过。
- 存储在cmake的"User Package Registry"(用户包注册表)中的路径。
- 通过设定
NO_CMAKE_PACKAGE_REGISTRY
来跳过 - 设定
CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY
为true, 来避开。
- 设定为当前系统定义的cmake变量:
- CMAKE_SYSTEM_PREFIX_PATH
- CMAKE_SYSTEM_FRAMEWORK_PATH
- CMAKE_SYSTEM_APPBUNDLE_PATH
- 通过设定
NO_CMAKE_SYSTEM_PATH
来跳过。
- 在cmake的"System Package Registry"(系统包注册表)中查找。
- 通过设定
NO_CMAKE_SYSTEM_PACKAGE_REGISTRY
跳过。 - 设定
CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY
为true,来避开。
- 从
PATHS
字段指定的路径中查找。
PATH`来跳过。
- 存储在cmake的"User Package Registry"(用户包注册表)中的路径。
- 通过设定
NO_CMAKE_PACKAGE_REGISTRY
来跳过 - 设定
CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY
为true, 来避开。
- 设定为当前系统定义的cmake变量:
- CMAKE_SYSTEM_PREFIX_PATH
- CMAKE_SYSTEM_FRAMEWORK_PATH
- CMAKE_SYSTEM_APPBUNDLE_PATH
- 通过设定
NO_CMAKE_SYSTEM_PATH
来跳过。
- 在cmake的"System Package Registry"(系统包注册表)中查找。
- 通过设定
NO_CMAKE_SYSTEM_PACKAGE_REGISTRY
跳过。 - 设定
CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY
为true,来避开。
- 从
PATHS
字段指定的路径中查找。
如果文章有用,欢迎点赞、打赏、转发。最重要的还是要谢谢大家的支持,我会一如既往地推送深度好文…
![]() | ![]() |