# CMake 使用 ## 概述 cmake的使用围绕着 编译选项 头文件 库文件搜索路径,需要编译哪些源文件,生成动态库,静态库和可执行文件 ### cmake 命令 ```shell # 配置阶段 -B # 构建生成的文件存放目录 -S # CMakeLists.txt 文件所在目录 -G # 所选工具集 可以通过--help 查看支持哪些工具集 # 如果需要传递给cmake参数,-Dxxxx=xxx # 编译 --build # 后面就是构建生成的文件存放目录 --install # 安装 后面是建生成的文件存放目录 --prefix 安装路径 ``` ### cmake方法 工程相关的方法看下面的例子 ```cmake set(key value) #定义变量 也可以拼接变量 # 使用时通过 ${key} 取值 set(变量名1 ${变量名1} ${变量名2} ...) list(APPEND [ ...]) # list 也可以移除 list(REMOVE_ITEM [ ...]) include(path/xx.cmake) # 加载指定路径下的.cmake文件,并执行其中的命令 add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) # source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录 # binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。 # EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标。 ``` ### cmake中常见的预设变量 下面的列表中为大家整理了一些CMake中常用的宏: | 宏 | 含义 | | ------------------------ | ------------------------------------------------------------ | | CMAKE_CURRENT_SOURCE_DIR | 当前处理的CMakeLists.txt所在的路径 | | CMAKE_CURRENT_BINARY_DIR | target 编译目录 | | PROJECT_SOURCE_DIR | 使用cmake命令后紧跟的目录,一般是工程的根目录 | | EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制可执行文件的存放位置 | | LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件的存放位置 | | PROJECT_NAME | 返回通过PROJECT指令定义的项目名称 | | CMAKE_BINARY_DIR | 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径 | | | | | | | ```cmake # 当前处理的 CMakeLists.txt 文件所在的路径 message("CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}") # 顶层 CMakeLists.txt 文件所在的路径(源代码根目录) message("CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}") # 顶层构建目录路径 message("CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}") # 当前处理的 CMakeLists.txt 所在的源代码目录路径 message("CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}") # 当前处理的 CMakeLists.txt 文件的构建目录路径 message("CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}") # 项目名称(由顶层 project() 定义) message("CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME}") # CMake 可执行文件路径 message("CMAKE_COMMAND: ${CMAKE_COMMAND}") # C 编译器路径 message("CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}") # C++ 编译器路径 message("CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}") # 安装路径前缀 message("CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") CMAKE_CXX_STANDARD # c++ 使用标准 CMAKE_C_STANDARD # C语言标准 ``` ### 日志 在CMake中可以用用户显示一条消息,该命令的名字为message: ```cmake message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...) ``` + (无) :重要消息 + STATUS :非重要消息 + WARNING:CMake 警告, 会继续执行 + AUTHOR_WARNING:CMake 警告 (dev), 会继续执行 + SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤 + FATAL_ERROR:CMake 错误, 终止所有处理过程 CMake的命令行工具会在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。 CMake警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。 ```cmake # 输出一般日志信息 message(STATUS "source path: ${PROJECT_SOURCE_DIR}") # 输出警告信息 message(WARNING "source path: ${PROJECT_SOURCE_DIR}") # 输出错误信息 message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}") ``` #### 输出一般日志信息 message(STATUS "source path: ${PROJECT_SOURCE_DIR}") #### 输出警告信息 message(WARNING "source path: ${PROJECT_SOURCE_DIR}") #### 输出错误信息 message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}") ### 构建基本工程 ```shell # 文件 │ CMakeLists.txt │ main.cpp │ tools.cpp │ └─include tools.h ``` 生成可执行文件 ```cmake cmake_minimum_required(VERSION 3.14)# 指定使用的 cmake 的最低版本 # 定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可 project(tools) set(CMAKE_CXX_STANDARD 17) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_LIST_DIR}/bin) # 设置可执行文件的输出路径 link_directories("C:/thirdpart/lib") # 搜索库文件目录 每一条命令只能包含一个路径 # ${CMAKE_CURRENT_LIST_DIR} 变量是最常用的变量之一,表示当前处理的CMakeLists.txt所在的路径,也就是项目的根目录 include_directories(${CMAKE_CURRENT_LIST_DIR}/include) # 搜索头文件目录 每一条命令只能包含一个路径 file(GLOB headers include/*.h*) # 搜索include文件夹下所有h hpp头文件 file(GLOB src ${CMAKE_CURRENT_LIST_DIR}/*.cpp) # 将当前目录下所有cpp文件后缀的源文件, 赋值给src # GLOB 参数 搜索给定路径下的所有匹配的文件,并将文件列表存储在变量中,不会递归搜索子目录,参数 GLBO_RECURSE 会递归搜索子目录 # aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/ src) # 将指定目录下的所有源文件的文件名存入变量名 src 中 与file功能一致 add_executable(${PROJECT_NAME} ${src} ${headers}) # 生成可执行文件 ${PROJECT_NAME} 为当前项目的名称 ${src} 为源文件列表 target_link_libraries(${PROJECT_NAME} PUBLIC datachannel) # 链接库 以公有的方式链接 datachannel 库 # add_library(${PROJECT_NAME} SHARED ${src} ${headers}) # 生成动态库 # add_library(${PROJECT_NAME} STATIC ${src} ${headers}) # 生成静态库 # 构建生成vs工程 # cd root/ cmake -B build -G "Visual Studio 17 2022" -S . # cmake --build build --config Debug ``` 生成的vs工程 ![image-20241109130700149](/assert/image-20241109130700149.png) 可以看出来,只有将源文件加载到了工程中,看不到头文件 需要搜索头文件,并加入生成可执行文件的依赖 ```cmake file(GLOB headers include/*.h*) # 搜索include文件夹下所有h hpp头文件 add_executable(${PROJECT_NAME} ${src} ${headers}) # 生成可执行文件不仅需要源文件还需要头文件 tip:只是针对vs ``` 再次构建编译后, 可以看到头文件也加载进来了 ![image-20241109131136773](/assert/image-20241109131136773.png) 针对构建特定目标,需要加载进来特定头文件等,而非全局 ```cmake # target_XXXXX target_link_directories(目标名字 PUBLIC | PRIVAET | INTERFACE xxxx) # 访问权限,默认为PUBLIC target_include_directories() target_link_libraries() # 就是跟下面的功能一样,只不过上面是针对特定目标 link_directories() include_directories() link_libraries() #eg: add_library(test1 STATIC test1.cpp) target_include_directories(test1 PUBLIC include/test1) add_library(test2 STATIC test2.cpp) target_include_directories(test1 PUBLIC include/test2) # test1 需要用到include/test1中的头文件,而test2依赖他所需要的头文件 # 当然不管需要不需要 直接全部用include_directories 全部加载进来也是可以的 # target_link_libraries 是更推荐的方式,因为它允许更精确的控制和管理链接库的依赖,特别是在大型项目中,它能够避免全局设置可能带来的问题。 # link_libraries 虽然简单,但在复杂的项目中可能会导致意外的问题,通常适用于简单的项目或临时设置。 # 建议在 CMake 项目中优先使用 target_link_libraries ``` + PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。 + PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库 + INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。 ### 流程控制 在 CMake 的 CMakeLists.txt 中也可以进行流程控制,也就是说可以像写 shell 脚本那样进行条件判断和循环。 #### 条件判断 ```cmake # 关于条件判断其语法格式如下: if() elseif() # 可选快, 可以重复 else() # 可选快 endif() # 在进行条件判断的时候,如果有多个条件,那么可以写多个elseif,最后一个条件可以使用else,但是开始和结束是必须要成对出现的,分别为:if和endif。 ``` 基本表达式 ```cmake # 基本表达式 if() # NOT AND OR #]=] 如果是基本表达式,expression 有以下三种情况:常量、变量、字符串。 如果是1, ON, YES, TRUE, Y, 非零值,非空字符串时,条件判断返回True 如果是 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串时,条件判断返回False #]=] # 逻辑判断 if( ) # 其实这就是一个取反操作,如果条件condition为True将返回False,如果条件condition为False将返回True。 如果cond1和cond2两个条件中至少有一个为True,返回True,如果两个条件都为False则返回False。 ``` 比较 ```cmake if( LESS ) if( GREATER ) if( EQUAL ) if( LESS_EQUAL ) if( GREATER_EQUAL ) # LESS:如果左侧数值小于右侧,返回True # GREATER:如果左侧数值大于右侧,返回True # EQUAL:如果左侧数值等于右侧,返回True # LESS_EQUAL:如果左侧数值小于等于右侧,返回True # GREATER_EQUAL:如果左侧数值大于等于右侧,返回True # 基于字符串的比较 if( STRLESS ) if( STRGREATER ) if( STREQUAL ) if( STRLESS_EQUAL ) if( STRGREATER_EQUAL ) # STRLESS:如果左侧字符串小于右侧,返回True # STRGREATER:如果左侧字符串大于右侧,返回True # STREQUAL:如果左侧字符串等于右侧,返回True # STRLESS_EQUAL:如果左侧字符串小于等于右侧,返回True #TRGREATER_EQUAL:如果左侧字符串大于等于右侧,返回True ``` #### 循环 在 CMake 中循环有两种方式,分别是:foreach和while。 使用 foreach 进行循环,语法格式如下: ```cmake foreach( ) endforeach() ``` 通过foreach我们就可以对items中的数据进行遍历,然后通过loop_var将遍历到的当前的值取出,在取值的时候有以下几种用法: ```cmake # 方法1 foreach( RANGE ) # RANGE:关键字,表示要遍历范围 # stop:这是一个正整数,表示范围的结束值,在遍历的时候从 0 开始,最大值为 stop。 # loop_var:存储每次循环取出的值 cmake_minimum_required(VERSION 3.2) project(test) # 循环 foreach(item RANGE 10) message(STATUS "当前遍历的值为: ${item}" ) endforeach() # 方法二 foreach( RANGE []) #[=[ 这是上面方法1的加强版,我们在遍历一个整数区间的时候,除了可以指定起始范围,还可以指定步长。 RANGE:关键字,表示要遍历范围 start:这是一个正整数,表示范围的起始值,也就是说最小值为 start stop:这是一个正整数,表示范围的结束值,也就是说最大值为 stop step:控制每次遍历的时候以怎样的步长增长,默认为1,可以不设置 loop_var:存储每次循环取出的值 #]=] cmake_minimum_required(VERSION 3.2) project(test) foreach(item RANGE 10 30 2) message(STATUS "当前遍历的值为: ${item}" ) endforeach() ``` ## 工程实践 ### 导出安装