7.2 将CMake源代码分成模块

NOTE:此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-7/recipe-02 中找到。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。

项目通常从单个CMakeLists.txt文件开始,随着时间的推移,这个文件会逐渐增长。本示例中,我们将演示一种将CMakeLists.txt分割成更小单元的机制。将CMakeLists.txt拆分为模块有几个动机,这些模块可以包含在主CMakeLists.txt或其他模块中:

  • CMakeLists.txt更易于阅读。
  • CMake模块可以在其他项目中重用。
  • 与函数相结合,模块可以帮助我们限制变量的作用范围。

本示例中,我们将演示如何定义和包含一个宏,该宏允许我们获得CMake的彩色输出(用于重要的状态消息或警告)。

准备工作

本例中,我们将使用两个文件,主CMakeLists.txtcmake/colors.cmake:

  1. .
  2. ├── cmake
  3. └── colors.cmake
  4. └── CMakeLists.txt

cmake/colors.cmake文件包含彩色输出的定义:

  1. # colorize CMake output
  2. # code adapted from stackoverflow: http://stackoverflow.com/a/19578320
  3. # from post authored by https://stackoverflow.com/users/2556117/fraser
  4. macro(define_colors)
  5. if(WIN32)
  6. # has no effect on WIN32
  7. set(ColourReset "")
  8. set(ColourBold "")
  9. set(Red "")
  10. set(Green "")
  11. set(Yellow "")
  12. set(Blue "")
  13. set(Magenta "")
  14. set(Cyan "")
  15. set(White "")
  16. set(BoldRed "")
  17. set(BoldGreen "")
  18. set(BoldYellow "")
  19. set(BoldBlue "")
  20. set(BoldMagenta "")
  21. set(BoldCyan "")
  22. set(BoldWhite "")
  23. else()
  24. string(ASCII 27 Esc)
  25. set(ColourReset "${Esc}[m")
  26. set(ColourBold "${Esc}[1m")
  27. set(Red "${Esc}[31m")
  28. set(Green "${Esc}[32m")
  29. set(Yellow "${Esc}[33m")
  30. set(Blue "${Esc}[34m")
  31. set(Magenta "${Esc}[35m")
  32. set(Cyan "${Esc}[36m")
  33. set(White "${Esc}[37m")
  34. set(BoldRed "${Esc}[1;31m")
  35. set(BoldGreen "${Esc}[1;32m")
  36. set(BoldYellow "${Esc}[1;33m")
  37. set(BoldBlue "${Esc}[1;34m")
  38. set(BoldMagenta "${Esc}[1;35m")
  39. set(BoldCyan "${Esc}[1;36m")
  40. set(BoldWhite "${Esc}[1;37m")
  41. endif()
  42. endmacro()

具体实施

来看下我们如何使用颜色定义,来生成彩色状态消息:

  1. 从一个熟悉的头部开始:

    1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. project(recipe-02 LANGUAGES NONE)
  2. 然后,将cmake子目录添加到CMake模块搜索的路径列表中:

    1. list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
  3. 包括colors.cmake模块,调用其中定义的宏:

    1. include(colors)
    2. define_colors()
  4. 最后,打印了不同颜色的信息:

    1. message(STATUS "This is a normal message")
    2. message(STATUS "${Red}This is a red${ColourReset}")
    3. message(STATUS "${BoldRed}This is a bold red${ColourReset}")
    4. message(STATUS "${Green}This is a green${ColourReset}")
    5. message(STATUS "${BoldMagenta}This is bold${ColourReset}")
  5. 测试一下(如果使用macOS或Linux,以下的输出应该出现屏幕上):7.2 将CMake源代码分成模块 - 图1

工作原理

这个例子中,不需要编译代码,也不需要语言支持,我们已经用LANGUAGES NONE明确了这一点:

  1. project(recipe-02 LANGUAGES NONE)

我们定义了define_colors宏,并将其放在cmake/colors.cmake。因为还是希望使用调用宏中定义的变量,来更改消息中的颜色,所以我们选择使用宏而不是函数。我们使用以下行包括宏和调用define_colors:

  1. include(colors)
  2. define_colors()

我们还需要告诉CMake去哪里查找宏:

  1. list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

include(colors)命令指示CMake搜索${CMAKE_MODULE_PATH},查找名称为colors.cmake的模块。

例子中,我们没有按以下的方式进行:

  1. list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
  2. include(colors)

而是使用一个显式包含的方式:

  1. include(cmake/colors.cmake)

更多信息

推荐的做法是在模块中定义宏或函数,然后调用宏或函数。将包含模块用作函数调用不是很好的方式。除了定义函数和宏以及查找程序、库和路径之外,包含模块不应该做更多的事情。实际的include命令不应该定义或修改变量,其原因是重复的include(可能是偶然的)不应该引入任何不想要的副作用。在第5节中,我们将创建一个防止多次包含的保护机制。