目录
在大型项目中,CMake通过CMakeLists.txt
文件来管理项目的结构。这些文件通常分布在项目的不同目录层级中,每个CMakeLists.txt
文件负责该目录下源代码的编译、链接等构建任务。
- 顶层CMakeLists.txt:通常包含对整个项目的全局设置,如指定CMake的最小版本、设置项目名称和版本号、配置全局编译选项等。
- 模块级CMakeLists.txt:在项目的各个模块或子目录中,这些文件负责具体模块的编译和链接,可以包含模块特有的编译选项、源文件列表、库依赖等。
示例
如图,以下是一个大型项目使用CMake时项目结构示例
- bin目录用于存放可执行文件
- build目录用于存放cmake生成的构建的文件
- include目录存放头文件
- lib目录存放生成库文件
在这个结构中,源文件目录都包含一个CMakeLists.txt
文件,用于管理该目录下的构建任务。顶层CMakeLists.txt
文件负责整个项目的全局设置,而模块级和测试级的CMakeLists.txt
文件则负责具体模块的编译和测试任务。即需要做的事情如下:
1、通过 test_calc目录中的测试文件进行计算相关的测试
2、通过 test2_sort目录中的测试文件进行排序相关的测试
Linux的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:
根节点CMakeLists.txt中的变量全局有效
父节点CMakeLists.txt中的变量可以在子节点中使用
子节点CMakeLists.txt中的变量只能在当前节点中使用在CMake中,父子节点之间的关系主要通过目录结构和
add_subdirectory()
命令来建立。
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
source_dir
:指定了CMakeLists.txt
源文件和代码文件的位置,即子目录的路径。binary_dir
(可选):指定了输出文件的路径,一般不需要指定,可以忽略。EXCLUDE_FROM_ALL
(可选):表示在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。- 作用:用于在CMake中添加子目录。这个命令告诉CMake在构建过程中,除了当前目录(父目录)外,还需要处理指定的子目录。
示例:如有以下目录结构:
project/ ├── CMakeLists.txt (父目录) └── subdirectory/ └── CMakeLists.txt (子目录)
在父目录的
CMakeLists.txt
文件中,通过以下命令包含子目录:add_subdirectory(subdirectory)
这样,子目录中的
CMakeLists.txt
文件就能够访问和继承父目录中的设置和变量了。
根目录
根目录中的 CMakeLists.txt文件内容如下:
# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称和版本
project(CMAKE_TEST)
#生成debug版本, 可以进行gdb调试
set(CMAKE_BUILD_TYPE "Debug")
# 指定C++标准 C++14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
#指定输出的路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#在include目录下寻找头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
#设置项目库文件的输出路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
#设置项目库文件搜索路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
#设置静态库的名字
set(CALC_LIB calc)
set(SORT_LIB sort)
#设置可执行文件的名字
set(CALC test_calc)
set(SORT test_sort)
#添加子目录
add_subdirectory(calculate)
add_subdirectory(sort)
add_subdirectory(test_calc)
add_subdirectory(test_sort)
在根节点对应的文件中主要做了两件事情:定义全局变量和添加子目录。
- 定义的全局变量主要是给子节点使用,目的是为了提高子节点中的CMakeLists.txt文件的可读性和可维护性,避免冗余并降低出差的概率。
- 一共添加了四个子目录,每个子目录中都有一个CMakeLists.txt文件,这样它们的父子关系就被确定下来了。
calculate目录
calculate目录中的 CMakeLists.txt文件内容如下:
set(SRC_LIST add.cpp mult.cpp sub.cpp)
add_library(${CALC_LIB} STATIC ${SRC_LIST})
第一行:定义变量,将SRC_LIST的值设置为包含add.cpp
、mult.cpp
和sub.cpp
这三个文件名的列表。
第二行:生成静态库,静态库名字CALC_LIB是在根节点文件中定义的,并且根节点文件中定义生成的静态库会被放在根目录下的lib目录中。
test_calc目录
test_calc目录中的 CMakeLists.txt文件内容如下:
set(SRC_LIST calc.cpp)
add_executable(${CALC} ${SRC_LIST})
target_link_libraries(${CALC} ${CALC_LIB})
第一行:定义SRC_LIST的值为calc.cpp。
第二行:生成可执行程序,CALC变量是在根节点文件中定义的,并且根节点文件中定义生成的可执行程序会被放在根目录下的bin目录中。
第三行:指定可执行程序要链接的库文件,CALC_LIB是在根节点文件中定义的
此处的可执行程序链接的是静态库,最终静态库会被打包到可执行程序中,可执行程序启动之后,静态库也就随之被加载到内存中了。
链接静态库,也可以使用link_libraries()命令
link_libraries(library1 library2 ...)
library1
、library2
等是库文件的名字(不包括前缀lib
和后缀如.so
、.dll
、.a
等,除非你的库文件就是这样命名的),或者是CMake能够找到的库目标的名称。- 作用:用于向目标(如可执行文件或库)添加链接时所需的库
因为link_libraries()会全局地影响后续所有目标的链接库,这可能导致意外的链接依赖。因此,CMake实践更推荐使用
target_link_libraries()
来代替link_libraries()
,因为它允许你为特定的目标指定链接库,从而避免了全局污染。
sort目录
sort目录中的 CMakeLists.txt文件内容如下:
aux_source_directory(. SRC_LIST)
add_library(${SORT_LIB} SHARED ${SRC_LIST})
第一行:搜索当前目录下的所有源文件,并将其存储在SRC_LIST中。
第二行:生成动态库,动态库名字SORT_LIB是在根节点文件中定义的,并且根节点文件中定义生成的动态库会被放在根目录下的lib目录中。
在生成库文件的时候,这个库可以是静态库也可以是动态库,一般需要根据实际情况来确定。如果生成的库比较大,建议将其制作成动态库。
test_sort目录
test_sort目录中的 CMakeLists.txt文件内容如下:
set(SRC_LIST sort.cpp)
add_executable(${SORT} ${SRC_LIST})
target_link_libraries(${SORT} ${SORT_LIB})
第一行: 定义变量SRC_LIST并赋值为sort.cpp
第二行:生成可执行程序,SORT变量是在根节点文件中定义的,并且根节点文件中定义生成的可执行程序会被放在根目录下的bin目录中。
第三行:指定可执行程序要链接的库文件,SORT_LIB是在根节点文件中定义的
在生成可执行程序的时候,动态库不会被打包到可执行程序内部。当可执行程序启动之后动态库也不会被加载到内存,只有可执行程序调用了动态库中的函数的时候,动态库才会被加载到内存中,且多个进程可以共用内存中的同一个动态库,所以动态库又叫共享库。
构建项目
进入根目录下的build目录中,开始构建项目
如上图可以得到如下信息:
1、在项目根目录的lib目录中生成了静态库libcalc.a
2、在项目根目录的lib目录中生成了动态库libsort.so
3、在项目根目录的bin目录中生成了可执行程序test_calc
4、在项目根目录的bin目录中生成了可执行程序test_sort
在项目中,如果将程序中的某个模块制作成了动态库或者静态库并且在CMakeLists.txt 中指定了库的输出目录,而后其它模块又需要加载这个生成的库文件,此时直接使用就可以了,如果没有指定库的输出路径或者需要直接加载外部提供的库文件,此时就需要使用 link_directories 将库文件路径指定出来。
流程控制
在 CMake 的 CMakeLists.txt 中也可以进行流程控制,也就是说可以像写 shell 脚本那样进行条件判断和循环。
条件判断
CMake中的条件判断主要通过if
语句实现,其基本语法如下:
if(<condition>)
# 条件为真时执行的命令
<commands>
elseif(<condition>) # 可选块,可以重复
# 前面的if或elseif条件不满足时,检查这里的条件
<commands>
else() # 可选块
# 所有的if和elseif条件都不满足时执行的命令
<commands>
endif()
在条件判断中,如果有多个条件,那么可以写多个elseif,最后一个条件可以使用else,但是开始和结束是必须要成对出现的,分别为:if和endif。其中,<condition>
可以是各种表达式,包括基本表达式、逻辑判断、比较、文件操作等。
- 基本表达式:
if(<expression>)
- 常量:如
1
、ON
、YES
、TRUE
、Y
等表示真(True),0
、OFF
、NO
、FALSE
、N
、IGNORE
、NOTFOUND
、空字符串等表示假(False)。 - 变量:变量的值如果是非零值或非空字符串,则条件为真;否则为假。
- 字符串:处理方式与变量类似,但注意字符串的比较有专门的比较操作符。
- 常量:如
- 逻辑判断:
NOT
:逻辑取反。if(NOT <condition>)
AND
:逻辑与,所有条件都为真时,整个表达式为真。if(<cond1> AND <cond2>)
OR
:逻辑或,至少有一个条件为真时,整个表达式为真。if(<cond1> OR <cond2>)
- 比较:
- 数值比较:
LESS
、GREATER
、EQUAL
、LESS_EQUAL
、GREATER_EQUAL
。if(<variable|string> LESS <variable|string>)
- 字符串比较:
STRLESS
、STRGREATER
、STREQUAL
、STRLESS_EQUAL
、STRGREATER_EQUAL
。if(<variable|string> STRLESS <variable|string>)
- 文件和目录存在性检查:
EXISTS
、IS_DIRECTORY
、IS_SYMLINK
、IS_ABSOLUTE
等。if(EXISTS path-to-file-or-directory)
- 数值比较:
循环
CMake中的循环主要通过foreach
和while
命令实现,语法格式如下:
foreach
循环的基本语法如下:
foreach(<loop_var> <items>)
# 对items中的每个元素执行以下命令,每次循环将当前元素的值赋给loop_var
<commands>
endforeach()
<items>
可以是一个范围(使用RANGE
关键字指定起始值和结束值,可选指定步长),也可以是一个列表或字符串(使用LISTS
或ITEMS
关键字)。
while的语法格式如下:
while(<condition>)
<commands>
endwhile()
while循环比较简单,只需要指定出循环结束的条件即可。
示例
条件判断示例:
set(MY_VAR 42)
if(MY_VAR)
message("MY_VAR is true")
endif()
set(FILE_PATH "${PROJECT_SOURCE_DIR}/myfile.txt")
if(EXISTS ${FILE_PATH})
message("${FILE_PATH} exists.")
else()
message("${FILE_PATH} does not exist.")
endif()
循环示例:
# 遍历数字范围
foreach(item RANGE 1 5)
message(STATUS "Current item: ${item}")
endforeach()
# 遍历列表
set(my_list "apple" "banana" "cherry")
foreach(fruit IN LISTS my_list)
message(STATUS "Fruit: ${fruit}")
endforeach()
# 创建一个列表 NAME
set(NAME luffy sanji zoro nami robin)
# 得到列表长度
list(LENGTH NAME LEN)
# 循环
while(${LEN} GREATER 0)
message(STATUS "names = ${NAME}")
# 弹出列表头部元素
list(POP_FRONT NAME)
# 更新列表长度
list(LENGTH NAME LEN)
endwhile()
输出结果为: