CMake从入门到实践

6 篇文章 0 订阅

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)
常见变量
序号语句注释
1PROJECT_BINARY_DIR、PROJECT_SOURCE_DIR CMAKE_BINARY_DIR、CMAKE_SOURCE_DIR工程目标文件目录 工程源文件目录
2CMAKE_CURRENT_BINARY_DIR CMAKE_CURRENT_SOURCE_DIR指当前处理的 CMakeLists.txt所在的路径
3CMAKE_CURRENT_LIST_FILE CMAKE_CURRENT_LIST_LINE输出调用这个变量的 CMakeLists.txt 的路径及行号
4<project name>_BINARY_DIR <project name>_SOURCE_DIRproject name 工程目标文件project name 源目标文件
5EXECUTABLE_OUTPUT_PATH最终目标二进制文件存放目录
6LIBRARY_OUT_PATH最终目标库文件存放目录
7CMAKE_INSTALL_PREFIX目标文件安装目录,默认目录为 /usr/local/bin
8CMAKE_MODULE_PATH定义自己的 CMake 模块所在路径
9PROJECT_NAME返回通过 PROJECT 指令定义的值
10CMAKE_INCLUDE_CURRENT_DIR自动添加CMAKE_CURRENT_BINARY_DIR 和CMAKE_CURRENT_SOURCE_DIR添 加到当前 CMakeLists.txt 处理。
11CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE将工程提供的头文件目录始终至于 系统头文件目录前面
12CMAKE_MAJOR_VERSION
CMAKE_MINOR_VERSION
CMAKE_PATCH_VERSION
CMake主版本号,2.4.6中的2
CMake次版本号,2.4.6中的4
CMake的补丁等级,2.4.6中的 6
13CMAKE_SYSTEM CMAKE_SYSTEM_NAME CMAKE_SYSTEM_VERSION CMAKE_SYSTEM_PROCESSOR系统名称,如 Linux-2.6.26 Linux 2.6.26 I386
14UNIX WIN32在所有的类 UNIX 平台值为 **TRUE,**包括 MacOS Cygwin 在所有的 WIN32 平台值为 TRUE,包括Cygwin
15CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS开关选项,用来控制 if else 的书写方式
16BUILD_SHARED_LIBS开关,默认为静态库
17CMAKE_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)

注释:

  1. CMakeLists.txt一般为工程管理的主文件, *.cmake文件一般为独立的代码块文件
  2. CMakeLists.txt一般采用add_subdirectory方式,*.cmake文件一般采用include
常用指令
序号指令注释
1PROJECT(project name [CXX][C][Java])定义工程名称(工程名与生成的目标文件名称是没有任何关系的)。此条指令隐含了两个变量 _BINARY_DIR _SOURCE_DIR
2SET(var [value] [cache type docstring[force]])自定义变量指令 set( SRC_LIST main.cpp hello.cpp) set(SRC_LIST “main.cpp” “test.cpp”)
3MASSEGE([SEND_ERROR|STATUS|FATAL_ERROR] “massage to display” …)SEND_ERROR:产生错误,生成过程被跳过 STATUS:输出前缀为—的信息FATAL_ERROR:立即终止所有 CMake过程
4$ENV{NAME} SET(ENV{变量名}值)调用系统环境变量 设置环境变量值
5INCLUDE(file [OPTIONAL]) INCLUDE(module [OPTIONAL])用来载入 CMakeLists.txt 文件或者 CMake 模块
6CMAKE_MINIMUM_REQUIRED(VERSION version_num [FATAL_ERROR])检查 CMake 版本,若不满足,产生 错误提示或退出
7SET_TARGET_PROPERTIES(target1 target2 … PROPERTIES prop1 value prop2 value2 …)设置目标输出的名字及属性
8GET_TARGET_PROPERTIES(VAR target property)获取目标的属性
9ADD_EXECUTABLE(target source_file…)增加可执行目标文件, target 由 source_file 生成
10ADD_LIBRARY(libname [SHARED|STATIC| MODULE] [EXCLUDE_FROM_ALL] source1 source2 … sourceN)STATIC 为静态链接库,SHARED 为动态链接库,而 MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
11ADD_DEFINITIONS向编译器添加-D 定义
12ADD_DEPENDENCIES(target_name depend_target1 depend_target)定义 target 依赖的其他 target
13ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE FROM ALL])增加子目录
14INCLUDE_DIRECTORIES/TARGET_INCLUDE_DIRECTORIES包含目录
15LINK_DIRECTORIES/TARGET_LINK_DIRECTORIES链接目录
16LINK_LIBRARIES /TARGET_LINK_LIBRARIES库文件
17INSTALL(TARGETS targets [ [ARCHIVE|LIBRARY|RUNTIME] [DESTINATION ] [PERMISSIONS permissions…] [CONFIGGURATIONS [Debug|Release|…]] [COMPONENT ] [OPTIONAL] ][…]) 安装目标文件 ARCHIVE 静态库文件LIBRARY 动态库文件RUNTIME 可执行文件 DESTINATION 定义安装路径,如果是绝对路径则覆盖了CMAKE_INSTALL_PREFIX,否则是指相对 CMAKE_INSTALL_PREFIX 的相对路径
18INSTALL(FILES files [ [DESTINATION ] [PERMISSIONS permissions…] [CONFIGGURATIONS [Debug|Release|…]] [COMPONENT ] [OPTIONAL] ][…]) 安装普通文件 可以指定权限,如果不指定,则默认是 644 权限
19INSTALL(DIRECTORY dirs [ [DESTINATION ] [FILE_PERMISSIONS permissions…] [DIRECTORY_PERMISSIONS permissions…] [USE_SOURCE_PERMISSIONS permissions…] [CONFIGGURATIONS [Debug|Release|…]] [COMPONENT ] [[PATTERN | REGEX ] [EXCLUDE] [PERMISSIONS permissions…]] […]) 安装目录 可以指定权限,如果不指定,则默认是 644 权限
20EXEC_PROGRAM(program [ARGS args]
[OUTPUT_VARIABLE var]
[RETURN_VALUE value])
ARGS 用于添加参数
OUTPUT_VARIABLE 用于获取命令输出 RETURN_VALUE 用于获取返回值
21FILE 指令
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)
写文件 添加内容到文件 读文件   移除目录 递归移除目录创建目录
22FIND指令
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要为链接器提供库的信息,如何哪些信息,如何提供是我们要思考的。我们先来了解一下静态库和动态库吧。

静态库
  1. 定义:

链接阶段,库中目标文件所含的所有将被程序使用的函数的机器码,被复制到最终的可执行文件中。因此对应的链接方式称为静态链接。

  1. 特点:
  • 静态库对函数库的链接是放在编译时期完成的;
  • 程序在运行时与函数库再无瓜葛,移植方便;
  • 运行效率相对快;
  • 占用磁盘和内存空间,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

在这里插入图片描述

  1. 局限性
  • 空间浪费是静态库的一个问题;
  • 静态库对程序的更新、部署和发布页会带来 麻烦;
  • 如果静态库lib更新了,所以使用它的应用程 序都需要重新编译、发布给用户;
  • 对于玩家来说,可能是一个很小的改动,却 导致整个程序重新下载,全量更新
动态库
  1. 定义:

程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入

  1. 特点:
  • 可执行文件只包含它需要的函数的引用表,而不是所有的函数代码;
  • 只有在程序执行时, 那些需要的函数代码才被拷贝到内存中;
  • 动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

在这里插入图片描述

  1. 优缺点:
  • 对库函数的链接载入推迟到程序运行的时期;

  • 实现进程之间的资源共享,也称为共享库;

  • 一些程序升级变得简单,增量更新;

  • 但依赖的模块也要存在,如果使用载入时动态链接,程序启动时发现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
如何链接一个外部的库呢?
  1. 我们需要什么?
  • 头文件
  • 库文件
    • 链接库的名字
    • 库的接口文件即头文件
    • 库所在位置
  1. 导入的方法有哪几种?
  • 绝对路径
  • 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})

搜索路径:

  1. 名为<PackageName>_ROOT的cmake变量或环境变量。
  • CMake3.12新增。设定CMP0074 Policy来关闭。
  • 如果定义了<PackageName>_DIR变量,那么<PackageName>_ROOT 不起作用。
  1. cmake特定的缓存变量:
  • CMAKE_PREFIX_PATH
  • CMAKE_FRAMEWORK_PATH
  • CMAKE_APPBUNDLE_PATH
  • 可以通过设定NO_CMAKE_PATH来关闭这一查找顺序
  1. cmake特定的环境变量
  • <PackageName>_DIR
  • CMAKE_PREFIX_PATH
  • CMAKE_FRAMEWORK_PATH
  • CMAKE_APPBUNDLE_PATH
  • 可以通过NO_CMAKE_ENVIRONMENT_PATH来跳过。
  1. HINT字段指定的路径

  2. 搜索标准的系统环境变量PATH。

  • 其中如果是以/bin或者/sbin结尾的,会自动转化为其父目录。
  • 通过指定NO_SYSTEM_ENVIRONMENT_PATH来跳过。
  1. 设定为当前系统定义的cmake变量:
  • CMAKE_SYSTEM_PREFIX_PATH
  • CMAKE_SYSTEM_FRAMEWORK_PATH
  • CMAKE_SYSTEM_APPBUNDLE_PATH
  • 通过设定NO_CMAKE_SYSTEM_PATH来跳过。
  1. 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能够找到这两个文件中的任何一个,我们都能成功使用该库。

搜索路径:

  1. Moudle:
  • CMAKE_MODULE_PATH(自己指定)
  • cmake module directory(cmake安装时的Modules目录
  1. Config:

https://cmake.org/cmake/help/v3.22/command/find_package.html?highlight=find_package

  1. 名为<PackageName>_ROOT的cmake变量或环境变量。
  • CMake3.12新增。设定CMP0074 Policy来关闭。
  • 如果定义了<PackageName>_DIR变量,那么<PackageName>_ROOT 不起作用。
  1. cmake特定的缓存变量:
  • CMAKE_PREFIX_PATH
  • CMAKE_FRAMEWORK_PATH
  • CMAKE_APPBUNDLE_PATH
  • 可以通过设定NO_CMAKE_PATH来关闭这一查找顺序
  1. cmake特定的环境变量
  • <PackageName>_DIR
  • CMAKE_PREFIX_PATH
  • CMAKE_FRAMEWORK_PATH
  • CMAKE_APPBUNDLE_PATH
  • 可以通过NO_CMAKE_ENVIRONMENT_PATH来跳过。
  1. HINT字段指定的路径

  2. 搜索标准的系统环境变量PATH。

  • 其中如果是以/bin或者/sbin结尾的,会自动转化为其父目录。
  • 通过指定NO_SYSTEM_ENVIRONMENT_PATH来跳过。
  1. 存储在cmake的"User Package Registry"(用户包注册表)中的路径。
  • 通过设定NO_CMAKE_PACKAGE_REGISTRY来跳过
  • 设定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY为true, 来避开。
  1. 设定为当前系统定义的cmake变量:
  • CMAKE_SYSTEM_PREFIX_PATH
  • CMAKE_SYSTEM_FRAMEWORK_PATH
  • CMAKE_SYSTEM_APPBUNDLE_PATH
  • 通过设定NO_CMAKE_SYSTEM_PATH来跳过。
  1. 在cmake的"System Package Registry"(系统包注册表)中查找。
  • 通过设定NO_CMAKE_SYSTEM_PACKAGE_REGISTRY跳过。
  • 设定CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY为true,来避开。
  1. PATHS字段指定的路径中查找。

PATH`来跳过。

  1. 存储在cmake的"User Package Registry"(用户包注册表)中的路径。
  • 通过设定NO_CMAKE_PACKAGE_REGISTRY来跳过
  • 设定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY为true, 来避开。
  1. 设定为当前系统定义的cmake变量:
  • CMAKE_SYSTEM_PREFIX_PATH
  • CMAKE_SYSTEM_FRAMEWORK_PATH
  • CMAKE_SYSTEM_APPBUNDLE_PATH
  • 通过设定NO_CMAKE_SYSTEM_PATH来跳过。
  1. 在cmake的"System Package Registry"(系统包注册表)中查找。
  • 通过设定NO_CMAKE_SYSTEM_PACKAGE_REGISTRY跳过。
  • 设定CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY为true,来避开。
  1. PATHS字段指定的路径中查找。

如果文章有用,欢迎点赞、打赏、转发。最重要的还是要谢谢大家的支持,我会一如既往地推送深度好文…

替精神支柱大猫仔化缘了,喵~
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值