【006】告别硬编译!CMake 选项配置让你的代码编译更智能

文章目录

  • 一、绪论
  • 二、向用户显示选项
    • 2.1、option 命令简介
    • 2.2、使用示例
  • 三、选项依赖与模块扩展
  • 四、总结

一、绪论

许多开发者在使用 CMake 时,常常将注意力集中在基本的构建任务上,而忽略了其更为高级和实用的特性,例如构建选项和编译定义。这些特性能够极大地提升构建过程的灵活性和可维护性,允许根据不同的环境、需求和目标平台,定制化构建流程,从而实现代码的优化、功能的裁剪以及特定平台的适配。

本文旨在深入探讨 CMake 构建选项和编译定义的应用,结合实际案例,详细剖析如何通过 option()cmake_dependent_option() 等命令,实现灵活的构建配置和编译控制。带领读者一步步了解如何在 CMake 中定义和使用选项开关、如何在编译时动态地定义宏。

通过阅读本文,不仅能够掌握 CMake 构建选项和编译定义的使用方法,更重要的是能够理解其背后的设计思想和应用场景,从而在实际项目中灵活运用这些技巧,构建出更加高效、可维护和可定制的软件系统。

二、向用户显示选项

前面的文章介绍了条件语句,并通过硬编码的方式设置逻辑变量的值。 这种做法存在局限性:它限制了用户在不修改 CMakeLists.txt 文件的情况下调整构建行为,也未能清晰地向用户表明这些变量可以从外部修改。 为了解决这个问题,强烈推荐在 CMakeLists.txt 文件中使用 option() 命令。

option() 命令的功能是创建选项开关,这些选项可以以图形界面(例如 CMake GUI)或命令行参数的形式呈现给用户,允许用户在构建时动态地控制构建系统的生成行为。 通过 option() 定义的选项,CMake 会自动处理变量的设置,并为用户提供友好的交互方式。

使用 option() 不仅方便了用户修改构建选项,而且清晰地传达了哪些变量可以从外部进行配置,提高了代码的可维护性和可读性。 此外,它也鼓励了一种更模块化和可配置的构建方法,使得项目更容易适应不同的环境和需求。

本节的示例将详细展示如何使用 option() 命令,演示如何定义选项、设置默认值,以及如何在条件语句中利用这些选项,从而实现灵活的构建过程定制。

2.1、option 命令简介

option() 命令用于定义可在构建过程中由用户控制的布尔型选项。它提供了一种清晰且标准的方式,将构建配置暴露给用户,允许他们根据需要启用或禁用特定功能、选择不同的编译模式等。

基本语法:

option(<variable> "" [initial_value])

参数说明:

  • (必需): 定义选项的变量名。这个变量会被设置为 ONOFF,分别对应选项的启用和禁用。建议使用大写字母和下划线,以遵循 CMake 的命名约定(例如:ENABLE_FEATURE_X)。

  • (必需):一个字符串,用于描述该选项的作用。当用户使用 CMake 的 GUI 工具 (如 cmake-gui) 或命令行工具 (cmake -LH) 时,这个字符串会显示给用户,帮助他们理解选项的含义。应该尽可能清晰、简洁地描述选项的功能和影响。

  • [initial_value](可选):选项的初始值,可以设置为 ONOFF,默认为 OFF这个初始值仅仅在第一次配置 CMake 时生效。如果变量 之前已经被定义过(例如在缓存中),CMake 会使用缓存中的值,而不是初始值。

工作原理:

  1. option() 命令会在 CMake 的作用域内创建一个名为 的变量。

  2. CMake 会在 GUI 和命令行界面中创建一个相应的开关或复选框,让用户可以设置该变量的值。

  3. 用户可以通过 GUI 或命令行设置 的值为 ONOFF

  4. 影响构建过程: 可以使用 if() 语句来根据 的值,选择性地执行 CMake 命令,从而控制构建过程。

2.2、使用示例

回顾之前的静态/动态库示例,曾采用硬编码的方式来设置 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_LIBRARYON,则会生成静态库 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>)

参数说明:

  • : 依赖选项的变量名。
  • "": 依赖选项的描述信息。
  • : 如果 的值为 时, 的初始值。
  • : 被依赖的选项的变量名。
  • : 必须具备的状态(ONOFF),才能使 应用于

示例:

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_LIBRARYOFF,因此不会构建库。 这是因为 MAKE_SHARED_LIBRARY 依赖于 USE_LIBRARY,只有当 USE_LIBRARYON 时,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() 命令引入额外的功能。
【006】告别硬编译!CMake 选项配置让你的代码编译更智能_第1张图片

你可能感兴趣的:(【006】告别硬编译!CMake 选项配置让你的代码编译更智能)