【编程技巧】如何写好CMakeList【常见命令汇总】
一、问题背景
开发过程中经常需要构建新的C++项目。受限于工期,往往草草从旧项目中抄一个CMakeList,要么是包含的头文件或者导入的库过多,加重了编译负担,要么就是删减过程中多删了某个头文件或者依赖的库,导致开发流水线被阻塞。所以总需要返工反复调试修改CMakeList文件。
写好一个CMakeList对于现在的我来说确实绝非易事,所以产生了以初学者的心态,重头学习的想法,目标在于:
- 能优雅的写好CMakeList文件,不过多添加依赖库和头文件,也不在缺乏依赖的情况下commit代码。
- 编译过程报错时,能迅速分析定界是代码问题还是CMakeList问题,并实施修改。
二、CMakeList常用命令
(1) project(<项目名称> [LANGUAGES <语言列表>])
用于定义项目的名称和使用的编程语言(如 C、C++、Fortran 等),其中 [LANGUAGES <语言列表>]为可选项。例如:
project(MyProject)
project(MyProject LANGUAGES C CXX)
(2)cmake_minimum_required(VERSION <版本号>)
用于指定 CMake 的最低版本要求,例如:
cmake_minimum_required(VERSION 3.10)
(3)set(<变量> <值>)
设置变量值,例如设置编译选项,例如:
set(MY_VARIABLE "Hello, World!") # 字符串
set(IS_DEBUG TRUE) # bool值
set(MY_LIST "apple" "banana" "cherry") # 列表
再次使用set命令可以修改变量:
set(MY_VARIABLE "Hello, World!")
message(${MY_VARIABLE})
set(MY_VARIABLE "Goodbye, World!")
message(${MY_VARIABLE})
${MY_VARIABLE}
对变量取值,message
用于打印,以上命令将依次打印"Hello, World!",“Goodbye, World!”
(4)add_compile_options(<选项…>)
添加编译器选项,适用于整个项目:
add_compile_options(-Wall -Werror)
这条命令将 -Wall
和 -Werror
编译选项添加到所有目标中。
-Wall
:启用所有常见的警告。-Werror
:将所有警告视为错误,编译过程中如果有警告则会停止编译。
(5)add_executable(<目标名> <源文件…>)
以下命令声明了一个名为 MyApp
的可执行文件目标,并指定了两个源文件 main.cpp
和 utils.cpp
。
set(ProjectName MyApp)
add_executable(${ProjectName} main.cpp utils.cpp)
(6)add_library(<库名> [STATIC | SHARED | MODULE] <源文件…>)
add_library
用于声明库目标的命令。库可以是静态库(.a
或 .lib
)、动态库(.so
或 .dll
),或者是模块库。
set(LibName MyLib)
add_library(${LibName} STATIC lib.cpp)
STATIC
:生成静态库。SHARED
:生成动态库。MODULE
:生成模块库,通常用于插件系统。
(7)find_package(<包名> [版本] [REQUIRED] [COMPONENTS <组件列表>])
find_package
用于查找和加载外部库或工具的命令。
# 查找 Boost 库,并要求必须找到 filesystem 和 system 组件
find_package(Boost 1.65 REQUIRED COMPONENTS filesystem system)
# 链接 Boost 库
target_link_libraries(MyApp PRIVATE Boost::filesystem Boost::system)
(8)include_directories(<目录…>)
用于指定编译器在查找头文件时需要搜索的目录。通过使用 include_directories
,可以添加一个或多个目录,使编译器能够在这些目录中查找头文件。
include_directories(${CMAKE_SOURCE_DIR}/include${CMAKE_SOURCE_DIR}/src/include)
以上命令包含CMake路径下的include路径和src/include路径下的所有头文件
(9)target_include_directories(<目标> PRIVATE|PUBLIC|INTERFACE <目录…>)
用于指定编译目标(如可执行文件或库)需要包含的头文件搜索路径的一个命令。target_include_directories
与include_directories
的区别在于作用域,include_directories
的作用域为全局,target_include_directories
的作用域为编译目标。
add_library(my_library src/my_library.cpp) # 创建一个my_library的库文件
target_include_directories(my_library PRIVATE include/my_project) # 包含另一个项目的头文件
PRIVATE
: 包含目录仅对当前目标可用,不会传递给依赖于这个目标的其他目标。PUBLIC
: 包含目录不仅对当前目标可用,也会传递给所有依赖于这个目标的目标。INTERFACE
: 包含目录只传递给依赖于这个目标的目标,但不对当前目标产生影响。这个选项通常用于库的目标,以告诉使用该库的其他目标需要哪些额外的包含路径。
(10)target_link_libraries(<目标> PRIVATE|PUBLIC|INTERFACE <库1> [<库2> …])
用于指定编译目标(例如可执行文件或库)需要链接的库,而无需手动管理链接顺序或路径。
target_link_libraries(my_executable PRIVATE my_library)
PRIVATE
: 指定的库仅对当前目标有用,不会传递给依赖于这个目标的其他目标。PUBLIC
: 指定的库不仅对当前目标有用,还会传递给所有依赖于这个目标的目标。INTERFACE
: 指定的库只传递给依赖于这个目标的目标,但不对当前目标产生影响。这个选项主要用于库目标,以告诉使用该库的其他目标需要链接哪些额外的库。
(11)add_subdirectory(<子目录>)
包含子目录中的 CMakeLists.txt
,以构建该子目录的内容:
set(SUB_SRC sub/src)
add_subdirectory(${SUB_SRC})
(12)include(<文件>)
用于包含另一个CMake文件,例如配置文件等
config.cmake
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")function(print_message message)message(STATUS "Custom message: ${message}")
endfunction()
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject)include(config.cmake) # 引入配置文件
add_executable(my_executable src/main.cpp)
print_message("Hello, World!") # 使用config.cmake中的函数
(13)file(GLOB <variable> [RELATIVE <path>] <pattern> …)
根据模式匹配文件并将其路径存储到变量中的命令
<variable>
: 存储匹配结果的变量名。[RELATIVE <path>]
: 可选参数,用于指定结果路径相对于哪个目录。如果不指定,则结果路径是绝对路径或相对于当前源目录的路径。<pattern> ...
: 一个或多个文件匹配模式。模式可以包含通配符,如*
和?
。
file(GLOB SRC_FILES src/*.cpp src/*.h) # 收集 src 目录下的所有 .cpp 和 .h 文件
(14)AUX_SOURCE_DIRECTORY(<dir> <variable>)
用于获取指定目录中的所有源文件,并将这些文件的路径存储到一个变量中。与file(GLOB ...)
的不同点在于AUX_SOURCE_DIRECTORY
会递归地扫描指定目录及其子目录中的所有源文件。
<dir>
: 要扫描的目录路径。<variable>
: 存储找到的源文件路径的变量名。
AUX_SOURCE_DIRECTORY(src SRC_FILES) # 收集 src 目录下的所有源文件
message(STATUS "Source files: ${SRC_FILES}") # 输出收集到的文件列表
(16)install(TARGETS <目标> [DESTINATION <路径>])
用于指定如何安装目标(如可执行文件、静态库、动态库等)的命令
file(GLOB SRC_FILES src/*.cpp src/*.h) # 收集 src 目录下的所有 .cpp 和 .h 文件
install(DIRECTORY ${SRC_FILES} DESTINATION /usr/local/include) # 安装头文件到 /usr/local/include