CMake进阶

补充上篇博客没有提到的内容,方便更完善的构建C++程序。

链接库

以下是一段链接库的代码:

1
2
3
4
target_link_libraries(hello_binary
PRIVATE
hello_library
)

在CMake中,**PRIVATEPUBLIC** 和 INTERFACE 是用来指定依赖库属性的关键词。它们用于控制库与目标(例如可执行文件或其他库)之间的链接方式和信息传递。

  1. **PRIVATE**:
    • 当你将一个库标记为 PRIVATE 时,这意味着依赖库仅在当前目标中使用,并且不会传递给由当前目标链接的其他目标。
    • 在本例中,**target_link_libraries(hello_binary PRIVATE hello_library)** 表示只有 hello_binary 可以访问 hello_library 提供的功能,其他与 hello_binary 无关的目标不会受到 hello_library 的影响。
  2. **PUBLIC**:
    • 将库标记为 PUBLIC 时,这表示依赖库在当前目标中使用的同时,也会传递给由当前目标链接的其他目标。
    • 在本例中,**target_include_directories(hello_library PUBLIC ${PROJECT_SOURCE_DIR}/include)** 会将 hello_library 的头文件包含路径公开给所有链接到它的目标,这样其他目标也能使用 hello_library 提供的头文件。
  3. **INTERFACE**:
    • INTERFACE 类似于 **PUBLIC**,但不会影响当前目标本身。它仅将依赖项传递给其他目标。
    • 如果你有一个中间库,它不会被链接到任何可执行文件,但你希望它的依赖项传递给其他库,那么可以使用 **INTERFACE**。

总结:

  • 使用 PRIVATE 来限制库的使用范围为当前目标。
  • 使用 PUBLIC 来将库的依赖传递给链接到当前目标的其他目标。
  • 使用 INTERFACE 将依赖项传递给其他目标,而不影响当前目标。

在你的示例代码中,**target_include_directories(hello_library PUBLIC ${PROJECT_SOURCE_DIR}/include)** 允许其他目标访问 hello_library 的头文件路径,而 target_link_libraries(hello_binary PRIVATE hello_library) 使 hello_binary 能够链接到 hello_library 的功能,但这些功能不会传递给其他目标。

设置宏定义

target_compile_definitions 是CMake中用于在目标(例如可执行文件、库等)编译过程中添加预处理宏定义的函数。预处理宏定义是在编译阶段进行文本替换的标识符,它可以影响代码的编译过程。

函数的基本语法如下:

1
2
3
4
5
target_compile_definitions(target_name
PRIVATE definition1 definition2 ...
INTERFACE definition3 definition4 ...
PUBLIC definition5 definition6 ...
)
  • **target_name**:目标的名称,可以是可执行文件、库等的名称。
  • PRIVATEINTERFACEPUBLIC:这些关键词用于指定定义的可见性。PRIVATE 表示仅在当前目标内部可见,**INTERFACE** 表示仅在与当前目标链接的目标中可见,**PUBLIC** 表示在当前目标和链接到它的目标中都可见。
  • definition1, definition2, …:要添加的预处理宏定义。

设置编译类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Set a default build type if none was specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message("Setting build type to 'RelWithDebInfo' as none was specified.")
// 如果用户没有明确指定构建类型,该代码将默认构建类型设置为"RelWithDebInfo"
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
// Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
"MinSizeRel" "RelWithDebInfo")
endif()

if(CMAKE_BUILD_TYPE MATCHES "Debug")
message("Debug build")
// 执行与Debug构建相关的代码
elseif(CMAKE_BUILD_TYPE MATCHES "Release")
message("Release build")
// 执行与Release构建相关的代码
elseif(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
message("Release with Debug Info build")
// 执行与RelWithDebInfo构建相关的代码
else()
message("Unknown build type")
// 执行在未知构建类型时的操作
endif()

编译命令检查

方法一

1
2
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)

方法二

1
2
// set the C++ standard to C++ 11
set(CMAKE_CXX_STANDARD 11)

方法三

1
2
// set the C++ standard to the appropriate standard for using auto
target_compile_features(hello_cpp11 PUBLIC cxx_auto_type)

CMake配置文件

configure_file 函数是CMake中的一个非常有用的工具,用于将源文件的内容复制到目标文件中,并在复制过程中进行文本替换。这对于生成配置文件、资源文件等非常有用。以下是**configure_file** 函数的基本用法:

1
configure_file(input_file output_file [@ONLY] [COPYONLY]
  • **input_file**:要作为源的输入文件路径。
  • **output_file**:要生成的目标文件路径。
  • **@ONLY:一个可选参数,当设置为这个值时,只有以@**开头的变量会被替换。如果不设置这个参数,所有变量都会被替换。
  • **COPYONLY**:一个可选参数,当设置为这个值时,只复制输入文件到输出文件,不进行变量替换。这在复制二进制文件等时很有用。

通常,您将在CMake中使用**configure_file**来生成配置文件,其中一些变量的值在构建过程中被设置。

下面是一个示例,演示如何使用**configure_file**生成一个配置文件:

假设你有一个名为 config.h.in 的输入文件,内容如下:

1
2
3
#define PROJECT_NAME "@PROJECT_NAME@"
#define VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define VERSION_MINOR @PROJECT_VERSION_MINOR@

然后,在CMakeLists.txt中,您可以使用**configure_file**来生成 config.h 配置文件:

1
configure_file(config.h.in config.h)

在上述示例中,CMake将 config.h.in 中的**@PROJECT_NAME@@PROJECT_VERSION_MAJOR@@PROJECT_VERSION_MINOR@**等变量替换为实际的值,并生成了一个名为 config.h 的输出文件。您可以在代码中包含 **config.h**,以使用正确的配置信息。

这是一个简单的示例,但是**configure_file**函数非常有用,可以用于生成各种配置文件,包括用于在构建过程中动态生成项目信息的头文件、脚本文件等。

宏和函数的区别

在CMake中,宏(macros)和函数(functions)都是用于封装一系列操作的工具,但它们在使用和行为方面存在一些区别。以下是它们之间的主要区别:

  1. 参数传递方式:
    • 宏(macros): 在调用宏时,参数会在调用点被展开,并将参数的值替换到宏的定义中。这意味着宏在展开时会完整地嵌入其调用点,包括所有参数的值和宏的代码。
    • 函数(functions): 函数的参数是按值传递的,这意味着在函数内部使用参数时,您在函数定义中定义的参数名将作为变量使用,保存传递给函数的实际值。
  2. 变量作用域:
    • 宏(macros): 宏的变量在调用宏时被展开到调用点,并且宏的定义中的所有变量都处于全局作用域。这意味着宏内部定义的变量可以影响宏调用点之外的代码。
    • 函数(functions): 函数内部定义的变量在函数内部有效,不会影响函数调用点之外的代码。函数的参数和局部变量仅在函数内部可见。
  3. 返回值:
    • 宏(macros): 宏没有返回值的概念。它们实际上是一组命令和逻辑的组合,宏的效果直接体现在宏的调用点。
    • 函数(functions): 函数可以有返回值。您可以在函数内部使用 return() 命令来返回一个值,并在函数调用点使用该值。
  4. 定义方式:
    • 宏(macros): 定义宏使用 macro 关键字。
    • 函数(functions): 定义函数使用 function 关键字。
  5. 调用方式:
    • 宏(macros): 调用宏使用宏的名称,后面跟上参数列表,参数之间使用空格分隔。
    • 函数(functions): 调用函数使用函数的名称,后面跟上参数列表,参数之间使用分号 ; 分隔。

综合起来,宏和函数在CMake中都用于封装代码块,但它们在参数传递、变量作用域、返回值等方面存在差异。您可以根据需要选择使用宏或函数来实现不同的封装和逻辑。

find_package、find_library和find_program的区别

find_packagefind_libraryfind_program 都是 CMake 中用于查找不同类型资源的命令,它们有不同的用途和区别:

  1. find_packagefind_package 用于查找已安装的软件包或库,并导入其配置信息。它通常用于查找外部的第三方库或工具,并在项目中使用它们。**find_package** 将检查指定的包是否安装在系统上,并在找到包时配置项目以使用该包。通过 CONFIG 模式,它还可以导入该软件包的配置文件,设置编译选项、链接库等。
  2. find_libraryfind_library 用于查找特定的库文件,并返回库文件的绝对路径。它主要用于查找并指定要链接到项目中的库,以便在编译和链接时使用。您可以指定库的名称以及查找路径,**find_library** 将返回库文件的完整路径,供您在项目中使用。
  3. find_programfind_program 用于查找可执行文件或命令。它允许您在CMake中查找特定的可执行文件,然后可以将找到的可执行文件与目标一起使用,或者执行自定义操作。例如,您可以使用 find_program 来查找编译器、工具或其他外部命令。

综合起来,这些命令在CMake中都具有不同的用途,用于查找不同类型的资源。**find_package** 用于查找库和工具,并导入其配置,**find_library** 用于查找库文件的路径,而 find_program 则用于查找可执行文件的路径。根据您的需求,您可以选择适当的命令来在CMake项目中查找所需的资源。

option的使用

option 函数用于在CMake项目中创建用户可配置的选项。这些选项通常用于允许用户自定义项目的行为或功能开关。**option** 函数接受三个参数:选项名称、选项描述和默认值。

其基本语法如下:

1
option(<option_name> <option_description> <initial_value>)
  • **<option_name>**:选项的名称,这是用户在CMake配置中使用的标识符。
  • **<option_description>**:选项的描述,通常是一段文本,用于说明选项的作用。
  • **<initial_value>**:选项的默认值,可以是 ON 或 **OFF**,表示选项的初始状态。

例如,以下是一个使用 option 函数的示例:

1
option(ENABLE_FEATURE_X "Enable feature X" ON)

在这个示例中,我们定义了一个名为 ENABLE_FEATURE_X 的选项,它用于启用或禁用某个功能(feature X)。默认情况下,该选项的值为 **ON**,表示功能是启用的。

要在项目中使用这个选项,您可以使用 if 语句来检查选项的值,例如:

1
2
3
4
if (ENABLE_FEATURE_X)
# 启用了功能 X,执行相关操作
# ...
endif()

用户在CMake配置中可以使用 -D 选项来设置选项的值。例如,要禁用功能 X,可以执行以下命令:

1
cmake -DENABLE_FEATURE_X:BOOL=OFF ..

这将覆盖默认值,并在CMake配置中禁用功能 X。

总之,**option** 函数允许您在CMake项目中创建用户可配置的选项,以便根据用户的需求来配置项目的行为。

使用FetchContent引入第三方库

FetchContent 是一个CMake模块,用于在CMake项目中下载和管理外部依赖项。它的目标是简化项目依赖项的管理,使得在项目中集成和使用第三方库更加方便。**FetchContent** 模块提供了一组函数,其中最重要的函数是 FetchContent_Declare 和 **FetchContent_MakeAvailable**。

以下是 FetchContent 相关函数的基本使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cmakeCopy code
include(FetchContent)

# 声明要下载的外部依赖项
FetchContent_Declare(
my_dependency
GIT_REPOSITORY https://github.com/example/my_dependency.git
GIT_TAG v1.0.0 # 可选:指定要下载的特定标签或分支
)

# 配置并下载外部依赖项
FetchContent_GetProperties(my_dependency)
if(NOT my_dependency_POPULATED)
FetchContent_Populate(my_dependency)
add_subdirectory(${my_dependency_SOURCE_DIR} ${my_dependency_BINARY_DIR})
endif()

# 使用已下载的外部依赖项
target_link_libraries(my_project PRIVATE my_dependency)

上述示例中的关键步骤包括:

  1. 包含 FetchContent 模块:首先,使用 include(FetchContent) 命令包含 FetchContent 模块。
  2. 声明要下载的外部依赖项:使用 FetchContent_Declare 函数声明要下载的外部依赖项。您需要提供依赖项的名称以及其来源(通常是一个Git仓库的URL)。您还可以选择性地指定特定的标签或分支。
  3. 配置并下载外部依赖项:使用 FetchContent_GetProperties 函数检查依赖项是否已经被下载和配置。如果尚未下载,使用 FetchContent_Populate 函数来下载和配置依赖项。这些函数会将依赖项的源代码下载到项目的构建目录中。
  4. 使用已下载的外部依赖项:在项目中使用已下载的依赖项,例如,通过链接到它们的库文件。

FetchContent 还提供了其他函数和选项,用于自定义依赖项的下载和配置过程。这使得您可以更灵活地管理和集成外部依赖项,而无需手动下载和管理它们。注意,**FetchContent** 在CMake版本3.11及更高版本中可用。

作者

echo

发布于

2023-08-27

更新于

2024-08-10

许可协议

评论