0. CMake应用示例

之前我们也整理过cmake 引入第三方库(头文件目录、库目录、库文件)。但是这里面整理的内容其实是不全的。所以我们需要进一步将CMake的使用整理好。以供后面的学习的工程师来检索查询。

cmake-template
├── CMakeLists.txt
└── build
└── include
└── src
     └── main.cpp

1. CMakeList基础15例

# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.15)

# 设置项目名称和版本
project(MyProject VERSION 1.0 DESCRIPTION "An example project with CMake")

# 选项,可以通过-D在命令行定义
option(USE_CUSTOM_LIBRARY "Use a custom library" ON)

# 定义条件预处理器宏
if(USE_CUSTOM_LIBRARY)
    add_definitions(-DUSE_CUSTOM_LIB)
endif()

# 寻找外部依赖包
find_package(Threads REQUIRED)

# 指定头文件的搜索路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 指定库文件搜索路径
link_directories(${PROJECT_SOURCE_DIR}/libs)

# 添加子目录,这些目录下也应该有自己的CMakeLists.txt
add_subdirectory(src)
add_subdirectory(libs)

# 添加一个可执行文件
add_executable(myExecutable src/main.cpp)

# 添加一个静态库
add_library(myStaticLib STATIC src/myStaticLib.cpp)

# 添加一个动态库
add_library(mySharedLib SHARED src/mySharedLib.cpp)

# 设置静态库和动态库的属性
set_target_properties(myStaticLib PROPERTIES
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/static_lib"
)
set_target_properties(mySharedLib PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/shared_lib"
    VERSION ${PROJECT_VERSION}
)

# 设置可执行文件的属性
set_target_properties(myExecutable PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    OUTPUT_NAME "myExecutableFinal"
)

# 指定链接库
target_link_libraries(myExecutable PRIVATE myStaticLib mySharedLib Threads::Threads)

# 安装规则
install(TARGETS myExecutable myStaticLib mySharedLib
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib/static
)

# 包含目录
target_include_directories(myExecutable PRIVATE ${PROJECT_SOURCE_DIR}/include)

# 自定义命令和依赖
add_custom_target(run
    COMMAND myExecutable
)
add_dependencies(run myExecutable)

# 宏定义,用于打印消息
macro(print_details)
    message("Project details: ${PROJECT_NAME} version ${PROJECT_VERSION}")
endmacro()
print_details()

# 文件操作示例
file(GLOB_RECURSE MY_SOURCES "src/*.cpp")
target_sources(myExecutable PRIVATE ${MY_SOURCES})

# 配置文件生成
configure_file(config.h.in ${CMAKE_BINARY_DIR}/config.h)

# 源文件目录搜索
aux_source_directory(. DIR_SRCS)
add_executable(myProgram ${DIR_SRCS})

# 添加目录,引入其他项目
add_subdirectory(external)

# 自定义目标,不产生输出文件
add_custom_target(CustomCommand ALL
    COMMAND echo "Executing a custom command"
    COMMENT "This is a custom build step"
)

# 文件复制命令
file(COPY ${CMAKE_SOURCE_DIR}/data.txt DESTINATION ${CMAKE_BINARY_DIR})

1.1 cmake_minimum_required

制定CMake的最小版本。它确保了 CMake 的特定版本或更高版本的特性能够被利用,并且保证了在旧版本的 CMake 中可能导致未知行为的特性不会被误用。

基本语法:

cmake_minimum_required(VERSION major.minor[.patch][.tweak] [FATAL_ERROR])
  • VERSION 关键字后面跟着所需的最低版本号。
  • FATAL_ERROR 是一个可选的参数,它在 CMake 的老版本中是必需的,如果用户使用的是比指定的 VERSION 更老的 CMake 版本,CMake 会报告一个致命错误并停止处理。在新版本的 CMake 中,默认情况下,如果版本不满足要求,CMake 就会报错并停止。

1.2 project

声明项目的名称和版本,并可选择指定支持的编程语言.此指令通常位于CMakeLists.txt文件的顶部,紧跟在cmake_minimum_required指令之后。

基本语法:

project(<PROJECT-NAME> [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
                 [DESCRIPTION <project-description-string>]
                 [HOMEPAGE_URL <url-string>]
                 [LANGUAGES <language-name>...])
  • <PROJECT-NAME> 是你的项目名称。
  • VERSION 关键字后面可以跟随项目的版本号。
  • DESCRIPTION可以添加项目描述。
  • HOMEPAGE_URL可以指定项目主页的URL。
  • LANGUAGES关键字后面可以跟一系列编程语言名称,例如 C, CXX (代表C++),CUDA,Fortran 等。如果不指定语言,CMake默认启用 C 和 CXX。

1.3 $$

CMake中的美元符号与括号一起使用,形如{...},用于引用变量的值。最常用的就是和project联系起来,例如:

基本语法:

# 设置项目名称和版本
project(MyProject VERSION 1.2.3 LANGUAGES CXX)
set(MY_VARIABLE "Hello, World")

message("Project name: ${PROJECT_NAME}")
message("Project version: ${PROJECT_VERSION}")
message(${MY_VARIABLE})

$${MY_VARIABLE} 会被 CMake 替换为变量 MY_VARIABLE 的值.而project 指令将会创建一些变量,比如 PROJECT_NAMEPROJECT_VERSION 等,这些变量随后可以被引用。

1.4 set

在CMake中,set命令用于定义和修改变量。这个命令非常灵活,是CMake脚本中用得最频繁的指令之一,因为它可以用来存储各种类型的数据,如字符串、文件路径、列表(即字符串数组)、布尔值等。

基本语法:

set(<variable> <value> [CACHE <type> <docstring> [FORCE]])

<variable> 是你要设置的变量名。
<value> 是分配给变量的值。可以是一个或多个值(如果是多个值,它们会被视为一个列表)。
CACHE 选项用于将变量存储到CMake的缓存中,这对于可配置的选项很有用,因为它们在不同的构建中保持不变,除非被用户或者项目脚本修改。
<type> 指定缓存中的变量类型,如STRING、BOOL等。
<docstring> 是对变量的描述,通常出现在CMake GUI中。
FORCE 用于强制覆盖缓存中的值,即使用户已经设置了该值。

在 CMake 中,set 指令用于设置变量的值。它的基本语法如下:

set(my_variable "Hello, World!")
  1. 设置一个列表变量:
set(my_list 1 2 3 4 5)
  1. 设置一个缓存变量:
set(my_cached_variable  Release CACHE STRING "Set C++ Compiler Flags" FORCE) # Release or Debug

# 取消缓存条目
unset(<variable> CACHE)

CMakeCache.txt文件用于存储缓存条目,第一次构建时会生成该文件,之后的构建并不会创建该文件。在引用缓存条目时,会去查找该文件,并返回值。

在使用 set()命令时,默认情况下,如果缓存条目在 CMakeCache.txt 文件不存在,会创建缓存条目,并写入到 CMakeCache.txt文件中。如果缓存条目在 CMakeCache.txt文件存在,忽略 set() 命令,不会覆盖现有的缓存条目。但是我们想强制覆盖现有的缓存条目,我们可以 FORCE 选项。

  1. 强制设置一个缓存变量:
set(my_cached_variable "Another value" CACHE STRING "Description of my_cached_variable" FORCE)

以上是 set 指令的基本语法和使用示例,你可以根据具体的需求设置不同类型的变量,如普通变量、列表变量或者缓存变量。

1.5 option

在 CMake 中,option 指令用于定义一个布尔类型的选项,这个选项可以用来控制编译过程中的一些行为或特性。基本语法如下:

基本语法:

option(<option_variable> "<help_string>" [initial_value])

其中:

  • <option_variable> 是要定义的选项的变量名。
  • "<help_string>" 是对选项的描述,会显示在 CMake GUI 或者命令行帮助信息中。
  • [initial_value] 是选项的初始值,可选,默认为 OFF

下面是定义一个开关选项,表示是否启用某个特性:

option(ENABLE_FEATURE "Enable some feature" ON)

1.6 add_definitions

在 CMake 中,add_definitions 指令用于向编译器添加预定义的宏定义。这些宏定义将在编译源文件时起作用。基本语法如下:

add_definitions(-D<macro1> [-D<macro2>] ...)

其中:

  • -D<macro> 表示要定义的宏,-D 后跟着宏的名称。如果宏需要带有值,则可以使用等号(=)将宏名称与值分隔开。

下面是一些 add_definitions 指令的使用示例:

  1. 定义一个简单的宏:
add_definitions(-DENABLE_LOGGING)
  1. 定义一个带有值的宏:
add_definitions(-DDEBUG_LEVEL=2)
  1. 定义多个宏:
add_definitions(-DENABLE_FEATURE_A -DENABLE_FEATURE_B)

通过 add_definitions 可以向编译器传递预定义的宏,这些宏在编译源文件时将起到作用。需要注意的是,使用 add_definitions 添加的宏定义将应用于整个项目中的所有源文件。

1.7 find_package

当使用 find_package 时,你需要指定要查找的软件包的名称,并可能需要提供版本信息和一些其他选项。以下是 find_package 的基本语法和详细使用例子:

基本语法:

find_package(<package_name> [version] [EXACT] [QUIET] [MODULE] [REQUIRED]
             [COMPONENTS [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])

参数说明:

  • <package_name>: 要查找的软件包的名称。
  • version: 可选参数,用于指定软件包的版本。
  • EXACT: 可选参数,要求找到的软件包的版本必须与指定的版本完全匹配。
  • QUIET: 可选参数,如果找不到软件包不会产生错误消息。
  • MODULE: 可选参数,表示要查找的软件包是一个模块文件而不是一个软件包。
  • REQUIRED: 可选参数,表示找不到软件包时会产生错误消息并停止配置过程。
  • COMPONENTS [components...]: 可选参数,用于指定软件包的组件。
  • OPTIONAL_COMPONENTS components...: 可选参数,用于指定可选的软件包组件。
  • NO_POLICY_SCOPE: 可选参数,指示 CMake 不要修改 CMake 策略。

使用例子:

  1. 查找 OpenSSL 软件包,并要求找到
find_package(OpenSSL REQUIRED)
  1. 查找 Boost 软件包,并指定版本
find_package(Boost 1.70 REQUIRED)
  1. 查找 MPI 软件包,并指定 C++ 组件
find_package(MPI REQUIRED COMPONENTS CXX)
  1. 查找 HDF5 软件包,但是不要求必须找到
find_package(HDF5 QUIET)
if(HDF5_FOUND)
    message(STATUS "HDF5 found: ${HDF5_VERSION}")
else()
    message(STATUS "HDF5 not found")
endif()
  1. 查找 PythonInterp 软件包,指定版本,如果找不到,打印警告信息
find_package(PythonInterp 3.6 REQUIRED)
if(NOT PythonInterp_FOUND)
    message(WARNING "Python 3.6 or later is required!")
endif()
  1. 查找 CUDA 软件包,指定版本和特定组件
find_package(CUDA 11.0 REQUIRED COMPONENTS Runtime)

通过这些例子,你可以更好地了解如何使用 find_package 来查找所需的软件包,并根据需要进行相应的处理。

1.8 include_directories

include_directories 指令用于向 CMake 项目中添加头文件搜索路径,这样在编译过程中就可以直接使用相对路径或文件名来引用头文件。下面是基本语法和详细使用例子,以 OpenCV 和 Boost 库为例:

基本语法:

include_directories([AFTER|BEFORE] [SYSTEM] directory1 [directory2 ...])

其中:

  • AFTERBEFORE 是可选的参数,用于指定添加目录的顺序,默认为 AFTER,即添加到其他包含目录的后面。
  • SYSTEM 是可选参数,用于标记这些目录中的头文件为系统头文件,编译器可能会忽略其中的警告信息。
  • directory1, directory2, … 是要添加的头文件搜索路径。

添加 OpenCV 的头文件搜索路径:

# 查找 OpenCV 软件包
find_package(OpenCV REQUIRED)

set (INCLUDE_PATH D:\\ProgramFiles\\Qt\\qt5_7_lib_shared_64)
include_directories(${INCLUDE_PATH}/include)       

# 添加外部库的头文件目录
include_directories(/path/to/my_library/include)
# 添加 OpenCV 的头文件搜索路径
include_directories(${OpenCV_INCLUDE_DIRS})

在这两个例子中,我们首先使用 find_package 查找了 OpenCV 软件包,并获取了它们的相关信息。然后,通过 include_directories 将它们的头文件搜索路径添加到项目中。这样的设置使得我们可以直接在源代码中使用 OpenCV 库提供的功能,而不需要指定完整的路径或文件名。

find_package 是用于在 CMake 项目中查找并配置外部软件包的功能。当你调用 find_package(OpenCV REQUIRED) 时,CMake 会搜索系统中安装的 OpenCV 软件包,并设置相关的变量,包括 OpenCV_INCLUDE_DIRSOpenCV_LIBS

  • OpenCV_INCLUDE_DIRS 变量包含了 OpenCV 头文件的搜索路径。
  • OpenCV_LIBS 变量包含了 OpenCV 库文件的路径和名称。
    因此,当你在 CMakeLists.txt 中使用 $${OpenCV_INCLUDE_DIRS} 时,实际上是在告诉 CMake 去寻找 OpenCV 头文件的位置,这样编译器在编译源代码时就能够找到相应的头文件。

link_libraries 指令用于将库链接到目标文件中。这个指令已经在 CMake 3.13 版本中被标记为不推荐使用,而推荐使用 target_link_libraries 指令。但是,我会提供 link_libraries 的基本语法和一个简单的使用例子。

基本语法:

link_libraries(library1 [library2 ...])

其中:

  • library1, library2, … 是要链接的库的名称。

详细使用例子:

# 链接某个库到可执行文件中
link_libraries(my_library)

在这个例子中,我们使用 link_libraries 指令将一个名为 my_library 的库链接到可执行文件中。虽然 link_libraries 指令可以完成这个任务,但是它的使用方式相对较简单,而且不够灵活。因此,建议使用更现代的 target_link_libraries 指令来替代它,特别是在大型项目中。

  • link_libraries 表示添加第三方 lib 库文件的搜索路径。若工程在编译的时候会需要用到某个第三方库的 lib 文件,此时就可以使用 link_libraries
  • target_link_libraries 子工程需要用到哪个lib库文件,需要使用 target_link_libraries 指定。(该lib库文件必须能在搜索路径中找到)

target_link_libraries 指令用于将目标文件(例如可执行文件、库文件)链接到一个或多个库。这是 CMake 中推荐使用的方式。下面是基本语法和详细使用例子:

基本语法:

target_link_libraries(target_name library1 [library2 ...])

其中:

  • target_name 是目标文件的名称,可以是可执行文件、库文件等。
  • library1, library2, … 是要链接到目标文件的库的名称。
  1. 基础用法
# 添加源文件
add_executable(my_program main.cpp)

# 查找并链接外部库(以 OpenCV 为例)
find_package(OpenCV REQUIRED)
target_link_libraries(my_program ${OpenCV_LIBS})

在这个例子中,我们首先使用 add_executable 添加了一个名为 my_program 的可执行文件。然后,我们使用 find_package 查找并配置了 OpenCV 软件包,并获取了它的相关信息。最后,我们使用 target_link_libraries 将 OpenCV 库链接到 my_program 可执行文件中。

target_link_libraries 指令用于将目标与一个或多个库进行链接,这可以是静态库、动态库或可执行文件。

基本语法:

target_link_libraries(target_name
    item1
    item2
    ...
)
  • target_name:目标名称,可以是可执行文件、静态库或动态库。
  • item1, item2, …:要链接的库的名称,可以是库的名称、路径或变量。
  1. 链接静态库:
# 假设有一个名为 my_library 的静态库
add_library(my_library STATIC ${LIBRARY_SOURCES})

# 将可执行文件 my_executable 与静态库 my_library 进行链接
target_link_libraries(my_executable my_library)
  1. 链接动态库:
# 假设有一个名为 my_dynamic_library 的动态库
add_library(my_dynamic_library SHARED ${DYNAMIC_LIBRARY_SOURCES})

# 将可执行文件 my_executable 与动态库 my_dynamic_library 进行链接
target_link_libraries(my_executable my_dynamic_library)
  1. 链接静态库和动态库:
# 将可执行文件 my_executable 既与静态库 my_library 链接,又与动态库 my_dynamic_library 链接
target_link_libraries(my_executable my_library my_dynamic_library)

在以上示例中,target_link_libraries 指令将指定的目标(my_executable)与给定的库(my_librarymy_dynamic_library)进行链接。这使得目标可以访问库中定义的函数和变量。

需要注意的是,在链接动态库时,还需要确保动态库在运行时可以被正确找到,可以通过设置LD_LIBRARY_PATH 等环境变量或者在运行时将动态库放置到合适的路径下。

export LD_LIBRARY_PATH=/mnt/xxx/usr/lib/

1.11 include

include 指令用于在 CMake 脚本中包含其他 CMake 脚本文件,以便在当前脚本中使用被包含文件中定义的变量、函数和命令。

基本语法:

include(filename)
  • filename:要包含的文件路径。可以是相对路径或绝对路径。

使用示例:

假设你的项目结构如下:

project/
│
├── CMakeLists.txt
└── cmake/
    └── protobuf-generate.cmake

你想在主项目的 CMakeLists.txt 中包含 protobuf-generate.cmake 文件,你可以这样做:

# project/CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(MyProject)

# 包含其他 CMake 脚本文件
include(cmake/protobuf-generate.cmake)

# 其他项目配置和构建逻辑

然后,在被包含的 cmake/protobuf-generate.cmake 文件中,你可以定义你的生成逻辑:

# project/cmake/protobuf-generate.cmake

# 定义生成逻辑
# 在这里可以定义变量、函数、添加目标等

# 例如,定义一个变量
set(PROTOBUF_FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/file1.proto
    ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/file2.proto
)

# 例如,添加一个自定义命令和目标来生成 protobuf 文件
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated_protobuf_files.cpp
    COMMAND protoc ${PROTOBUF_FILES} --cpp_out=${CMAKE_CURRENT_BINARY_DIR}
    DEPENDS ${PROTOBUF_FILES}
    COMMENT "Generating Protobuf files"
)

add_custom_target(generate_protobuf_files
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated_protobuf_files.cpp
)

通过 include 指令,你可以在主项目的 CMakeLists 文件中包含其他的 CMake 脚本文件,使得你的项目结构更加清晰,同时也可以重用已有的代码逻辑。

1.12 add_executable

add_executable 指令用于创建一个可执行文件目标,它将源文件编译成一个可执行文件。下面是基本语法和详细使用例子:

基本语法:

add_executable(target_name source1 [source2 ...])

其中:

  • target_name 是可执行文件的名称。
  • source1, source2, … 是要编译成可执行文件的源文件。

详细使用例子:

假设我们有一个简单的项目,其中包含两个源文件 main.cppfunctions.cpp,我们希望将它们编译成一个可执行文件。

# 添加可执行文件目标
add_executable(my_program main.cpp functions.cpp)

1.13 add_library

add_library 指令用于向 CMake 项目中添加一个新的库(静态库、动态库或模块库)。

基本语法:
add_library(<lib_name> [STATIC | SHARED | MODULE | OBJECT]
            [EXCLUDE_FROM_ALL]
            source1.cpp source2.cpp ...)
  • <lib_name>:库的名称。
  • STATIC:生成静态库(默认)。
  • SHARED:生成动态库。
  • MODULE:生成模块库。
  • OBJECT:生成对象库。
  • EXCLUDE_FROM_ALL:表示该库不会被默认构建目标所依赖。
使用示例:
# 添加源文件
set(SOURCES source1.cpp source2.cpp)

# 生成静态库
add_library(my_static_lib STATIC ${SOURCES})

# 生成动态库
add_library(my_dynamic_lib SHARED ${SOURCES})

# 生成模块库
add_library(my_module_lib MODULE ${SOURCES})

# 生成对象库
add_library(my_object_lib OBJECT ${SOURCES})

在以上示例中,分别使用 add_library 指令生成了静态库 my_static_lib、动态库 my_dynamic_lib、模块库 my_module_lib 和对象库 my_object_lib,并指定了对应的源文件列表。

这些库的类型由可选的参数确定,默认为 STATIC,即生成静态库。如果需要生成动态库、模块库或对象库,只需在指令中相应地指定类型即可。

1.14 target_include_directories

target_include_directories 指令用于向特定目标添加包含目录,以便在编译时能够找到头文件。

基本语法:
target_include_directories(target_name [SYSTEM] [BEFORE]
                           PRIVATE|PUBLIC|INTERFACE
                           [items1...]
                           [items2...]
                           ...)
  • target_name:要添加包含目录的目标名称。
  • SYSTEM:可选参数,指定包含的目录是否视为系统头文件目录。
  • BEFORE:可选参数,指定将包含目录添加到目标的头文件搜索路径的开头。
  • PRIVATE|PUBLIC|INTERFACE:指定包含目录的作用范围。
  • items1, items2, …:要添加的包含目录,可以是目录路径、变量、生成器表达式等。
使用示例:

假设你有一个名为 my_library 的库目标,你希望将 include 目录添加到该目标的包含路径中。

target_include_directories(my_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

在这个示例中,my_library 是目标名称,PUBLIC 表示包含路径将被添加到 my_library 的公共接口中,以便其他目标可以访问。$${CMAKE_CURRENT_SOURCE_DIR}/include 是要添加的包含目录路径。

你也可以添加多个目录:

target_include_directories(my_library PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${SOME_OTHER_DIR}/include
)

在这个示例中,除了 {CMAKE_CURRENT_SOURCE_DIR}/include 外,还将 {SOME_OTHER_DIR}/include 添加到 my_library 的公共接口中。

1.15 install

install 指令用于将构建生成的文件(可执行文件、库文件、头文件等)安装到指定的位置。这个指令可以用来将你的软件安装到系统中,使得其他用户可以方便地使用你的软件。

基本语法:
install(TARGETS target_name
        [EXPORT <export-name>]
        [ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|DESTINATION <dir>]
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [OPTIONAL]
        [INCLUDES DESTINATION [<dir> ...]]
)

install(FILES files...
        [DESTINATION <dir>]
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [OPTIONAL]
)

install(DIRECTORY dirs...
        [DESTINATION <dir>]
        [FILE_PERMISSIONS permissions...]
        [DIRECTORY_PERMISSIONS permissions...]
        [USE_SOURCE_PERMISSIONS]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [OPTIONAL]
)

install(EXPORT <export-name>
        DESTINATION <dir>
        [NAMESPACE <namespace>]
        [FILE <filename>]
)
  1. TARGETS:安装目标,可以是可执行文件、库文件等。

    这个选项用于指定要安装的目标,可以是通过 add_executableadd_libraryadd_custom_target 创建的目标。它告诉 CMake 要安装哪些构建目标。

    • EXPORT <export-name>:将目标导出到其他项目,以便其他项目可以引用这些目标。
      • ARCHIVE:指定安装目标为静态库归档文件(.a 文件)。
      • LIBRARY:指定安装目标为共享库文件(动态库,.so 文件)。
      • RUNTIME:指定安装目标为可执行文件。
      • OBJECTS:指定安装目标为对象库文件(.o 文件)。
      • FRAMEWORK:指定安装目标为 macOS 下的框架文件。
      • BUNDLE:指定安装目标为 macOS 下的捆绑包文件。
      • DESTINATION <dir>:指定安装目标的目标路径。
    • PERMISSIONS permissions...:指定安装目标的权限。
    • CONFIGURATIONS [Debug|Release|...]:指定只在指定构建配置下安装目标。
    • COMPONENT <component>:指定安装目标的组件。
    • OPTIONAL:指定安装目标是否为可选。
    • INCLUDES DESTINATION [<dir> ...]:指定安装目标的头文件。
  2. FILES:要安装的文件列表。

    这个选项用于指定要安装的文件列表。你可以列出要安装的每个文件,然后通过 install(FILES ...) 将它们安装到指定位置。

    • DESTINATION <dir>:指定安装的目标路径。
    • PERMISSIONS permissions...:指定安装文件的权限。
    • CONFIGURATIONS [Debug|Release|...]:指定只在指定构建配置下安装文件。
    • COMPONENT <component>:指定安装文件的组件。
    • OPTIONAL:指定安装文件是否为可选。
  3. DIRECTORY:要安装的目录列表。

    这个选项用于指定要安装的目录列表。它会递归地复制目录中的所有文件和子目录。

    • DESTINATION <dir>:指定安装的目标路径。
    • FILE_PERMISSIONS permissions...:指定安装文件的权限。
    • DIRECTORY_PERMISSIONS permissions...:指定安装目录的权限。
    • USE_SOURCE_PERMISSIONS:使用源文件的权限。
    • CONFIGURATIONS [Debug|Release|...]:指定只在指定构建配置下安装目录。
    • COMPONENT <component>:指定安装目录的组件。
    • OPTIONAL:指定安装目录是否为可选。
  4. EXPORT:将目标导出到其他项目中使用。

    这个选项用于将目标导出到其他项目中使用,以便其他项目可以引用这些目标。

    • DESTINATION <dir>:指定导出的目标路径。
    • NAMESPACE <namespace>:指定导出的命名空间。
    • FILE <filename>:指定导出的文件名。
使用示例:

安装可执行文件:

install(TARGETS my_executable DESTINATION bin)

安装库文件:

install(TARGETS my_library DESTINATION lib)

安装头文件:

install(FILES my_header.h DESTINATION include)

安装整个目录:

install(DIRECTORY my_directory DESTINATION share/my_project)

导出目标:

install(EXPORT my_targets DESTINATION lib/my_project)

这些示例分别演示了如何安装可执行文件、库文件、头文件、整个目录以及如何导出目标到其他项目。

2. Cmake变量

以下是常用的一些 CMake 变量及其对应的内容,以及在 CMakeLists.txt 中的使用示例和输出:

变量名 内容 在 CMakeLists.txt 中的使用和输出 输出结果
CMAKE_BINARY_DIR 当前正在构建的项目的顶级二进制目录 message("CMAKE_BINARY_DIR: $${CMAKE_BINARY_DIR}") CMAKE_BINARY_DIR: /path/to/build
PROJECT_BINARY_DIR 与 CMAKE_BINARY_DIR 相同 message("PROJECT_BINARY_DIR: $${PROJECT_BINARY_DIR}") PROJECT_BINARY_DIR: /path/to/build
_BINARY_DIR 与 CMAKE_BINARY_DIR 相同 message("_BINARY_DIR: $${_BINARY_DIR}") _BINARY_DIR: /path/to/build
CMAKE_SOURCE_DIR 当前正在配置的项目的顶级源代码目录 message("CMAKE_SOURCE_DIR: $${CMAKE_SOURCE_DIR}") CMAKE_SOURCE_DIR: /path/to/source
PROJECT_SOURCE_DIR 与 CMAKE_SOURCE_DIR 相同 message("PROJECT_SOURCE_DIR: $${PROJECT_SOURCE_DIR}") PROJECT_SOURCE_DIR: /path/to/source
_SOURCE_DIR 与 CMAKE_SOURCE_DIR 相同 message("_SOURCE_DIR: $${_SOURCE_DIR}") _SOURCE_DIR: /path/to/source
CMAKE_CURRENT_SOURCE_DIR 正在处理的 CMakeLists.txt 所在的源代码目录 message("CMAKE_CURRENT_SOURCE_DIR: $${CMAKE_CURRENT_SOURCE_DIR}") CMAKE_CURRENT_SOURCE_DIR: /path/to/source/current
CMAKE_CURRENT_BINARY_DIR 正在处理的 CMakeLists.txt 所在的构建目录 message("CMAKE_CURRENT_BINARY_DIR: $${CMAKE_CURRENT_BINARY_DIR}") CMAKE_CURRENT_BINARY_DIR: /path/to/build/current
CMAKE_CURRENT_LIST_FILE 当前处理的 CMakeLists.txt 文件的完整路径 message("CMAKE_CURRENT_LIST_FILE: $${CMAKE_CURRENT_LIST_FILE}") CMAKE_CURRENT_LIST_FILE: /path/to/source/CMakeLists.txt
CMAKE_CURRENT_LIST_LINE CMakeLists.txt 中当前行的行号 message("CMAKE_CURRENT_LIST_LINE: $${CMAKE_CURRENT_LIST_LINE}") CMAKE_CURRENT_LIST_LINE: 10
CMAKE_MODULE_PATH CMake 查找模块文件的额外路径 message("CMAKE_MODULE_PATH: $${CMAKE_MODULE_PATH}") CMAKE_MODULE_PATH: /path/to/cmake/modules
EXECUTABLE_OUTPUT_PATH 设置生成可执行文件的输出路径 message("EXECUTABLE_OUTPUT_PATH: $${EXECUTABLE_OUTPUT_PATH}") EXECUTABLE_OUTPUT_PATH: /path/to/build/bin
PROJECT_NAME 当前项目的名称 message("PROJECT_NAME: $${PROJECT_NAME}") PROJECT_NAME: MyProject
CMAKE_BUILD_TYPE 当前构建类型(Release、Debug 等) message("CMAKE_BUILD_TYPE: $${CMAKE_BUILD_TYPE}") CMAKE_BUILD_TYPE: Release
CMAKE_CXX_FLAGS C++ 编译器选项 message("CMAKE_CXX_FLAGS: $${CMAKE_CXX_FLAGS}") CMAKE_CXX_FLAGS: -std=c++11 -Wall
CMAKE_CXX_FLAGS_RELEASE Release 构建类型下的 C++ 编译器选项 message("CMAKE_CXX_FLAGS_RELEASE: $${CMAKE_CXX_FLAGS_RELEASE}") CMAKE_CXX_FLAGS_RELEASE: -O3
CMAKE_MAJOR_VERSION CMake 的主版本号 message("CMAKE_MAJOR_VERSION: $${CMAKE_MAJOR_VERSION}") CMAKE_MAJOR_VERSION: 3
CMAKE_SYSTEM 当前操作系统类型 message("CMAKE_SYSTEM: $${CMAKE_SYSTEM}") CMAKE_SYSTEM: Linux
CMAKE_SYSTEM_VERSION 当前操作系统版本 message("CMAKE_SYSTEM_VERSION: $${CMAKE_SYSTEM_VERSION}") CMAKE_SYSTEM_VERSION: 18.04
CMAKE_SYSTEM_PROCESSOR 当前系统的处理器架构 message("CMAKE_SYSTEM_PROCESSOR: $${CMAKE_SYSTEM_PROCESSOR}") CMAKE_SYSTEM_PROCESSOR: x86_64
LIBRARY_OUTPUT_PATH 设置生成库文件的输出路径 message("LIBRARY_OUTPUT_PATH: $${LIBRARY_OUTPUT_PATH}") LIBRARY_OUTPUT_PATH: /path/to/build/lib
CMAKE_SIZEOF_VOID_P Void 指针的大小(以字节为单位),该变量会告诉我们CPU是32位还是64位 message("CMAKE_SIZEOF_VOID_P: $${CMAKE_SIZEOF_VOID_P}") CMAKE_SIZEOF_VOID_P: 8
CMAKE_HOST_SYSTEM_PROCESSOR 主机系统的处理器架构 message("CMAKE_HOST_SYSTEM_PROCESSOR: $${CMAKE_HOST_SYSTEM_PROCESSOR}") CMAKE_HOST_SYSTEM_PROCESSOR: x86_64

3. CMake进阶用法

link_directories 指令用于向链接器指定额外的库搜索路径,以便链接器可以在指定路径中查找要链接的库文件。这个指令并不会直接链接库,而是告诉链接器在哪里查找库文件。

基本语法:

link_directories(directory1 [directory2 ...])
  • directory1, directory2, …:要添加到链接器搜索路径的目录列表。

使用示例:

# 在CMakeLists.txt中添加link_directories指令
set(LINK_DIRECTORIES ${SYSROOT}/usr/local/lib 
    ${CMAKE_CURRENT_SOURCE_DIR}/../../install/sal/utility/accelerator/lib/
)

link_directories(
    ${LINK_DIRECTORIES}

)

注意: 尽管 link_directories 指令可以添加库搜索路径,但尽量避免使用 link_directories 命令。而是优先使用 target_link_libraries 命令来显式指定要链接的库文件,或者使用 Find 模块来查找库文件。这样可以更好地处理库文件的位置和依赖关系,使项目更加可移植和可靠。(个人理解:使用link_directories并不能直观显示哪个库对应了哪个文件)

3.2 set_target_properties

set_target_properties 指令用于设置目标(例如可执行文件、静态库或共享库)的属性。通过这个指令,你可以设置目标的输出名称、输出路径、编译器选项等。

基本语法:

set_target_properties(target1 target2 ... PROPERTIES prop1 value1 prop2 value2 ...)
  • target1, target2, …:要设置属性的目标列表。
  • prop1, prop2, …:要设置的属性名称。
  • value1, value2, …:要设置的属性值。

使用示例:

# 设置可执行文件的输出名称为 "my_executable"
set_target_properties(my_executable PROPERTIES OUTPUT_NAME "my_executable")

# 设置静态库的输出路径为 "/path/to/static_lib_output"
set_target_properties(my_static_lib PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "/path/to/static_lib_output")

# 设置共享库的编译器标志为 "-fPIC"
set_target_properties(my_shared_lib PROPERTIES COMPILE_FLAGS "-fPIC")

# 用于告诉链接器在链接时要使用的附加库搜索路径
set_target_properties(Camera3dPls PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/release
        LINK_FLAGS "-Wl,--disable-new-dtags,-rpath-link,/opt/Mrc05Sdk/sysroot/usr/lib/aarch64-linux-gnu:/opt/Mrc05Sdk/sysroot/usr/lib,-rpath,/userdata/CarryBoy/CAM"
    )

# 需要为项目中的不同目标设置不同的 C++ 标准,这部分内容会和第五章内容交杂,这里就是将可执行文件指定了一些标志
set_target_properties(myTarget PROPERTIES
    CXX_STANDARD 11
    CXX_STANDARD_REQUIRED ON
    CXX_EXTENSIONS OFF
)

通过 set_target_properties 指令,你可以灵活地定制目标的属性,以满足特定的构建需求。

以下是常用的一些属性名称:

  1. OUTPUT_NAME:设置目标的输出名称。
  2. ARCHIVE_OUTPUT_DIRECTORY:设置静态库的输出路径。
  3. LIBRARY_OUTPUT_DIRECTORY:设置共享库的输出路径。
  4. RUNTIME_OUTPUT_DIRECTORY:设置可执行文件的输出路径。
  5. COMPILE_FLAGS:设置目标的编译器选项。
  6. LINK_FLAGS:设置目标的链接器选项。
  7. SUFFIX:设置目标的输出文件后缀。
  8. PREFIX:设置目标的输出文件前缀。
  9. INSTALL_NAME_DIR:设置安装时目标的名称目录。
  10. INSTALL_RPATH:设置目标的运行时搜索路径。
  11. VERSION:设置目标的版本号。
  12. SOVERSION:设置共享库的 API 版本。
  13. POSITION_INDEPENDENT_CODE:设置目标是否生成位置无关代码。

此外,你也可以定义自定义的属性名称【可以在set中使用的一般都可以传入到这个里面,但是全局变量优先还是在set里面设置的】,并使用 get_target_properties 指令来获取已设置的属性值。

3.3 target_compile_features

target_compile_features 指令用于指定目标(例如可执行文件、静态库或共享库)所需的编译特性或功能。通过这个指令,你可以告诉 CMake 使用的编译器需要支持的标准特性或功能,以便在编译过程中启用相应的功能。

基本语法:

target_compile_features(target_name <PRIVATE|PUBLIC|INTERFACE> feature1 feature2 ...)
  • target_name:目标名称。
  • PRIVATEPUBLICINTERFACE:指定特性应用的范围。PRIVATE 意味着特性仅应用于目标自身,PUBLIC 意味着特性应用于目标自身和依赖该目标的目标,INTERFACE 意味着特性仅应用于依赖该目标的目标。
  • feature1, feature2, …:所需的编译特性或功能列表。

使用示例:

target_compile_features(my_target PRIVATE cxx_std_11)

这个示例指定了目标 my_target 所需的编译特性,其中包括 C++11 标准。

你也可以同时指定多个特性,例如:

target_compile_features(my_target PUBLIC cxx_std_11 cxx_auto_type)

这个示例将 cxx_std_11cxx_auto_type 特性应用于 my_target 目标,并且这些特性也会应用于依赖 my_target 的其他目标。

3.4 GLOB

GLOB 函数用于获取指定模式匹配的文件列表。它可以帮助你在 CMake 中获取符合特定模式的文件,例如匹配所有的源文件、头文件等。

基本语法:

file(GLOB variable [RELATIVE path] [globbingexpressions]...)
  • variable:用于存储匹配文件列表的变量名称。
  • path:可选参数,指定匹配文件的相对路径。
  • globbingexpressions:指定用于匹配文件的通配符表达式,可以是单个通配符或多个通配符组合。

使用示例:

file(GLOB SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)

这个示例将当前源文件目录中所有的 .cpp 文件匹配并存储到 SRC_FILES 变量中。

另一个示例,如果你想匹配某个目录下的所有 .txt 文件,可以这样写:

file(GLOB TXT_FILES ${CMAKE_CURRENT_SOURCE_DIR}/data/*.txt)

这个示例将 data 目录下所有的 .txt 文件匹配并存储到 TXT_FILES 变量中。

注意: 使用 GLOB 函数时要小心,因为它会在构建目录生成时立即解析文件列表,并且不会在后续构建中更新。如果新增或删除了文件,CMake 不会自动更新已获取的文件列表。因此,在大型项目中最好明确列出源文件,而不是依赖于 GLOB 函数。但是,在某些情况下,如匹配测试用例文件等,GLOB 函数仍然很方便。

3.5 target_compile_definitions

target_compile_definitions 指令用于向目标添加预处理器定义。这个指令可以用于定义编译时使用的宏,可以控制编译过程中的条件编译。

基本语法:

target_compile_definitions(target_name
    PUBLIC|PRIVATE|INTERFACE
    definition1
    definition2
    ...
)
  • target_name:目标名称。
  • PUBLIC|PRIVATE|INTERFACE:指定预处理器定义的作用范围。PUBLIC 表示该定义将应用于目标本身以及依赖该目标的其他目标;PRIVATE 表示该定义仅应用于目标本身;INTERFACE 表示该定义仅应用于依赖该目标的其他目标。
  • definition1, definition2, …:预处理器定义的列表,可以是单个宏,也可以是宏与值的键值对,如 KEY=VALUE

target_compile_definitions的参数可以使用语法为$$<…>的”生成器表达式”。
项(item)上的任何前导-D都将被删除。忽略空项(Empty items are ignored).
定义可以选择有值。
注意:许多编译器将-DFOO等同于-DFOO=1,但是其它工具可能无法在所有情况下都识别出此情况(例如IntelliSense)。

使用示例:

target_compile_definitions(my_target PRIVATE DEBUG=1)

这个示例将宏 DEBUG 定义为 1,并将其添加到 my_target 目标的编译选项中,仅对 my_target 本身有效。

# 以下各项都是等效的
target_compile_definitions(main PUBLIC FOO)
target_compile_definitions(main PUBLIC -DFOO)  # -D removed
target_compile_definitions(main PUBLIC "" FOO) # "" ignored
target_compile_definitions(main PUBLIC -D FOO) # -D becomes "", then ignored

这个示例将宏 FOO 添加到 main 目标的编译选项中,并使其对依赖于 main 的其他目标可见。

3.6 get_filename_component

get_filename_component 函数用于获取文件名或路径的特定部分,如目录名、文件名、扩展名等。它可以帮助你在 CMake 脚本中提取文件路径的各个部分,以进行后续的操作。

基本语法:

get_filename_component(variable file_path component)
  • variable:用于存储提取结果的变量名称。
  • file_path:要提取部分的文件路径。
  • component:要提取的部分名称,可以是以下之一:
    • ABSOLUTE:绝对路径。
    • DIRECTORY:目录名。
    • FILENAME:文件名(包含扩展名)。
    • NAME:文件名(不包含扩展名)。
    • EXTENSION:扩展名。

使用示例:

get_filename_component(DIR_NAME "/path/to/file.txt" DIRECTORY)
message("Directory: ${DIR_NAME}")

这个示例将会输出文件路径 /path/to 中的目录名,即 Directory: /path/to

get_filename_component(FILE_NAME "/path/to/file.txt" NAME)
message("File name: ${FILE_NAME}")

这个示例将会输出文件路径 /path/to/file.txt 中的文件名(不包含扩展名),即 File name: file

get_filename_component(EXTENSION "/path/to/file.txt" EXTENSION)
message("Extension: ${EXTENSION}")

这个示例将会输出文件路径 /path/to/file.txt 中的扩展名,即 Extension: txt

3.7 configure_file 和@使用

configure_file 指令用于在构建过程中根据配置文件模板生成目标文件。@ 符号通常在配置文件模板中用于表示变量或占位符,以便在 configure_file 指令中使用实际值进行替换。

基本语法:

configure_file(input_file output_file [@ONLY])
  • input_file:配置文件模板,可以是任何文本文件,其中包含待替换的变量或占位符,通常使用 @VAR@ 形式表示。
  • output_file:生成的目标文件。
  • @ONLY:可选参数,指示只替换文件中的 @VAR@ 形式的变量,忽略 $$VAR 形式的变量。

使用示例:
假设有一个配置文件模板 config.h.in,内容如下:

#define VERSION_MAJOR @VERSION_MAJOR@
#define VERSION_MINOR @VERSION_MINOR@

我们可以使用 configure_file 指令来生成 config.h 文件,示例如下:

configure_file(config.h.in config.h)

这个示例将会在构建过程中生成一个 config.h 文件,其中的 @VERSION_MAJOR@@VERSION_MINOR@ 将被实际的版本号替换。

@ 符号是 CMake 中的一种约定,用于表示变量或占位符,以便在配置文件模板中进行替换。在 configure_file 指令中使用 @ 符号时,一般需要配合使用 @ONLY 参数,以确保只替换指定形式的变量。

3.8 target_sources(基本不用)

target_sources 指令用于向指定的目标(可执行文件、静态库或共享库)添加源文件。这个指令允许你向目标添加源文件,而不必直接在 add_executableadd_library 中列出所有源文件。

基本语法:

target_sources(target_name
    PRIVATE|PUBLIC|INTERFACE
    source1 [source2 ...]
)
  • target_name:目标名称。
  • PRIVATE|PUBLIC|INTERFACE:指定源文件的作用范围。PRIVATE 表示源文件仅在当前目标中使用,PUBLIC 表示源文件在当前目标以及依赖于当前目标的其他目标中使用,INTERFACE 表示源文件仅在依赖于当前目标的其他目标中使用。
  • source1, source2, …:源文件列表。

使用示例:

# 添加源文件到可执行文件或库
target_sources(my_target PRIVATE source1.cpp source2.cpp)

# 添加头文件到静态库或共享库
target_sources(my_library PUBLIC include/header1.h include/header2.h)

这个示例向名为 my_target 的目标添加了两个源文件 source1.cppsource2.cpp,并将它们作为 PRIVATE 文件添加,这意味着这些源文件仅在 my_target 中可见。

另外,示例中向名为 my_library 的目标添加了两个头文件 header1.hheader2.h,并将它们作为 PUBLIC 文件添加,这意味着这些头文件在 my_library 中可见,并且也会在依赖于 my_library 的其他目标中可见。

通过使用 target_sources 指令,你可以更灵活地管理源文件,避免在 add_executableadd_library 中列出大量的源文件路径。

3.9 aux_source_directory

aux_source_directory 指令用于将指定目录中的所有源文件自动添加到当前项目的源文件列表中。

基本语法:

aux_source_directory(dir variable)
  • dir:指定的源文件目录。
  • variable:用于存储找到的源文件列表的变量名称。

使用示例:
假设你有一个目录 src,其中包含了一些源文件,你可以使用 aux_source_directory 指令将这些源文件添加到你的项目中:

aux_source_directory(src SOURCES)

这个示例将会自动查找 src 目录下的所有源文件,并将它们的路径存储在名为 SOURCES 的变量中。

然后,你可以将这些源文件添加到你的目标中:

add_executable(my_executable ${SOURCES})

这个示例将会创建一个名为 my_executable 的可执行文件,并将 SOURCES 变量中的所有源文件添加到这个可执行文件中。

通过使用 aux_source_directory 指令,你可以更方便地管理大量的源文件,而不必一个一个地手动列出它们的路径。

3.10 add_subdirectory

add_subdirectory 指令用于向 CMake 构建系统添加一个子目录,以便在该子目录中构建更多的项目。

基本语法:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
  • source_dir:要添加的子目录的路径。
  • binary_dir:可选参数,指定用于存放该子目录构建结果的目录。如果不提供此参数,则默认为在源目录下创建一个与 source_dir 相同名称的目录。
  • EXCLUDE_FROM_ALL:可选参数,如果设置,则子目录将被排除在 ALL 构建目标之外。
使用示例:

假设你的项目有以下结构:

project/
│
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   ├── library/
│   │   ├── CMakeLists.txt
│   │   ├── source.cpp
│   │   └── header.h
│   └── executable/
│       ├── CMakeLists.txt
│       └── main.cpp

现在,你想在主项目的 CMakeLists.txt 中添加这两个子目录。

# project/CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(MyProject)

# 添加子目录
add_subdirectory(src/library)
add_subdirectory(src/executable)

然后,你需要在每个子目录的 CMakeLists.txt 文件中定义你的构建逻辑。

# project/src/library/CMakeLists.txt

# 定义一个库
add_library(my_library source.cpp header.h)
# project/src/executable/CMakeLists.txt

# 定义一个可执行文件,并链接库
add_executable(my_executable main.cpp)
target_link_libraries(my_executable my_library)

通过这种方式,你可以将项目组织成更清晰的模块化结构,使得每个模块的构建逻辑都相对独立,易于维护和管理。

3.11 add_custom_target

add_custom_target 指令用于添加一个自定义的构建目标,这个目标通常不会生成实际的文件,而是执行一系列自定义的构建命令。它可以用来添加一些与编译、链接或测试等无关的自定义构建步骤。

基本语法:
add_custom_target(target_name
    [COMMAND command1 [args1...]]
    [COMMAND command2 [args2...] ...]
    [WORKING_DIRECTORY dir]
    [COMMENT comment]
    [VERBATIM]
    [USES_TERMINAL]
    [COMMAND_EXPAND_LISTS]
)
  • target_name:自定义目标的名称。
  • COMMAND command1 [args1...]:要执行的命令及其参数。可以有多个命令。
  • WORKING_DIRECTORY dir:指定命令执行的工作目录。
  • COMMENT comment:添加一个注释,用于描述自定义目标的作用。
  • VERBATIM:将参数原样传递给命令,不进行任何转义。
  • USES_TERMINAL:在命令执行时使用终端。
  • COMMAND_EXPAND_LISTS:对参数进行列表扩展。
使用示例:
# 创建一个名为 "clean" 的自定义目标,用于清理生成的文件
add_custom_target(clean
    COMMAND ${CMAKE_COMMAND} -E remove_directory build
    COMMENT "Cleaning build directory"
)

# 创建一个名为 "generate" 的自定义目标,用于生成文件
add_custom_target(generate
    COMMAND ${CMAKE_COMMAND} -E make_directory build
    COMMAND ${CMAKE_COMMAND} -E touch build/generated_file.txt
    COMMENT "Generating files"
)

# 创建一个名为 "run_tests" 的自定义目标,用于运行测试
add_custom_target(run_tests
    COMMAND ${CMAKE_COMMAND} -E echo "Running tests..."
    COMMENT "Running tests"
)

# 创建一个名为 "print_message" 的自定义目标,用于打印消息
add_custom_target(print_message
    COMMAND ${CMAKE_COMMAND} -E echo "Hello, custom target!"
    COMMENT "Printing message"
)

在这个示例中,我们创建了四个自定义目标:

  • clean:清理 build 目录。
  • generate:生成一个文件到 build 目录。
  • run_tests:运行测试。
  • print_message:打印一条消息。

这些自定义目标可以通过 make cleanmake generatemake run_testsmake print_message 等命令在构建系统中调用。

3.12 add_dependencies

add_dependencies 指令用于定义目标之间的依赖关系。它可以确保在构建一个目标之前,另一个或多个目标已经构建完成。这对于确保特定的构建顺序或构建过程中的必要条件非常有用。

基本语法:
add_dependencies(target_name depends1 depends2 ...)
  • target_name:目标的名称,表示依赖关系的目标。
  • depends1 depends2 ...:依赖的目标列表,表示目标 target_name 的依赖项。
使用示例:

假设你有一个项目,其中包含两个可执行文件 app1app2,并且 app2 需要在 app1 构建完成后才能构建。你可以使用 add_dependencies 指令来定义这种依赖关系:

# 声明构建可执行文件 app1
add_executable(app1 app1.cpp)

# 声明构建可执行文件 app2
add_executable(app2 app2.cpp)

# 定义 app2 的依赖关系,确保在构建 app2 之前先构建 app1
add_dependencies(app2 app1)

在这个示例中,我们将 app2 的构建过程依赖于 app1,因此在构建 app2 之前,CMake 将确保 app1 已经构建完成。

add_dependencies 指令通常用于管理多个目标之间的构建顺序或确保特定目标在构建时具有必要的前提条件。

4. C++常见标识符

4.1 利用if设置不同的CMAKE_BUILD_TYPE

# STREQUAL:判断字符串是否相等,判断时并且区分大小写
# CMAKE_CXX_FLAGS_DEBUG、CMAKE_CXX_FLAGS_RELEASE:CMake预定义的内建变量,且他们是全局的。
# -O0: 这是一个优化级别的选项,在这里表示禁用所有优化。这样可以确保调试时生成的代码更容易理解和调试。
# -Wall: 这个选项启用了编译器的警告信息,并显示警告消息。开启这个选项可以帮助发现潜在的问题和不规范的代码。
# -g2: 这个选项启用了调试信息的生成。其中的 2 表示生成详细的调试信息,包括函数名、局部变量等。这样可以更方便地在调试器中进行代码调试。
# -ggdb: 这个选项指定了调试信息的格式和版本。-ggdb 是用于与 GDB 调试器兼容的调试信息格式,这样 GDB 可以正确地读取和解析调试信息。

set(CMAKE_BUILD_TYPE "Debug")
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")  

if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
    message("Debug build:" ${CMAKE_CXX_FLAGS_DEBUG})
elseif(${CMAKE_BUILD_TYPE} STREQUAL "Release")
    message("Release build" ${CMAKE_CXX_FLAGS_RELEASE})
else()
    message("Unknown build type")
endif()

4.2 macro

macro 是 CMake 中用于定义宏(类似于函数)的指令。宏可以接受参数,并在调用时执行一系列 CMake 命令。定义的宏可以在 CMakeLists.txt 文件中多次调用,以实现代码的重用和简化。

基本语法:

macro(macro_name arg1 arg2 ...)
    # 宏的执行代码块
endmacro()
  • macro_name:宏的名称。
  • arg1, arg2, …:宏的参数列表。

使用示例:

# 定义一个名为 print_hello 的宏,接受一个参数
macro(print_hello name)
    message("Hello, ${name}!")
endmacro()

# 调用 print_hello 宏
print_hello("CMake")


#例2
macro(add_deployment_file SRC DEST)
    file(RELATIVE_PATH path ${PROJECT_SOURCE_DIR}
         ${PROJECT_BINARY_DIR})
    file(APPEND "${PROJECT_BINARY_DIR}/QtCreatorDeployment.txt"
         "${path}/${SRC}:${DEST}\n")
endmacro()

add_deployment_file("bin/Camera3dPls" "CarryBoy/CAM")
Add_Deployment_File("bin/Camera3dPls" "CarryBoy/CAM")

宏可以使用流程控制语句、变量、函数等来实现更复杂的逻辑。其中name是macro的名字,参数为arg1,arg2等。与function一样,macro名称也不区分大小写,但强烈建议使用macro定义中声明的相同名称。通常,macro使用全小写名称。

4.3 file

file 指令用于在 CMake 中操作文件系统,包括创建、复制、移动、删除文件等操作。它提供了许多功能,如读取文件内容、获取文件属性等。

基本语法:

file(<COMMAND> <arguments>)

其中 <COMMAND> 可以是以下之一:

  • COPY:复制文件或目录。
  • MAKE_DIRECTORY:创建目录。
  • REMOVE:删除文件或目录。
  • READ:读取文件内容。
  • WRITE:写入内容到文件。

具体用法和示例如下:

复制文件或目录(COPY)
file(COPY source_file DESTINATION destination_directory)

示例:

file(COPY data.txt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

这个示例将 data.txt 文件复制到当前构建目录。

创建目录(MAKE_DIRECTORY)
file(MAKE_DIRECTORY directory)

示例:

file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/output)

这个示例将在当前构建目录下创建一个名为 output 的目录。

删除文件或目录(REMOVE)
file(REMOVE file_or_directory)

示例:

file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/output)

这个示例将删除当前构建目录下的名为 output 的目录。

读取文件内容(READ)
file(READ filename variable)

示例:

file(READ data.txt file_content)

这个示例将 data.txt 文件的内容读取到变量 file_content 中。

写入内容到文件(WRITE)
file(WRITE filename content)

示例:

file(WRITE output.txt "Hello, CMake!")

这个示例将 “Hello, CMake!” 写入到名为 output.txt 的文件中。

file 指令还有其他用法和选项,如获取文件属性、检查文件是否存在等。以上是一些常见用法的示例。如果需要更多信息或有其他问题,请随时告诉我。

4.4 生成器表达式

CMake的生成器表达式不算是特别常用,但是有一些场景可能是必须要使用的;或者在针对不同编译类型设置不同编译参数的时候可以巧妙应用,从而减少配置代码。

生成器表达式简单来说就是在CMake生成构建系统的时候根据不同配置动态生成特定的内容。比如:

  1. 条件链接,如针对同一个编译目标,debug版本和release版本链接不同的库文件
  2. 条件定义,如针对不同编译器,定义不同的宏

所以可以看到,其中的要点是条件,之所以需要自动生成,那绝大多数时候肯定是因为开发者无法提前确定某些配置,不能提前确定那往往就是有条件的。

生成器表达式的格式形如$$<...>,可以嵌套,可以用在很多构建目标的属性设置和特定的CMake命令中。值得强调的是,生成表达式被展开是在生成构建系统的时候,所以不能通过解析配置CMakeLists.txt阶段的message命令打印,文末会介绍其调试方法。

1. 布尔生成器表达式
逻辑运算符

逻辑运算很多语言都是需要的,CMake生成器表达式中有这些:

  1. $$<BOOL:string>:如果字符串为空、0;不区分大小写的FALSEOFFNNOIGNORENOTFOUND;或者区分大小写以-NOTFOUND结尾的字符串,则为0,否则为1
  2. $$<AND:conditions>:逻辑与,conditons是以逗号分割的条件列表
  3. $$<OR:conditions>:逻辑或,conditons是以逗号分割的条件列表
  4. $$<NOT:condition>:逻辑非

一般来说,条件是列表的,都是使用逗号进行分割,后面不再赘述。

字符串比较
  1. $$<STREQUAL:string1,string2>:判断字符串是否相等
  2. $$<EQUAL:value1,value2>:判断数值是否相等
  3. $$<IN_LIST:string,list>:判断string是否包含在list中,list使用分号分割

注意这里的list是在逗号后面的列表,所以其内容需要使用分号分割。

变量查询

这个会是比较常用的,在实际使用的时候会根据不同CMake内置变量生成不同配置,核心就在于“判断”:

  1. $$<TARGET_EXISTS:target>:判断目标是否存在
  2. $$<CONFIG:cfgs>:判断编译类型配置是否包含在cfgs列表(比如”release,debug”)中;不区分大小写
  3. $$<PLATFORM_ID:platform_ids>:判断CMake定义的平台ID是否包含在platform_ids列表中
  4. $$<COMPILE_LANGUAGE:languages>:判断编译语言是否包含在languages列表中
2. 字符串值生成器表达式

请注意,前面都是铺垫,这里才是使用生成器表达式的主要目的:生成特定的字符串。 比如官方的例子:基于编译器ID指定include目录:

include_directories(/usr/include/$<CXX_COMPILER_ID>/)

根据编译器的类型,$$<CXX_COMPILER_ID>会被替换成对应的ID(比如“GNU”、“Clang”)。

条件表达式

这便是本文的核心了,主要有两个格式:

  1. $$<condition:true_string>:如果条件为真,则结果为true_string,否则为空
  2. $$<IF:condition,str1,str2>:如果条件为真,则结果为str1,否则为str2

这里的条件一般情况下就是前面介绍的布尔生成器表达式。 比如要根据编译类型指定不同的编译选项,可以像下面这样:

set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")

但是使用生成器表达式可以简化成:

add_compile_options("$<$<CONFIG:Debug>:-g;-O0>")
add_compile_options($<$<CONFIG:Release>:-O2>)

如果需要指定多个编译选项,必须使用双引号把生成器表达式包含起来,且选项之间使用分号。

后面这个方法适用于设置一些对所有编译器(取决于项目编译语言)都通用的编译选项,而需要设置一些编译器特有的选项时,通过设置指定编译器的编译选项(前一种方法)更加简洁明了。

当然,可以用表达式判断编译器ID设置不同编译选项,不过明显有些为了用而用,这是没必要的。

转义字符

这比较好理解,因为有一些字符有特殊含义,所以可能需要转义,比如常用的<COMMA><SEMICOLON>,分别表示,;

字符串操作

常用的有<LOWER_CASE:string><UPPER_CASE:string>用于转换大小写。

获取变量值

获取变量的值和前文提到的变量查询很类似,前面说的变量查询是判断是否存在于指定列表中或者等于指定值。语法格式是类似的,以CONFIG为例:

  1. 获取变量值:$$<CONFIG>
  2. 判断是否存在于列表中:$$<CONFIG:cfgs>

详见:Variable Queries

编译目标查询

这里的查询是指获取编译目标(通过add_executable()add_library()命令生成的)相关的一些信息,包括:

  1. $$<TARGET_FILE:tgt>:获取编译目标的文件路径
  2. $$<TARGET_FILE_NAME:tgt>:获取编译目标的文件名
  3. $$<TARGET_FILE_BASE_NAME:tgt>:获取编译目标的基础名字,也就是文件名去掉前缀和扩展名

官网有更多详细介绍,有其他需要可以阅读:target-dependent-queries

3.调试

调试可以通过输出到文件的方式,在cmake执行完之后去检查是否符合预期,比如:

file(GENERATE OUTPUT "./generator_test.txt" CONTENT "$<$<CONFIG:Debug>:-g;-O0>,$<PLATFORM_ID>\n")

在MacOS中执行cmake,得到的结果如下:

# cmake -B cmake-build -DCMAKE_BUILD_TYPE=Debug
...
# cat cmake-build/generator_test.txt
-g;-O0,Darwin

如果不想写文件,也可以添加一个自定义目标,比如:

add_custom_target(gentest COMMAND ${CMAKE_COMMAND} -E echo "\"$<$<CONFIG:Debug>:-g;-O0>,$<PLATFORM_ID>\"")

注意这里需要双引号转义,确保生成器表达式展开之后是字符串。

在执行cmake之后,可以使用make gentest输出到生成器表达式的内容:

# cd cmake-build && make gentest
-g;-O0,Darwin
Built target gentest

5. C++标准设置

5.1 CMAKE_CXX_STANDARD

CMAKE_CXX_STANDARD 是一个 CMake 变量,用于指定 C++ 编译器要遵循的 C++ 标准版本。这个变量用于设置项目中所有 C++ 目标的编译器标准。它可以确保在编译项目时,所有的 C++ 源文件都遵循相同的 C++ 标准。

基本语法:

set(CMAKE_CXX_STANDARD <version>)
  • <version>:要使用的 C++ 标准版本,可以是 98、11、14、17、20 等。如果 CMake 版本不支持指定的标准版本,会自动降级到最接近的支持版本。

使用示例:

set(CMAKE_CXX_STANDARD 11)

这个示例设置了项目中所有 C++ 目标的编译器标准为 C++11。

5.2 CMAKE_CXX_STANDARD_REQUIRED

CMAKE_CXX_STANDARD_REQUIRED 是一个 CMake 变量,用于指定是否要求编译器严格遵循指定的 C++ 标准版本。如果设置为 ON,则要求编译器严格遵循指定的标准版本;如果设置为 OFF,则编译器可以选择向后兼容的版本。

基本语法:

set(CMAKE_CXX_STANDARD_REQUIRED <value>)
  • <value>:指定是否要求编译器严格遵循指定的 C++ 标准版本。可以是 ONOFF

使用示例:

set(CMAKE_CXX_STANDARD_REQUIRED ON)

5.3 CMAKE_CXX_EXTENSIONS

CMAKE_CXX_EXTENSIONS 是一个 CMake 变量,用于指定是否允许编译器使用 C++ 扩展特性。如果设置为 ON,则允许编译器使用扩展特性;如果设置为 OFF,则编译器只能使用标准的 C++ 特性。

基本语法:

set(CMAKE_CXX_EXTENSIONS <value>)
  • <value>:指定是否允许编译器使用扩展特性。可以是 ONOFF

使用示例:

set(CMAKE_CXX_EXTENSIONS OFF)

这个示例设置了禁止编译器使用 C++ 扩展特性。

通常情况下,建议将 CMAKE_CXX_EXTENSIONS 设置为 OFF,该属性会指定是否使用编译器特有的扩展(即,不同的编译器在 C++ 标准之外自行实现的、非通用的特性)。对于某些编译器来说,启用此选项后会在编译行中添加特殊的标志,比如用 -std=gnu++11 替换 -std=c++11。该属性默认为 ON。如果某个项目对可移植性有较高的要求,可能为不同的平台使用不同的编译器,那么建议将其设置为 OFF。

5.4 CMAKE_POSITION_INDEPENDENT_CODE

CMAKE_POSITION_INDEPENDENT_CODE 是一个 CMake 变量,用于指示是否为目标启用位置无关代码(PIC)。位置无关代码是一种编译选项,用于生成可与动态库链接的目标。当启用位置无关代码时,编译器会生成与地址无关的代码,以便在运行时加载目标时可以将其放置在内存中的任何位置。

基本语法:

set(CMAKE_POSITION_INDEPENDENT_CODE ON/OFF)
  • ON:启用位置无关代码。
  • OFF:禁用位置无关代码。

使用示例:

# 在主项目的 CMakeLists.txt 文件中设置 CMAKE_POSITION_INDEPENDENT_CODE
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# 添加一个库目标并链接到其他库
add_library(my_library OBJECT source1.cpp source2.cpp)
target_link_libraries(my_library PUBLIC some_library)

在这个示例中,我们通过将 CMAKE_POSITION_INDEPENDENT_CODE 设置为 ON,启用了位置无关代码。然后,我们创建了一个名为 my_library 的对象库,并链接到了 some_library。由于我们启用了位置无关代码,因此 my_library 将以位置无关的方式编译,使得它可以与动态库进行链接。

5.5 CMAKE_CXX_COMPILER_ID

CMAKE_CXX_COMPILER_ID 是一个 CMake 变量,用于获取当前 C++ 编译器的标识符。这个变量可以告诉你当前使用的编译器是哪个厂商的,例如 Clang、GNU、Intel 等。

基本语法:

${CMAKE_CXX_COMPILER_ID}

使用示例:

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    message("Using GNU Compiler")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    message("Using Clang Compiler")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    message("Using Microsoft Visual C++ Compiler")
else()
    message("Unknown compiler")
endif()

在这个示例中,我们使用了 if 条件语句来检查 CMAKE_CXX_COMPILER_ID 的值,并根据不同的编译器输出相应的消息。你可以根据需要使用不同的条件来执行不同的操作,例如针对不同的编译器设置不同的编译选项或链接不同的库。

5.6 交叉编译之CMAKE_C_COMPILER、CMAKE_CXX_COMPILER、CMAKE_TOOLCHAIN_FILE….

通常,我们在开发时,需要使用系统库或第三方库的功能,在生成可执行文件时,将其进行链接。cmake 提供了 FIND_PROGRAM(),FIND_LIBRARY(), FIND_FILE(), FIND_PATH() 和 FIND_PACKAGE() 实现相应的查找功能。如果我们在进行交叉编译时使用了上述指令,那么并不能生成可执行文件。因为默认情况下,上述指令查找的是主机上的相关文件,其并不适用于目标机器。

  1. 设置交叉编译器路径:
SET(CMAKE_C_COMPILER   /path/to/your/cross-compiler/bin/arm-linux-gcc)
SET(CMAKE_CXX_COMPILER /path/to/your/cross-compiler/bin/arm-linux-g++)
  1. 设置交叉编译工具链文件:
SET(CMAKE_TOOLCHAIN_FILE /path/to/your/toolchain-file.cmake)
  1. 设置交叉编译环境的目标平台:
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR arm)
  1. 设置交叉编译器的一些参数(可选):
SET(CMAKE_FIND_ROOT_PATH /path/to/your/cross-compiler/sysroot)#指定交叉编译环境的目录
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) #从来不在指定目录(交叉编译)下查找工具程序。(编译时利用的是宿主的工具)
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)#只在指定目录(交叉编译)下查找库文件
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)#只在指定目录(交叉编译)下查找头文件
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)#只在指定的目录(交叉编译)下查找依赖包
  1. 使用示例:

假设你有一个交叉编译工具链文件 toolchain-arm-linux.cmake,内容类似如下:

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR arm)

SET(CMAKE_C_COMPILER   /path/to/your/cross-compiler/bin/arm-linux-gcc)
SET(CMAKE_CXX_COMPILER /path/to/your/cross-compiler/bin/arm-linux-g++)

你可以在你的 CMakeLists.txt 文件中引入这个工具链文件:

SET(CMAKE_TOOLCHAIN_FILE /path/to/your/toolchain-arm-linux.cmake)

# 下面是你的项目设置和构建代码

通过以上设置,你就可以在 CMake 中指定交叉编译环境的目录。确保根据你的实际情况修改路径和参数。

如果需要更多信息或有其他问题,请随时告诉我。

5.7 protobuf

在 CMakeLists.txt 中使用 Protocol Buffers(protobuf)通常需要执行以下步骤:

  1. 配置 protobuf 消息文件的查找和生成。
  2. 创建库或可执行目标,并将生成的 C++ 文件添加到目标中。
  3. 链接 Protocol Buffers 库。

以下是使用 Protocol Buffers 的 CMakeLists.txt 示例:

cmake_minimum_required(VERSION 3.5)
project(MyProject)

# 寻找 Protocol Buffers 库
find_package(Protobuf REQUIRED)

# 设置 protobuf 消息文件的路径
file(GLOB_RECURSE PROTO_FILES CONFIGURE_DEPENDS "path/to/your/proto/files/*.proto")

# 生成 protobuf 消息的 C++ 文件
protobuf_generate_cpp(PROTO_SRC PROTO_HEADER ${PROTO_FILES})

# 创建库或可执行目标
add_executable(my_executable main.cpp ${PROTO_SRC} ${PROTO_HEADER})

# 将生成的头文件目录添加到包含路径中
target_include_directories(my_executable PUBLIC ${CMAKE_CURRENT_BINARY_DIR})

# 链接 Protocol Buffers 库
target_link_libraries(my_executable PRIVATE protobuf::libprotobuf)

在这个示例中:

  • 使用 find_package(Protobuf REQUIRED) 命令找到并加载 Protocol Buffers 库。
  • 使用 file(GLOB_RECURSE ...) 命令找到指定路径下所有的 .proto 文件。
  • 使用 protobuf_generate_cpp 命令生成 protobuf 消息的 C++ 文件,并将生成的源文件和头文件保存到变量中。
  • 创建一个可执行目标 my_executable,并将生成的 C++ 文件添加到目标中。
  • 使用 target_include_directories 命令将生成的头文件目录添加到可执行目标的包含路径中。
  • 使用 target_link_libraries 命令将 Protocol Buffers 库链接到可执行目标中。

通过这些步骤,你就可以在 CMake 项目中使用 Protocol Buffers,并在构建过程中生成和使用 protobuf 消息的 C++ 文件。

6. 参考链接

https://blog.csdn.net/qq_43389847/article/details/129681223
https://blog.csdn.net/wuqingshan2010/article/details/113625248
https://zhuanlan.zhihu.com/p/603137782
https://zhuanlan.zhihu.com/p/437404485