7.8 使用target_sources避免全局变量

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

本示例中,我们将讨论前一个示例的另一种方法,并不使用add_subdirectory的情况下,使用module include组装不同的CMakeLists.txt文件。这种方法的灵感来自https://crascit.com/2016/01/31/enhance-sours-file-handling-with-target_sources/ ,其允许我们使用target_link_libraries链接到当前目录之外定义的目标。

准备工作

将使用与前一个示例相同的源代码。惟一的更改将出现在CMakeLists.txt文件中,我们将在下面的部分中讨论这些更改。

具体实施

  1. CMakeLists.txt包含以下内容:

    1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. project(recipe-08 LANGUAGES CXX)
    3. set(CMAKE_CXX_STANDARD 11)
    4. set(CMAKE_CXX_EXTENSIONS OFF)
    5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
    6. include(GNUInstallDirs)
    7. set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
    8. ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
    9. set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
    10. ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
    11. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
    12. ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
    13. # defines targets and sources
    14. include(src/CMakeLists.txt)
    15. include(external/CMakeLists.txt)
    16. enable_testing()
    17. add_subdirectory(tests)
  2. 与前一个示例相比,external/CMakeLists.txt文件没有变化。

  3. src/CMakeLists.txt文件定义了两个库(automaton和evolution):

    1. add_library(automaton "")
    2. add_library(evolution "")
    3. include(${CMAKE_CURRENT_LIST_DIR}/evolution/CMakeLists.txt)
    4. include(${CMAKE_CURRENT_LIST_DIR}/initial/CMakeLists.txt)
    5. include(${CMAKE_CURRENT_LIST_DIR}/io/CMakeLists.txt)
    6. include(${CMAKE_CURRENT_LIST_DIR}/parser/CMakeLists.txt)
    7. add_executable(automata "")
    8. target_sources(automata
    9. PRIVATE
    10. ${CMAKE_CURRENT_LIST_DIR}/main.cpp
    11. )
    12. target_link_libraries(automata
    13. PRIVATE
    14. automaton
    15. conversion
    16. )
  4. src/evolution/CMakeLists.txt文件包含以下内容:

    1. target_sources(automaton
    2. PRIVATE
    3. ${CMAKE_CURRENT_LIST_DIR}/evolution.cpp
    4. PUBLIC
    5. ${CMAKE_CURRENT_LIST_DIR}/evolution.hpp
    6. )
    7. target_include_directories(automaton
    8. PUBLIC
    9. ${CMAKE_CURRENT_LIST_DIR}
    10. )
    11. target_sources(evolution
    12. PRIVATE
    13. ${CMAKE_CURRENT_LIST_DIR}/evolution.cpp
    14. PUBLIC
    15. ${CMAKE_CURRENT_LIST_DIR}/evolution.hpp
    16. )
    17. target_include_directories(evolution
    18. PUBLIC
    19. ${CMAKE_CURRENT_LIST_DIR}
    20. )
  5. 其余CMakeLists.txt文件和src/initial/CMakeLists.txt相同:

    1. target_sources(automaton
    2. PRIVATE
    3. ${CMAKE_CURRENT_LIST_DIR}/initial.cpp
    4. PUBLIC
    5. ${CMAKE_CURRENT_LIST_DIR}/initial.hpp
    6. )
    7. target_include_directories(automaton
    8. PUBLIC
    9. ${CMAKE_CURRENT_LIST_DIR}
    10. )
  6. 配置、构建和测试的结果与前面的方法相同:

    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4. $ cmake --build build
    5. $ ctest
    6. Running tests...
    7. Start 1: test_evolution
    8. 1/1 Test #1: test_evolution ................... Passed 0.00 sec
    9. 100% tests passed, 0 tests failed out of 1

工作原理

与之前的示例不同,我们定义了三个库:

  • conversion(在external定义)
  • automaton(包含除转换之外的所有源)
  • evolution(在src/evolution中定义,并通过cpp_test链接)

本例中,通过使用include()引用CMakeLists.txt文件,我们在父范围内,仍然能保持所有目标可用:

  1. include(src/CMakeLists.txt)
  2. include(external/CMakeLists.txt)

我们可以构建一个包含树,记住当进入子目录(src/CMakeLists.txt)时,我们需要使用相对于父范围的路径:

  1. include(${CMAKE_CURRENT_LIST_DIR}/evolution/CMakeLists.txt)
  2. include(${CMAKE_CURRENT_LIST_DIR}/initial/CMakeLists.txt)
  3. include(${CMAKE_CURRENT_LIST_DIR}/io/CMakeLists.txt)
  4. include(${CMAKE_CURRENT_LIST_DIR}/parser/CMakeLists.txt)

这样,我们就可以定义并链接到通过include()语句访问文件树中任何位置的目标。但是,我们应该选择在对维护人员和代码贡献者容易看到的地方,去定义它们。

更多信息

我们可以再次使用CMake和Graphviz (http://www.graphviz.org/)生成这个项目的依赖关系图:

  1. $ cd build
  2. $ cmake --graphviz=example.dot ..
  3. $ dot -T png example.dot -o example.png

对于当前设置,我们得到如下依赖关系图:

7.8 使用target_sources避免全局变量 - 图1