【问题标题】:Check for optionally targets in cmake that are not in the correct order检查 cmake 中顺序不正确的可选目标
【发布时间】:2015-09-24 16:10:55
【问题描述】:

我目前正在开发一个使用 cmake 作为构建系统的大型软件项目。但是我有一个问题要检查另一个目标是否存在(或将存在)。

例如,有根 CMakeLists.txt 和两个可以选择作为子文件夹添加到软件项目的模块。

.
├── A
│   └── CMakeLists.txt
├── B
│   └── CMakeLists.txt
└── CMakeLists.txt

在根 CMakeLists 中,这些模块是通过 add_subdirectory 命令添加的:

cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
project(root)
add_subdirectory(./A)
add_subdirectory(./B)

在某些情况下,如果模块 B 存在,我想检查模块 A,并向模块 A 中的编译选项添加定义:

cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
project(A)
add_libary(A a.cpp a.hpp)
if (TARGET B)
    target_compile_definitions(A PUBLIC HAVE_B)
endif()

if(TARGET target-name)

command 将返回 false,因为这只有在模块以正确的顺序添加到根 CMakeLists.txt 时才有效。

cmake 中是否有另一个不依赖于目标顺序的检查?

问候 佩里

【问题讨论】:

  • CMake 进程配置阶段按顺序进行。所以,当A/CMakeLists.txt正在处理时,CMake没有任何信息,B/CMakeLists.txt是否会被处理。以正确的顺序枚举它们是模块集成商的问题。
  • 欢迎来到 StackOverflow。当我写一个关于如何处理你的 CMake 问题的答案时,我在想你的问题可能有一个 C++ 解决方案。一种解决方案是使用dependency injection pattern 吗?带有#ifdef HAVE_B 签入的东西可能比使用接口的东西更难进行单元测试。

标签: c++ cmake


【解决方案1】:

这可以使用宏来解决。

这个想法是每个子目录必须有一个关联的顶级变量,其他子目录可以检查,因此必须在顶部 CMakeLists.txt 文件中声明它们。此外,所有变量都必须在 add_subdirectory 规则开始之前定义。

所以我们的宏会把子目录列表作为参数,先输出变量声明列表,再输出子目录列表。

下面是这样一个宏的代码:

macro(declare_modules)
    foreach(module ${ARGN})
        set(TARGET_${module} yes)
    endforeach()

    foreach(module ${ARGN})
        add_subdirectory(./${module})
    endforeach()
endmacro(declare_modules)

可以这样使用:

declare_modules(A B C)

它会展开成如下代码:

foreach(module A B C)
    set(TARGET_${module} yes)
endforeach()

foreach(module A B C)
    add_subdirectory(./${module})
endforeach()

这将导致变量 TARGET_ATARGET_BTARGET_C 被声明并设置为 yes,并且它们将可用于所有模块。

添加新模块只需向declare_modules 调用添加新参数即可。

【讨论】:

    【解决方案2】:

    因为根CMakeLists.txt文件必须知道项目的子目录,所以我通常把我的可选设置放在那里:

    CMakeLists.txt

    cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
    
    project(root CXX)
    
    if (EXISTS "B/CMakeLists.txt")
        set(HAVE_B 1)
    endif()
    
    add_subdirectory(A)
    if (HAVE_B)
        add_subdirectory(B)
    endif()
    

    A/CMakeLists.txt

    add_library(A a.cpp a.hpp)
    if (HAVE_B)
        target_link_libraries(A B)
    endif()
    

    B/CMakeLists.txt

    add_library(B b.cpp b.hpp)
    target_compile_definitions(B PUBLIC HAVE_B)
    target_include_directories(B PUBLIC .)
    

    有很多地方可以设置编译器定义和必要的包含目录。在此示例中,我选择使用target_compile_definitions(... PUBLIC ...)target_include_directories(... PUBLIC ...) 进行宣传。然后,您只需使用 target_link_libraries() 设置依赖项,然后 CMake 处理其余部分(并且 target_link_libraries() 确实接受前向声明)。

    【讨论】:

      【解决方案3】:

      没有可用的 $<TARGET_EXISTS:...> 生成器表达式,您可以在其中编写:

      add_libary(A a.cpp a.hpp)
      target_compile_definitions(A PUBLIC $<<TARGET_EXISTS:B>:HAVE_B=1>)
      

      虽然我向 cmake 提出这个建议,here。作为一种解决方法,您可以创建影子目标来跟踪其他目录中的目标:

      # Custom property to check if target exists
      define_property(TARGET PROPERTY "INTERFACE_TARGET_EXISTS"
          BRIEF_DOCS "True if target exists"
          FULL_DOCS "True if target exists"
      )
      
      # Create shadow target to notify that the target exists
      macro(shadow_notify TARGET)
          if(NOT TARGET _shadow_target_${TARGET})
              add_library(_shadow_target_${TARGET} INTERFACE IMPORTED GLOBAL)
          endif()
          set_target_properties(_shadow_target_${TARGET} PROPERTIES INTERFACE_TARGET_EXISTS 1)
      endmacro()
      # Check if target exists by querying the shadow target
      macro(shadow_exists OUT TARGET)
          if(NOT TARGET _shadow_target_${TARGET})
              add_library(_shadow_target_${TARGET} INTERFACE IMPORTED GLOBAL)
              set_target_properties(_shadow_target_${TARGET} PROPERTIES INTERFACE_TARGET_EXISTS 0)
          endif()
          set(${OUT} "$<TARGET_PROPERTY:_shadow_target_${TARGET},INTERFACE_TARGET_EXISTS>")
      endmacro()
      

      这将创建两个函数,以及一个用于跟踪目标是否存在的自定义属性。 shadow_notify 将创建一个影子目标(如果它不存在),然后将 INTERFACE_TARGET_EXISTS 属性设置为 true(如果它存在)。

      shadow_exists 目标将创建一个生成器表达式来查询目标是否存在。如果INTERFACE_TARGET_EXISTS 属性设置为0,则它​​通过创建一个新的影子目标来执行此操作。然后它将创建一个生成器表达式,该表达式将查询影子目标上的INTERFACE_TARGET_EXISTS 属性。影子目标将始终存在,如果实际目标存在,则shadow_notify 将属性设置为 1。

      那么你可以在你的cmake中写这个:

      add_libary(A a.cpp a.hpp)
      # Notify that target A exists
      shadow_notify(A)
      # Check if target B exists
      shadow_exists(HAS_B B)
      target_compile_definitions(A PUBLIC $<${HAS_B}:HAVE_B=1>)
      

      当您创建库B 时,您还需要调用shadow_notify(B)

      如果您不喜欢这些额外的shadow_notify 调用,您也可以覆盖add_library 来为您完成:

      macro(add_library LIB)
          _add_library(${LIB} ${ARGN})
          if(NOT TARGET _shadow_target_${TARGET})
              _add_library(_shadow_target_${TARGET} INTERFACE IMPORTED GLOBAL)
          endif()
          set_target_properties(_shadow_target_${TARGET} PROPERTIES INTERFACE_TARGET_EXISTS 1)
      endmacro()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-16
        相关资源
        最近更新 更多