许多开发者在使用 CMake 时,常常将注意力集中在基本的构建任务上,而忽略了其更为高级和实用的特性,例如构建选项和编译定义。这些特性能够极大地提升构建过程的灵活性和可维护性,允许根据不同的环境、需求和目标平台,定制化构建流程,从而实现代码的优化、功能的裁剪以及特定平台的适配。
本文旨在深入探讨 CMake 构建选项和编译定义的应用,结合实际案例,详细剖析如何通过 option()
、cmake_dependent_option()
等命令,实现灵活的构建配置和编译控制。带领读者一步步了解如何在 CMake 中定义和使用选项开关、如何在编译时动态地定义宏。
通过阅读本文,不仅能够掌握 CMake 构建选项和编译定义的使用方法,更重要的是能够理解其背后的设计思想和应用场景,从而在实际项目中灵活运用这些技巧,构建出更加高效、可维护和可定制的软件系统。
前面的文章介绍了条件语句,并通过硬编码的方式设置逻辑变量的值。 这种做法存在局限性:它限制了用户在不修改 CMakeLists.txt
文件的情况下调整构建行为,也未能清晰地向用户表明这些变量可以从外部修改。 为了解决这个问题,强烈推荐在 CMakeLists.txt
文件中使用 option()
命令。
option()
命令的功能是创建选项开关,这些选项可以以图形界面(例如 CMake GUI)或命令行参数的形式呈现给用户,允许用户在构建时动态地控制构建系统的生成行为。 通过 option()
定义的选项,CMake 会自动处理变量的设置,并为用户提供友好的交互方式。
使用 option()
不仅方便了用户修改构建选项,而且清晰地传达了哪些变量可以从外部进行配置,提高了代码的可维护性和可读性。 此外,它也鼓励了一种更模块化和可配置的构建方法,使得项目更容易适应不同的环境和需求。
本节的示例将详细展示如何使用 option()
命令,演示如何定义选项、设置默认值,以及如何在条件语句中利用这些选项,从而实现灵活的构建过程定制。
option()
命令用于定义可在构建过程中由用户控制的布尔型选项。它提供了一种清晰且标准的方式,将构建配置暴露给用户,允许他们根据需要启用或禁用特定功能、选择不同的编译模式等。
基本语法:
option(<variable> "" [initial_value])
参数说明:
(必需): 定义选项的变量名。这个变量会被设置为 ON
或 OFF
,分别对应选项的启用和禁用。建议使用大写字母和下划线,以遵循 CMake 的命名约定(例如:ENABLE_FEATURE_X
)。
(必需):一个字符串,用于描述该选项的作用。当用户使用 CMake 的 GUI 工具 (如 cmake-gui
) 或命令行工具 (cmake -LH
) 时,这个字符串会显示给用户,帮助他们理解选项的含义。应该尽可能清晰、简洁地描述选项的功能和影响。
[initial_value]
(可选):选项的初始值,可以设置为 ON
或 OFF
,默认为 OFF
。这个初始值仅仅在第一次配置 CMake 时生效。如果变量
之前已经被定义过(例如在缓存中),CMake 会使用缓存中的值,而不是初始值。
工作原理:
option()
命令会在 CMake 的作用域内创建一个名为
的变量。
CMake 会在 GUI 和命令行界面中创建一个相应的开关或复选框,让用户可以设置该变量的值。
用户可以通过 GUI 或命令行设置
的值为 ON
或 OFF
。
影响构建过程: 可以使用 if()
语句来根据
的值,选择性地执行 CMake 命令,从而控制构建过程。
回顾之前的静态/动态库示例,曾采用硬编码的方式来设置 USE_LIBRARY
变量,决定是否将源代码编译成库。这种硬编码方式限制了用户的自定义能力,无法在不修改 CMakeLists.txt
文件的情况下切换构建行为。
为了解决这个问题,这里使用 option()
命令替换之前的 set(USE_LIBRARY OFF)
语句,使其成为一个可配置的选项,同时提供一个合理的默认值。这样,用户可以在构建时通过外部参数来覆盖默认值,从而灵活地控制构建过程。
使用 option()
命令定义 USE_LIBRARY
选项:
option(USE_LIBRARY "Compile sources into a library" OFF)
这条语句的含义:
option(USE_LIBRARY ...)
: 声明一个名为 USE_LIBRARY
的选项。"Compile sources into a library"
: 为该选项提供一个描述性的帮助信息,告知用户该选项控制是否将源代码编译成库。这个信息会在 CMake GUI 或命令行工具中显示。OFF
: 设置该选项的默认值为 OFF
,即默认情况下不编译成库。完整的 CMakeLists.txt 内容:
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(hello-world LANGUAGES CXX)
# 设置 C++ 标准 (可选,但推荐)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(USE_LIBRARY "Compile sources into a library" OFF)
list(APPEND _sources include/message.hpp src/message.cpp)
if (USE_LIBRARY)
message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
# 创建一个静态库 (或动态库)
add_library(message ${_sources})
add_executable(hello-world helloWorld.cpp)
target_link_libraries(hello-world message)
else()
# 创建一个可执行文件
add_executable(hello-world helloWorld.cpp ${_sources})
endif()
现在,可以通过 -DCLI
选项将信息传递给 CMake,从而覆盖 USE_LIBRARY
的默认值。例如,要启用库的构建:
mkdir -p build
cd build
cmake -D USE_LIBRARY=ON ..
通过 -D
选项将 USE_LIBRARY
变量设置为 ON
。 这会覆盖 option()
命令中设置的默认值 OFF
。
输出:
-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Compile sources into a library? ON
-- Configuring done
-- Generating done
构建项目:
cmake --build .
执行此命令后,CMake 会根据 USE_LIBRARY
的值来决定是否将源代码编译成库。如果 USE_LIBRARY
为 ON
,则会生成静态库 libmessage.a
。从CMake输出中可以看到相关的提示信息:
-- Compile sources into a library? ON
-- Configuring done
-- Generating done
-- Build files have been written to: /home/fly/workspace/cmake-learning-code/recipe_07_option/build
[ 25%] Building CXX object CMakeFiles/message.dir/src/message.cpp.o
[ 50%] Linking CXX static library libmessage.a
[ 50%] Built target message
[ 75%] Building CXX object CMakeFiles/hello-world.dir/helloWorld.cpp.o
[100%] Linking CXX executable hello-world
[100%] Built target hello-world
可以看到,message
目标被构建成了一个静态库 libmessage.a
。
-D
选项的通用性:
需要注意的是,-D
选项可以用于为 CMake 设置任何类型的变量,不仅限于布尔型的逻辑变量。例如,可以使用 -D
选项来设置路径、字符串等变量,从而控制构建过程的各个方面。
通过使用 option()
命令和 -D
选项,极大地增强了构建系统的灵活性,用户能够方便地根据自己的需求定制构建过程,而无需修改 CMakeLists.txt
文件。
在构建复杂项目中,选项之间存在依赖关系是很常见的。例如,希望提供生成静态库或动态库的选项,但只有在启用了通用库构建的情况下,这些选项才有意义。为了应对这种场景,CMake 提供了 cmake_dependent_option()
命令,允许定义依赖于其他选项的选项。
cmake_dependent_option()
命令: 用于定义一个选项,该选项的初始值和可见性取决于另一个选项的值。
语法:
cmake_dependent_option(<variable> "" <value> <depend_var> <depend_status>)
参数说明:
: 依赖选项的变量名。""
: 依赖选项的描述信息。
: 如果
的值为
时,
的初始值。
: 被依赖的选项的变量名。
:
必须具备的状态(ON
或 OFF
),才能使
应用于
。示例:
include(CMakeDependentOption)
option(USE_LIBRARY "Enable library compilation" OFF)
cmake_dependent_option(
MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF
"USE_LIBRARY" ON
)
cmake_dependent_option(
MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON
"USE_LIBRARY" ON
)
实验效果, 完整 CMakeLists.txt 内容如下:
include(CMakeDependentOption)
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(hello-world LANGUAGES CXX)
# 设置 C++ 标准 (可选,但推荐)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(USE_LIBRARY "Compile sources into a library" OFF)
cmake_dependent_option(MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF "USE_LIBRARY" ON)
cmake_dependent_option(MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON "USE_LIBRARY" ON)
list(APPEND _sources include/message.hpp src/message.cpp)
if (USE_LIBRARY)
message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
# 创建一个静态库 (或动态库)
add_library(message ${_sources})
add_executable(hello-world helloWorld.cpp)
target_link_libraries(hello-world message)
else()
# 创建一个可执行文件
add_executable(hello-world helloWorld.cpp ${_sources})
endif()
执行以下 CMake 命令:
mkdir -p build
cd build
cmake -D USE_LIBRARY=OFF -D MAKE_SHARED_LIBRARY=ON ..
输出:
-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/fly/workspace/cmake-learning-code/recipe_07_dependent_option/build
尽管尝试将 MAKE_SHARED_LIBRARY
设置为 ON
,但由于 USE_LIBRARY
为 OFF
,因此不会构建库。 这是因为 MAKE_SHARED_LIBRARY
依赖于 USE_LIBRARY
,只有当 USE_LIBRARY
为 ON
时,MAKE_SHARED_LIBRARY
选项才会被考虑。CMake配置时,MAKE_SHARED_LIBRARY
会维持默认值或者如果之前有缓存值则使用缓存值,但是实际构建中由于USE_LIBRARY
为OFF, 所以构建库的相关代码不会执行。
CMake 的模块机制:
cmake_dependent_option()
命令并非 CMake 内置命令,而是由一个名为 CMakeDependentOption
的模块提供的。 为了使用 cmake_dependent_option()
命令,需要在 CMakeLists.txt
文件中使用 include(CMakeDependentOption)
命令包含该模块。
模块的必要性:
CMake 的设计理念是提供一个简洁的核心功能集,并通过模块来扩展其语法和功能。 模块可以由 CMake 自带,也可以由用户自定义。 通过这种模块化的设计,CMake 能够保持核心功能的简洁性,同时提供高度的可扩展性。 如果没有 include(CMakeDependentOption)
,CMake 将无法识别 cmake_dependent_option()
命令,并会报错。
获取模块帮助信息:
CMake 提供了方便的命令行工具来获取模块的帮助信息。 可以使用以下命令来查看 CMakeDependentOption
模块的帮助文档:
cmake --help-module CMakeDependentOption
该命令会将 CMakeDependentOption
模块的完整手册页面打印到终端,包括该模块提供的命令、变量、以及用法示例。 这对于理解和使用 CMake 模块非常有帮助。 CMake 也支持 --help-command
和 --help-variable
来分别获取某个命令或变量的帮助信息。
参见 https://cmake.org/cmake/help/latest/module/CMakeDependentOption.html
本文深入探讨了 CMake 构建选项和编译定义的使用。通过 option()
命令,可以将构建配置暴露给用户,实现灵活的定制。cmake_dependent_option()
命令则用于处理选项间的依赖关系,构建更复杂的配置逻辑。使用 -D
选项可以覆盖默认值,实现外部配置。同时,CMake 的模块化设计允许通过 include()
命令引入额外的功能。