CMake简介
CMake 是一个项目构建工具,并且是跨平台的。关于项目构建我们所熟知的还有Makefile(通过 make 命令进行项目的构建),大多是IDE软件都集成了make,比如:VS 的 nmake、linux 下的 GNU make、Qt 的 qmake等,如果自己动手写 makefile,会发现,makefile 通常依赖于当前的编译平台,而且编写 makefile 的工作量比较大,解决依赖关系时也容易出错。而 CMake 恰好能解决上述问题, 其允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需make编译即可,所以可以把CMake看成一款自动生成 Makefile的工具。
CMake的主要特点:
- 跨平台支持: CMake 支持多种操作系统,包括 Windows、Linux、macOS,以及各种嵌入式系统。
- 生成器支持: CMake 可以生成多种构建系统文件,如 Unix Makefiles、Ninja 文件、Visual Studio 工程文件等。
- 模块化和可扩展性: 通过模块和脚本,可以扩展和自定义 CMake 的功能。
- 易于集成: CMake 很容易与外部库和工具集成,如 BOOST、Qt、OpenCV 等。
CMake和make、Makefile的关系
CMake、make 和 Makefile 是构建系统中密切相关但各自有不同角色的组成部分。
以下是它们之间的关系和作用:
CMake
- 角色: CMake 是一个跨平台的构建系统生成器,它的主要作用是生成适合于特定平台和编译器的构建文件。
- 输入: CMake 使用txt 文件作为输入,这些文件包含了项目的构建配置、依赖关系、编译选项等。
- 输出: CMake 根据配置生成平台和工具链特定的构建文件,例如 Makefile、Ninja 文件、Visual Studio 解决方案等。
Make 和 Makefile
- 角色:make 是一个构建自动化工具,使用 Makefile 文件来指导构建过程。它广泛用于 Unix/Linux 系统。
- 输入:make 读取 Makefile 文件,Makefile 文件定义了构建目标、依赖关系和构建规则。
- 输出:make 根据 Makefile 中的规则执行相应的命令,最终生成可执行文件、库文件等目标文件。
CMake 和 Make 的关系
- 生成 执行: CMake 的主要作用是生成构建系统文件,而make 的作用是读取这些文件并执行构建过程。当 CMake 生成 Makefile 时,make 执行这些 Makefile 来完成实际的编译和链接。
- 抽象层次: CMake 提供了一个更高层次的抽象,隐藏了具体平台和工具链的细节,而 Makefile 是更低层次的具体构建规则。
通过 CMake 生成 Makefile,然后使用 make 执行构建,可以实现跨平台和高效的构建过程。这种组合利用了 CMake 的高层次抽象和 make 的实际执行能力,是许多项目中常见的构建方式。
CMake的使用
CMake的工作流程:
- 编写txt 文件: 这是 CMake 的配置文件,用于描述项目的构建设置、依赖关系、编译选项等。
- 配置项目: 使用cmake 命令或图形工具(如 cmake-gui)配置项目,这一步会生成构建系统特定的文件。
- 生成项目: 在配置完成后,使用生成的构建系统文件(如 Makefile 或 Visual Studio 工程)来编译项目。
假设有一个项目目录结构如下:
/HelloWorld - CMakeLists.txt - main.cpp
CMakeLists.txt 内容如下:
cmake_minimum_required(VERSION 3.10) project(HelloWorld) add_executable(hello main.cpp)
main.cpp 内容如下:
#include <iostream> int main() { std::cout << "Hello, World!" << std::endl; return 0; }
在该目录下,运行以下命令配置和生成项目:
mkdir build cd build cmake .. cmake --build .
生成的可执行文件 hello 将位于 build 目录下。
CMakeLists.txt的编写
CMakeLists.txt 是 CMake 的核心配置文件,用于描述一个项目的构建信息。它包含了项目的元数据、源文件、生成目标、依赖关系以及各种构建选项。以下是对 CMakeLists.txt 文件的详细介绍和一些常用命令的说明。
以下是一个简单的 CMakeLists.txt 示例文件,用于构建一个包含单个源文件的可执行程序:
cmake_minimum_required(VERSION 3.10) # 设置最低要求的 CMake 版本 project(HelloWorld) # 定义项目名称 add_executable(hello main.cpp) # 定义可执行文件及其源文件
常用命令
- cmake_minimum_required:这个命令用于设置要求的最低 CMake 版本,从而确保使用该txt 文件时的兼容性。cmake_minimum_required(VERSION 3.10)
- project:定义项目的名称、版本信息等。这些信息可以用于后续配置中。project(HelloWorld VERSION 1.0 LANGUAGES CXX)
- add_executable:定义一个可执行文件及其源文件。add_executable(hello main.cpp)
- add_library:定义一个库文件及其源文件。add_library(MyLibrary STATIC mylibrary.cpp)
- target_link_libraries:将指定的库链接到目标(可执行文件或者库)。target_link_libraries(hello MyLibrary)
- include_directories:为项目添加额外的头文件搜索路径。include_directories(${PROJECT_SOURCE_DIR}/include)
- find_package:查找外部包或库,并设置相应的变量以便后续使用。find_package(Boost 1.70 REQUIRED COMPONENTS filesystem)
- set:设置变量的值,可以是简单变量或列表。set(CMAKE_CXX_STANDARD 11),set(SOURCES main.cpp helper.cpp)
- add_subdirectory:添加子目录,这样可以在一个项目中包含多个txt 文件,适用于大型项目。add_subdirectory(src)
- file: 文件操作命令,如 GLOB、GLOB_RECURSE 等。
查找和使用包
在CMake中,查找和使用外部包(libraries和modules)是一个常见且重要的任务。CMake 提供了多种方法来查找和使用这些包,包括 find_package() 和 find_library() 等指令。以下是一些常见的查找和使用包的方法及示例。
使用 find_package()
find_package() 是查找和使用外部包的主要指令。CMake 提供了许多内置的模块,可以查找常见的库,如 Boost、Qt 和 OpenCV。使用 find_package() 可以查找这些库,并自动设置相关的编译和链接标志。
基本用法:
find_package(<PackageName> [version] [REQUIRED] [components])
- <PackageName>: 包的名称。
- [version]: 可选,指定所需的版本。
- [REQUIRED]: 可选,如果指定则表示查找失败将导致配置失败。
- [components]: 可选,指定要查找的组件。
示例:
cmake_minimum_required(VERSION 3.10) project(FindPackageExample) # 查找 Boost 库 find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system) # 包含 Boost 头文件目录 include_directories(${Boost_INCLUDE_DIRS}) # 添加源文件 add_executable(my_app main.cpp) # 链接 Boost 库 target_link_libraries(my_app ${Boost_LIBRARIES}) # 查找 Qt 库 find_package(Qt5 COMPONENTS Widgets REQUIRED) # 包含 Qt 头文件目录 include_directories(${Qt5Widgets_INCLUDE_DIRS}) # 添加源文件 add_executable(my_app main.cpp) # 链接 Qt 库 target_link_libraries(my_app Qt5::Widgets)
使用 find_library()
find_library() 指令用于查找特定的库文件。例如,你可以使用它查找一个自定义的或不常见的库。
基本用法:
find_library(<VAR> <name> [path1 path2 ...] [NO_DEFAULT_PATH])
- <VAR>: 存储查找到的库路径的变量名称。
- <name>: 要查找的库的名称。
- [path1 path2 …]: 可选,指定要查找的路径。
- [NO_DEFAULT_PATH]: 可选,仅在指定路径中查找,不使用默认路径。
示例,查找自定义库:
假设你有一个自定义库 libmylib.so,位于 /usr/local/lib 目录下。
cmake_minimum_required(VERSION 3.10) project(FindLibraryExample) # 查找自定义库 find_library(MYLIB_LIBRARY NAMES mylib PATHS /usr/local/lib) # 包含库头文件目录 include_directories(/usr/local/include) # 添加源文件 add_executable(my_app main.cpp) # 链接自定义库 target_link_libraries(my_app ${MYLIB_LIBRARY})
使用 pkg_check_modules()
对于一些使用 pkg-config 的库,可以使用 pkg_check_modules() 查找并使用库。这需要先包含 FindPkgConfig 模块。
基本用法:
find_package(PkgConfig REQUIRED) pkg_check_modules(<prefix> [REQUIRED] <module1> [<module2> ...])
示例:查找使用 pkg-config 的库,假设你要查找并使用 glib-2.0 库。
cmake_minimum_required(VERSION 3.10) project(PkgConfigExample) # 包含 FindPkgConfig 模块 find_package(PkgConfig REQUIRED) # 使用 pkg-config 查找 glib-2.0 库 pkg_check_modules(GLIB REQUIRED glib-2.0) # 包含 GLib 头文件目录 include_directories(${GLIB_INCLUDE_DIRS}) # 添加源文件 add_executable(my_app main.cpp) # 链接 GLib 库 target_link_libraries(my_app ${GLIB_LIBRARIES})
在 CMake 中,除了 find_package() 和 find_library() 之外,还有 find_path() 和 find_file() 指令,用于查找头文件路径和任意文件路径。这些指令非常有用,特别是在查找自定义或不常见的文件时。
find_path()
find_path() 指令用于查找包含特定文件(如头文件)的目录路径。
基本用法:
find_path(<VAR> name1 [path1 path2 ...] [PATHS path1 path2 ...] [HINTS path1 path2 ...] [DOC "documentation string"] [NO_DEFAULT_PATH])
- <VAR>: 用于存储查找到的路径的变量名称。
- name1: 要查找的文件名。
- [path1 path2 …]: 可选,指定要查找的路径。
- [PATHS path1 path2 …]: 可选,指定额外的查找路径。
- [HINTS path1 path2 …]: 可选,给出一些提示路径,优先级高于PATHS。
- [DOC “documentation string”]: 可选,提供关于变量的文档字符串。
- [NO_DEFAULT_PATH]: 可选,仅在指定路径中查找,不使用默认路径。
示例:假设你有一个头文件 mylib.h,位于 /usr/local/include 目录下。
cmake_minimum_required(VERSION 3.10) project(FindPathExample) # 查找头文件路径 find_path(MYLIB_INCLUDE_DIR mylib.h PATHS /usr/local/include) # 包含头文件目录 include_directories(${MYLIB_INCLUDE_DIR}) # 添加源文件 add_executable(my_app main.cpp)
find_file()
find_file() 指令用于查找特定文件的路径。
基本用法:
find_file(<VAR> name1 [path1 path2 ...] [PATHS path1 path2 ...] [HINTS path1 path2 ...] [DOC "documentation string"] [NO_DEFAULT_PATH])
- <VAR>: 用于存储查找到的文件路径的变量名称。
- name1: 要查找的文件名。
- [path1 path2 …]: 可选,指定要查找的路径。
- [PATHS path1 path2 …]: 可选,指定额外的查找路径。
- [HINTS path1 path2 …]: 可选,给出一些提示路径,优先级高于PATHS。
- [DOC “documentation string”]: 可选,提供关于变量的文档字符串。
- [NO_DEFAULT_PATH]: 可选,仅在指定路径中查找,不使用默认路径。
示例:假设你有一个配置文件 config.json,位于 /etc/myapp 目录下。
cmake_minimum_required(VERSION 3.10) project(FindFileExample) # 查找配置文件路径 find_file(MY_CONFIG_FILE config.json PATHS /etc/myapp) # 打印配置文件路径 message("Config file found at: ${MY_CONFIG_FILE}") # 添加源文件 add_executable(my_app main.cpp)
依赖管理
在现代软件开发中,管理项目的依赖关系非常重要。CMake 提供了多种方法来管理依赖关系,包括但不限于外部库、内部库和第三方库。以下是一些常见的依赖管理策略和示例。
依赖管理策略:
- 使用find_package() 查找和配置外部库: 适用于常见的库,如 Boost、Qt、OpenCV 等。
- 使用ExternalProject 下载和构建外部项目: 适用于需要从外部来源(如 Git、URL)获取并构建的库。
- 使用FetchContent 下载和包含外部内容: 更简单的方式来下载和包含外部依赖项。
- 使用add_subdirectory() 添加子目录: 适用于项目中的内部库或直接包含在项目中的第三方库。
使用 ExternalProject
ExternalProject 模块提供了一种灵活的方法来下载、配置和构建外部项目。
示例:下载和构建 GoogleTest
cmake_minimum_required(VERSION 3.10) project(ExternalProjectExample) include(ExternalProject) ExternalProject_Add( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.10.0 CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR> UPDATE_COMMAND "" ) # 添加包含目录 include_directories(${CMAKE_BINARY_DIR}/include) # 添加测试可执行文件 add_executable(test_app test_main.cpp) # 链接 GoogleTest 库 add_dependencies(test_app googletest) target_link_libraries(test_app ${CMAKE_BINARY_DIR}/lib/libgtest.a ${CMAKE_BINARY_DIR}/lib/libgtest_main.a)
使用 FetchContent
FetchContent 模块提供了一种更简单的方法来下载和包含外部内容。
示例:使用 GoogleTest
cmake_minimum_required(VERSION 3.10) project(FetchContentExample) include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.10.0 ) FetchContent_MakeAvailable(googletest) # 添加测试可执行文件 add_executable(test_app test_main.cpp) # 链接 GoogleTest 库 target_link_libraries(test_app gtest gtest_main)
使用 add_subdirectory()
add_subdirectory() 是将子目录添加到构建中,并适用于直接包含在项目中的内部库或第三方库。
示例:添加内部库
目录结构
/MyProject - CMakeLists.txt - src - main.cpp - lib - MyLib - CMakeLists.txt - mylib.h - mylib.cpp
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10) project(MyProject) # 添加子目录 add_subdirectory(lib/MyLib) # 包含头文件目录 include_directories(lib/MyLib) # 添加源文件 add_executable(my_app src/main.cpp) # 链接内部库 target_link_libraries(my_app MyLib)
子目录 CMakeLists.txt (lib/MyLib/CMakeLists.txt)
cmake_minimum_required(VERSION 3.10) project(MyLib) # 添加库 add_library(MyLib mylib.cpp) # 包含头文件目录 target_include_directories(MyLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories
target_include_directories 用于为指定的目标添加头文件搜索路径。这对于确保编译器能够找到头文件至关重要。
基本用法:
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...])
- <target>: 目标名称。
- [SYSTEM]: 可选,将目录标识为系统包含路径,可能会抑制某些编译器警告。
- [AFTER|BEFORE]: 可选,指定包含路径的优先级。
- <INTERFACE|PUBLIC|PRIVATE>:
- INTERFACE: 仅在链接到该目标的目标中使用。
- PUBLIC: 在当前目标和链接到该目标的目标中使用。
- PRIVATE: 仅在当前目标中使用。
- [items1…]: 要添加的包含路径。
示例:
add_executable(my_app main.cpp) target_include_directories(my_app PUBLIC ${CMAKE_SOURCE_DIR}/include)
target_compile_definitions
target_compile_definitions 用于为指定的目标添加编译定义(宏)。这些定义通常用于条件编译代码。
基本用法:
target_compile_definitions(<target> [INTERFACE|PUBLIC|PRIVATE] [definitions1...])
- <target>: 目标名称。
- <INTERFACE|PUBLIC|PRIVATE>:
- INTERFACE: 仅在链接到该目标的目标中使用。
- PUBLIC: 在当前目标和链接到该目标的目标中使用。
- PRIVATE: 仅在当前目标中使用。
- [definitions1…]: 要添加的编译定义。
示例:
add_executable(my_app main.cpp) target_compile_definitions(my_app PRIVATE MY_DEFINE=1)
target_compile_options
target_compile_options 用于为指定的目标添加编译选项。这些选项可以是编译器特定的标志,用于控制编译行为。
基本用法:
target_compile_options(<target> [INTERFACE|PUBLIC|PRIVATE] [options1...])
- <target>: 目标名称。
- <INTERFACE|PUBLIC|PRIVATE>:
- INTERFACE: 仅在链接到该目标的目标中使用。
- PUBLIC: 在当前目标和链接到该目标的目标中使用。
- PRIVATE: 仅在当前目标中使用。
- [options1…]: 要添加的编译选项。
示例:
add_executable(my_app main.cpp) target_compile_options(my_app PRIVATE -Wall -Wextra)
条件控制和循环
CMake 提供了一系列用于条件控制和循环控制的指令,类似于编程语言中的条件语句和循环语句。这些指令使得 CMakeLists.txt 文件可以在不同的配置和生成环境中表现出灵活性和可定制性,从而实现更复杂的构建逻辑。如果你熟练掌握这些指令,可以大幅度提升 CMake 文件的可读性和可维护性。if/else/endif: 用于条件判断,执行不同的配置逻辑。
if/else/endif
if、else 和 endif 指令用于在 CMakeLists.txt 中进行条件判断,根据不同的条件执行不同的配置逻辑。
语法:
if(CONDITION) # Commands to execute if CONDITION is true elseif(OTHER_CONDITION) # Commands to execute if OTHER_CONDITION is true else() # Commands to execute if none of the above conditions are true endif()
示例:
set(MY_VAR TRUE) if(MY_VAR) message("MY_VAR is TRUE") else() message("MY_VAR is FALSE") endif() if(CMAKE_BUILD_TYPE STREQUAL "Debug") message("Building in Debug mode") elseif(CMAKE_BUILD_TYPE STREQUAL "Release") message("Building in Release mode") else() message("Unknown build type") endif()
foreach
foreach 指令用于遍历列表中的每一个元素,并对每个元素执行一系列命令。
语法:
foreach(VAR IN LISTS LIST_VARIABLE) # Commands to execute for each item in LIST_VARIABLE endforeach() foreach(VAR RANGE START STOP [STEP]) # Commands to execute for each number in the range from START to STOP endforeach()
示例:
set(MY_LIST "item1" "item2" "item3") foreach(ITEM IN LISTS MY_LIST) message("Item: ${ITEM}") endforeach() foreach(NUM RANGE 1 10 2) message("Number: ${NUM}") endforeach()
while
while 指令用于在条件为真时重复执行一系列命令,直到条件变为假。
语法:
while(CONDITION) # Commands to execute while CONDITION is true endwhile()
示例:
set(COUNTER 0) while(COUNTER LESS 5) message("Counter: ${COUNTER}") math(EXPR COUNTER "${COUNTER} + 1") endwhile()
break/continue
break 和 continue 指令用于控制循环的执行流程。
- break: 立即终止循环,不再执行循环体中的命令。
- continue: 跳过本次循环的剩余命令,直接开始下一个循环。
语法:
foreach(VAR IN LISTS LIST_VARIABLE) if(CONDITION) break() endif() # Commands to execute if CONDITION is false endforeach() while(CONDITION) if(OTHER_CONDITION) continue() endif() # Commands to execute if OTHER_CONDITION is false endwhile()
示例:
set(MY_LIST "item1" "item2" "stop" "item3") foreach(ITEM IN LISTS MY_LIST) if(ITEM STREQUAL "stop") break() endif() message("Item: ${ITEM}") endforeach() set(COUNTER 0) while(COUNTER LESS 5) math(EXPR COUNTER "${COUNTER} + 1") if(COUNTER EQUAL 3) continue() endif() message("Counter: ${COUNTER}") endwhile()
自定义命令和目标
add_custom_command
添加自定义命令,这些命令可以在构建过程中执行,如生成文件、拷贝文件等。
add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/generated.cpp COMMAND python3 ${CMAKE_SOURCE_DIR}/generate_code.py DEPENDS ${CMAKE_SOURCE_DIR}/generate_code.py )
add_custom_target
添加自定义目标,这些目标可以执行自定义命令,且不一定与构建项目直接相关。
add_custom_target( RunAllTests COMMAND ${CMAKE_CTEST_COMMAND} --verbose )
配置选项
在 CMake 中,set 和 option 是两个常用的指令,用于定义和配置变量。这两个指令用于不同的目的,掌握它们的用法可以帮助你更灵活地配置项目并控制构建流程。
set 指令
set 指令用于定义变量并赋值,这些变量可以是字符串、布尔值或列表。你可以使用 set 指令来定义在整个 CMakeLists.txt 文件中使用的变量。
# 字符串变量: set(MY_VAR "Hello, World!") message("MY_VAR: ${MY_VAR}") # 布尔变量: set(ENABLE_FEATURE ON) if(ENABLE_FEATURE) message("Feature is enabled") endif() # 列表变量: set(MY_LIST "item1" "item2" "item3") foreach(ITEM IN LISTS MY_LIST) message("List Item: ${ITEM}") endforeach() # CACHE 变量: # CACHE 选项用于将变量添加到 CMake 的缓存中,这样可以在后续的 CMake 配置过程中保留它们的值。 set(MY_CACHE_VAR "Default Value" CACHE STRING "Description of my cache variable") message("MY_CACHE_VAR: ${MY_CACHE_VAR}")
option 指令
option 指令用于定义布尔选项,这些选项可以在 CMake 配置过程中通过命令行传递。这使得用户可以在配置时启用或禁用特定功能或特性。
基本用法:
option(ENABLE_FEATURE "Enable the special feature" ON) if(ENABLE_FEATURE) message("Special feature is enabled") else() message("Special feature is disabled") endif()
使用命令行传递配置选项
可以在命令行通过 -D 选项传递 set 和 option 变量的值。
# 设置缓存变量和布尔选项 cmake -S . -B build -DMY_CACHE_VAR="New Value" -DBUILD_TESTS=OFF
常见 CMake 变量
CMake 提供了许多内置变量和用户定义变量,用于控制和定制构建过程。这些变量涵盖了编译器设置、构建类型、路径设置等许多方面。以下是一些常见的 CMake 变量及其用途。
编译器和工具链相关变量
- CMAKE_CXX_COMPILER: 指定 C++ 编译器。set(CMAKE_CXX_COMPILER “/usr/bin/g++”)
- CMAKE_C_COMPILER: 指定 C 编译器。set(CMAKE_C_COMPILER “/usr/bin/gcc”)
- CMAKE_LINKER: 指定链接器。set(CMAKE_LINKER “/usr/bin/ld”)
- CMAKE_AR: 指定静态库管理工具。set(CMAKE_AR “/usr/bin/ar”)
- CMAKE_RANLIB: 指定库索引生成工具。set(CMAKE_RANLIB “/usr/bin/ranlib”)
构建类型相关变量
- CMAKE_BUILD_TYPE: 指定构建类型(仅适用于单配置生成器,如 Makefile)。常见值:Debug、Release、RelWithDebInfo、MinSizeRel。set(CMAKE_BUILD_TYPE “Release”)
- CMAKE_CONFIGURATION_TYPES: 指定多配置生成器(如 Visual Studio)支持的构建类型。set(CMAKE_CONFIGURATION_TYPES “Debug;Release;MinSizeRel;RelWithDebInfo”)
编译选项相关变量
- CMAKE_CXX_FLAGS: 为所有 C++ 编译器设置附加编译标志。set(CMAKE_CXX_FLAGS “-Wall -Wextra”)
- CMAKE_C_FLAGS: 为所有 C 编译器设置附加编译标志。set(CMAKE_C_FLAGS “-Wall -Wextra”)
- CMAKE_EXE_LINKER_FLAGS: 设置链接可执行文件时的附加标志。set(CMAKE_EXE_LINKER_FLAGS “-s”)
路径相关变量
- CMAKE_SOURCE_DIR: 项目根目录下的源代码路径。message(“Source Directory: ${CMAKE_SOURCE_DIR}”)
- CMAKE_BINARY_DIR: 构建目录的根路径。message(“Binary Directory: ${CMAKE_BINARY_DIR}”)
- CMAKE_INSTALL_PREFIX: 指定 make install 安装的前缀路径。set(CMAKE_INSTALL_PREFIX “/usr/local”)
项目和目标相关变量
- PROJECT_NAME: 当前项目的名称。message(“Project Name: ${PROJECT_NAME}”)
- PROJECT_SOURCE_DIR: 当前项目的源代码路径。message(“Project Source Directory: ${PROJECT_SOURCE_DIR}”)
- CMAKE_PROJECT_NAME: 根项目的名称(当有多个项目时)。message(“CMake Project Name: ${CMAKE_PROJECT_NAME}”)
系统相关变量
- CMAKE_SYSTEM_NAME: 操作系统的名称。message(“System Name: ${CMAKE_SYSTEM_NAME}”)
- CMAKE_SYSTEM_VERSION: 操作系统的版本。message(“System Version: ${CMAKE_SYSTEM_VERSION}”)
- CMAKE_HOST_SYSTEM_NAME: 生成系统的操作系统名称。message(“Host System Name: ${CMAKE_HOST_SYSTEM_NAME}”)
- CMAKE_HOST_SYSTEM_VERSION: 生成系统的操作系统版本。message(“Host System Version: ${CMAKE_HOST_SYSTEM_VERSION}”)
CTest 相关变量
- BUILD_TESTING: 指示是否构建测试。option(BUILD_TESTING “Build the testing tree” ON)
- CMAKE_CTEST_COMMAND: CTest 的路径。message(“CTest Command: ${CMAKE_CTEST_COMMAND}”)
模块和宏
在 CMake 中,模块和宏是两种非常有用的工具,它们可以帮助你组织和简化构建脚本。了解如何编写和使用它们,可以极大地提高你的 CMakeLists.txt 文件的可读性和可维护性。
CMake 模块
CMake 模块是一种用于封装查找和配置外部包或执行特定任务的脚本。CMake 自带许多常用的模块,如 FindBoost.cmake、FindOpenCV.cmake 等。你也可以编写自定义模块来满足特定需求。
假设你想编写一个自定义模块 FindMyLib.cmake,用于查找和配置一个名为 MyLib 的库。
目录结构
/MyProject - CMakeLists.txt - cmake - FindMyLib.cmake - src - main.cpp - include - mylib.h - lib - libmylib.so
FindMyLib.cmake
# 查找头文件 find_path(MYLIB_INCLUDE_DIR mylib.h PATHS /usr/local/include /usr/include) # 查找库文件 find_library(MYLIB_LIBRARY NAMES mylib PATHS /usr/local/lib /usr/lib) # 如果找到头文件和库文件,设置变量 MYLIB_FOUND if(MYLIB_INCLUDE_DIR AND MYLIB_LIBRARY) set(MYLIB_FOUND TRUE) set(MYLIB_INCLUDE_DIRS ${MYLIB_INCLUDE_DIR}) set(MYLIB_LIBRARIES ${MYLIB_LIBRARY}) else() set(MYLIB_FOUND FALSE) endif() # 提供给 find_package() 使用的宏 include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MyLib DEFAULT_MSG MYLIB_INCLUDE_DIR MYLIB_LIBRARY)
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10) project(MyProject) # 设置模块路径 list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") # 查找 MyLib find_package(MyLib REQUIRED) # 包含头文件目录 include_directories(${MYLIB_INCLUDE_DIRS}) # 添加可执行文件 add_executable(my_app src/main.cpp) # 链接 MyLib 库 target_link_libraries(my_app ${MYLIB_LIBRARIES})
CMake 宏
CMake 宏是一种用于封装常用命令和逻辑的脚本,类似于函数,但没有返回值。宏可以帮助你简化重复的任务,并使 CMakeLists.txt 文件更具可读性。
假设你想编写一个宏,用于简化添加可执行文件并链接公共库的过程。
目录结构
/MyProject - CMakeLists.txt - cmake - MyMacros.cmake - src - main.cpp - other.cpp
MyMacros.cmake
macro(add_executable_with_libs exe_name src_files) # 添加可执行文件 add_executable(${exe_name} ${src_files}) # 链接公共库(假设公共库为 pthread 和 m) target_link_libraries(${exe_name} pthread m) endmacro()
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10) project(MyProject) # 包含自定义宏文件 include(${CMAKE_SOURCE_DIR}/cmake/MyMacros.cmake) # 使用宏添加可执行文件 add_executable_with_libs(my_app "src/main.cpp;src/other.cpp")
宏与函数的区别
CMake 中也有函数,与宏类似,但有一些关键区别:
- 作用域: 宏在调用它的作用域内执行,而函数有自己的作用域。
- 参数处理: 宏的参数直接替换文本,而函数的参数在局部变量中处理。
自定义函数
function(add_executable_with_libs exe_name src_files) # 添加可执行文件 add_executable(${exe_name} ${src_files}) # 链接公共库(假设公共库为 pthread 和 m) target_link_libraries(${exe_name} pthread m) endfunction()
你可以像使用宏一样使用这个函数,但请注意它们的作用域和参数处理方式不同。
小结
- 模块: 用于封装查找和配置外部包或执行复杂任务。将模块保存在一个专用的目录中,如cmake/,并通过 list(APPEND CMAKE_MODULE_PATH …) 将该目录添加到模块搜索路径。
- 宏与函数: 用于简化和重用构建逻辑。将宏和函数定义保存在一个专用的文件中,如cmake,并通过 include() 指令包含该文件。
测试和打包
在 CMake 中,测试和打包是两个非常重要的功能。测试可以确保你的代码在各种情况下都能正确运行,而打包可以方便地分发你的应用程序或库。CMake 提供了一套强大的工具来支持这两个功能。
测试
CMake 提供了 CTest 模块,用于集成测试。CTest 可以与各种测试框架(如 GoogleTest 和 Catch2)配合使用。
基本用法:
- 启用测试: 使用enable_testing() 指令启用测试。
- 添加测试: 使用add_test() 指令添加测试。
示例:使用 CTest 和 GoogleTest
目录结构
/MyProject - CMakeLists.txt - src - main.cpp - mylib.cpp - mylib.h - tests - CMakeLists.txt - test_mylib.cpp
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10) project(MyProject) # 启用测试 enable_testing() # 添加源文件 add_library(MyLib src/mylib.cpp) # 添加可执行文件 add_executable(my_app src/main.cpp) target_link_libraries(my_app MyLib) # 添加子目录 add_subdirectory(tests) tests/CMakeLists.txt cmake_minimum_required(VERSION 3.10) # 查找 GoogleTest find_package(GTest REQUIRED) include_directories(${GTest_INCLUDE_DIRS}) # 添加测试可执行文件 add_executable(runUnitTests test_mylib.cpp) target_link_libraries(runUnitTests ${GTest_LIBRARIES} MyLib) # 添加测试 add_test(NAME MyLibTest COMMAND runUnitTests)
tests/test_mylib.cpp
#include <gtest/gtest.h> #include "mylib.h" TEST(MyLibTest, SampleTest) { EXPECT_EQ(1, 1); } src/main.cpp #include <iostream> #include "mylib.h" int main() { std::cout << "Hello, World!" << std::endl; mylib_function(); return 0; }
src/mylib.cpp
#include "mylib.h" #include <iostream> void mylib_function() { std::cout << "mylib_function called" << std::endl; } src/mylib.h #ifndef MYLIB_H #define MYLIB_H void mylib_function(); #endif // MYLIB_H
打包
CMake 提供了 CPack 模块,用于生成各种格式的安装包(如 tar.gz、zip、DEB、RPM 等)。
基本用法:
- 设置打包变量: 设置项目名称、版本号、维护者等信息。
- 启用打包: 使用include(CPack) 包含 CPack 模块。
示例
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10) project(MyProject VERSION 1.0.0) # 启用测试 enable_testing() # 添加源文件 add_library(MyLib src/mylib.cpp) # 添加可执行文件 add_executable(my_app src/main.cpp) target_link_libraries(my_app MyLib) # 添加子目录 add_subdirectory(tests) # 设置打包变量 set(CPACK_PACKAGE_NAME "MyProject") set(CPACK_PACKAGE_VERSION "1.0.0") set(CPACK_PACKAGE_CONTACT "maintainer@example.com") set(CPACK_PACKAGE_DESCRIPTION "MyProject is a sample project") set(CPACK_PACKAGE_VENDOR "MyCompany") set(CPACK_PACKAGE_FILE_NAME "MyProject-1.0.0-Linux") # 启用打包 include(CPack)
在构建目录中运行以下命令生成安装包:
cmake --build . --target package
这将生成一个包含所有目标文件的安装包。
总结
- CTest: 用于集成测试,支持与各种测试框架配合使用。
- 通过enable_testing() 启用测试。
- 使用add_test() 添加测试。
- CPack: 用于生成各种格式的安装包。
- 设置打包变量,如项目名称、版本号、维护者等信息。
- 通过include(CPack) 启用打包。
通过合理使用 CTest 和 CPack,可以确保你的项目在开发、测试和发布阶段都能顺利进行。测试可以保证代码的质量,而打包可以方便地分发应用程序或库。
CMake的配置和生成
CMake 通过配置(Configuration)和生成(Generation)两个主要阶段来创建项目构建系统。这两个阶段分别处理不同的任务,共同确保项目能够正确编译和链接。以下是对这两个阶段的详细介绍。
配置(Configuration)
配置阶段的主要目标是读取和解析 CMakeLists.txt 文件,设置项目的构建选项和依赖关系,并生成一个内部的构建描述。
步骤:
- 读取txt 文件:CMake 会从项目的根目录开始读取 CMakeLists.txt 文件,并递归读取所有子目录中的 CMakeLists.txt 文件。
- 解析配置指令:解析 cmake_minimum_required、project 等基本指令。设置编译选项、添加源文件、定义库和可执行目标等。
- 查找依赖库和包:使用 find_package、find_library 等指令找到外部库和包,并设置相应的变量。
- 处理条件和循环:根据条件语句(如 if、elseif、else)和循环语句(如 foreach、while)执行相应的配置逻辑。
- 生成中间构建文件:在配置阶段结束时,CMake 会生成一个txt 文件,其中包含了所有配置选项和变量的值。
示例:
# 在项目根目录下运行 CMake 配置 cmake -S . -B build
- -S . 指定源目录(当前目录)。
- -B build 指定构建目录(build 目录)。
生成(Generation)
生成阶段的主要目标是根据配置阶段生成的中间构建描述,生成实际的构建系统文件(例如 Makefile、Ninja 文件、Visual Studio 工程文件等)。
步骤:
- 选择生成器:根据用户指定或系统默认的生成器,选择适当的生成器(如 Makefile、Ninja、Visual Studio 等)。
- 生成平台特定的构建文件:使用配置阶段的信息,生成适用于特定平台和编译器的构建文件。例如:如果选择的是 Unix Makefiles 生成器,CMake 会生成 Makefile 文件;如果选择的是 Visual Studio 生成器,CMake 会生成对应的 .sln 和 .vcxproj 文件。
- 输出构建文件:生成的构建文件会放置在指定的构建目录中(如 build 目录)。
示例
# 在指定的构建目录下运行生成命令 cmake --build build
这将根据生成的构建文件(例如 Makefile)实际编译和链接项目。
CMake生成器
CMake 生成器(Generator)是 CMake 用来生成特定平台和构建工具的构建文件的组件。不同的生成器可以生成适用于不同环境的构建系统文件,例如 Makefile、Ninja 文件、Visual Studio 解决方案等。选择合适的生成器可以显著简化项目的构建和开发过程。
主要生成器类型:
- UNIX Makefiles
- 描述: 生成标准的 Makefile 文件,适用于大多数 Unix/Linux 系统和基于 Make 的构建流程。
- 使用场景: 适用于大多数 Unix/Linux 系统,特别是那些已经有 Make 工具的环境。
- 指定方式:cmake -G “Unix Makefiles” ..
- Ninja
- 描述: 生成 Ninja 构建文件。Ninja 是一个小而快速的构建系统,可以显著加速大型项目的构建过程。
- 使用场景: 适用于需要高速构建的环境,尤其是大型项目。
- 指定方式:cmake -G “Ninja” ..
- Visual Studio
- 描述: 生成 Visual Studio 解决方案和项目文件,适用于 Windows 平台的开发环境。
- 使用场景: 适用于 Windows 平台的开发,特别是那些使用 Visual Studio 作为主要开发工具的团队。
- 指定方式:cmake -G “Visual Studio 16 2019” ..
- Xcode
- 描述: 生成 Xcode 项目文件,适用于 macOS 平台的开发环境。
- 使用场景: 适用于 macOS 平台的开发,特别是那些使用 Xcode 作为主要开发工具的团队。
- 指定方式:cmake -G “Xcode” ..
生成器的选择通常取决于开发环境、构建工具和项目需求。以下是一些常见的选择策略:
- 平台特定:
- Windows: Visual Studio 生成器。
- Linux/Unix: Unix Makefiles 或 Ninja。
- macOS: Xcode 或 Unix Makefiles。
- 构建速度:
- Ninja 通常比 Makefile 更快,适用于大型项目。
- 集成开发环境(IDE):
- 使用 Visual Studio 或 Xcode 作为主要开发工具时,选择相应的生成器更为合适。
CMakeCache.txt
在使用 CMake 构建系统时,理解 CMakeCache.txt 文件和如何使用命令行参数来配置 CMake 是非常重要的。这些工具和技术可以帮助你更好地管理和控制构建过程。
CMakeCache.txt 文件是 CMake 用来存储项目配置选项和变量的缓存文件。它通常位于构建目录中,并包含所有通过命令行参数或者 CMakeLists.txt 文件设置的变量。这个文件的作用是使得 CMake 在重新运行时保持配置的一致性,而不需要重复输入相同的参数。
查看 CMakeCache.txt
你可以直接打开 CMakeCache.txt 文件查看其中的内容。每个变量都有一个注释说明,并以 key:type=value 的格式存储。例如:
//The C compiler identification is GNU 9.3.0 CMAKE_C_COMPILER:FILEPATH=/usr/bin/gcc //The CXX compiler identification is GNU 9.3.0 CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++
修改 CMakeCache.txt
虽然你可以手动编辑 CMakeCache.txt,但更推荐使用 CMake 提供的命令行工具或者 GUI 工具来配置和修改缓存变量。
使用命令行参数设置变量
在运行 CMake 时,你可以使用 -D 选项来设置变量。这是一个方便的方式来配置构建选项,而不需要修改 CMakeLists.txt 文件。
基本语法:
cmake -D<variable>=<value> <path-to-source>
- <variable>: 变量名。
- <value>: 变量值。
- <path-to-source>: 源代码目录的路径。
示例,假设你要设置安装路径和构建类型:
cmake -DCMAKE_INSTALL_PREFIX=/opt/myproject -DCMAKE_BUILD_TYPE=Release ..
这将设置安装路径为 /opt/myproject,并将构建类型设置为 Release。
以下是一些常用的 CMake 变量及其说明:
- CMAKE_BUILD_TYPE: 指定构建类型(Debug、Release、RelWithDebInfo、MinSizeRel)。
- CMAKE_INSTALL_PREFIX: 指定安装路径。
- CMAKE_C_COMPILER: 指定 C 编译器。
- CMAKE_CXX_COMPILER: 指定 C++ 编译器。
- CMAKE_MODULE_PATH: 指定自定义模块搜索路径。
- BUILD_SHARED_LIBS: 设置为ON 或 OFF,指定是否构建共享库。
使用 ccmake 或 cmake-gui
如果你更喜欢图形化的方式配置 CMake,可以使用 ccmake(基于终端的交互界面)或者 cmake-gui(图形用户界面)。
- 在 ccmake 界面中,你可以浏览和修改所有的缓存变量。
- cmake-gui 提供了一个图形界面,可以更方便地配置和修改缓存变量。
如果你需要重新配置项目,可以使用 CMake 的 -U 选项清除某些变量,或者直接删除 CMakeCache.txt 文件。
# 清除特定变量 cmake -U<variable> .. # 删除缓存文件 rm CMakeCache.txt # 然后重新运行 CMake: cmake ..
CMake构建和安装
在 CMake 中,构建和安装项目通常涉及以下几个步骤。首先,你需要配置项目,然后生成构建文件,最后编译和安装项目。以下是详细的过程:
生成构建文件
配置完成后,CMake 会生成适用于所选生成器的构建文件。这些文件可以是 Makefile(对于 Unix Makefiles 生成器)或其他特定于生成器的文件。
# 生成构建文件 cmake --build .
编译项目
使用生成的构建文件编译项目。对于 Makefile,使用 make 命令;对于 Ninja,使用 ninja 命令。
# 使用 Makefile 编译 make # 使用 Ninja 编译 ninja
安装项目
编译完成后,可以使用 make install 或 cmake –install 命令将项目安装到指定的路径。安装路径通常在配置阶段通过 CMAKE_INSTALL_PREFIX 设置。
# 使用 Makefile 安装 make install # 使用 Ninja 安装 ninja install # 或者使用 CMake 的 --install 选项 cmake --install .
参考链接: