【问题标题】:cmake: add_custom_command only invoked first timecmake:add_custom_command 仅在第一次调用
【发布时间】:2013-10-22 21:34:31
【问题描述】:

对于自定义目标(使用 add_custom_target 创建),我遇到了 add_custom_command 的问题。

我的总体思路是在 cmake 工具链中加入一个静态代码分析工具。我的解决方案基于此处描述的解决方案:https://github.com/rpavlik/cmake-modules/blob/master/CppcheckTargets.cmake

简而言之,我要为其运行静态代码分析的每个项目都有以下两行代码:

include(cppcheck)
add_cppcheck(${le_project} STYLE POSSIBLE_ERROR FAIL_ON_WARNINGS)

模块在文件顶部有这个:

if (NOT TARGET ANALYZE_CODE)

    add_custom_target(ANALYZE_CODE WORKING_DIRECTORY ${LE_LITEN_ROOT})
    set_target_properties(ANALYZE_CODE PROPERTIES EXCLUDE_FROM_ALL TRUE)

  endif ()

随后在函数中添加了自定义命令:

 add_custom_command(TARGET
      ANALYZE_CODE
      PRE_BUILD
      COMMAND
      ${CPPCHECK_EXECUTABLE}
      ${CPPCHECK_QUIET_ARG}
      ${CPPCHECK_TEMPLATE_ARG}
      ${_cppcheck_args}
      ${_files}
      WORKING_DIRECTORY
      "${CMAKE_CURRENT_SOURCE_DIR}"
      COMMENT
      "${_name}_cppcheck: Running cppcheck on target ${_name}..."
      VERBATIM)

我看到的问题是只为首先包含该文件的项目添加该命令。我不确定为什么以及发生了什么。我使用 message() 命令验证了以下内容:

  • 目标只创建一次
  • add_custom_command 为调用该函数的每个项目运行,并带有适当的参数

但是当我实际在visual studio中查看目标时,只添加了第一个包含/函数调用命令。

如果只包含文件而不调用函数,则根本不添加自定义命令。

期望的行为:

我想要一个名为“ANALYZE_CODE”的目标来运行调用函数添加的所有命令。

即如果 3 个项目包含上述两行,则目标 ANALYZE_CODE 会创建一次,但会向其中添加 3 个自定义命令,每个项目一个。

【问题讨论】:

    标签: visual-studio cmake cmake-custom-command


    【解决方案1】:

    事实证明,您在这里有点卡在岩石和坚硬的地方之间。我认为这个问题归结为几个因素。

    首先,虽然文档没有说清楚,add_custom_command(TARGET ...) 仅适用于在同一目录中创建的目标。所以第一个调用include(cppcheck)的子项目是唯一一个可以有效地将自定义命令添加到目标ANALYZE_CODE的子项目。

    解决方法似乎是将所有对 add_cppcheck 的调用从它们各自的子目录移至顶级 CMakeLists 文件。

    include(cppcheck)
    add_cppcheck(${le_first_project} STYLE POSSIBLE_ERROR FAIL_ON_WARNINGS)
    add_cppcheck(${le_second_project} STYLE POSSIBLE_ERROR FAIL_ON_WARNINGS)
    ...
    

    这不是一个很好的解决方案,因为这些确实属于它们自己的子目录。但更大的问题是源文件上的属性只存在于添加它们的 CMakeLists.txt 的范围内。这一点都不明显,但来自set_source_files_properties 的文档:

    源文件属性仅对添加到同一目录 (CMakeLists.txt) 中的目标可见。

    add_cppcheck 的内部代码如下:

    foreach(_source ${_cppcheck_sources})
      get_source_file_property(_cppcheck_lang "${_source}" LANGUAGE)
      get_source_file_property(_cppcheck_loc "${_source}" LOCATION)
      if("${_cppcheck_lang}" MATCHES "CXX")
        list(APPEND _files "${_cppcheck_loc}")
      endif()
    endforeach()
    

    所以这是在将给定目标的每个源文件添加到要提供给 cppcheck 的文件列表之前检查是否将其指定为 C++ 文件。如果从定义目标的 CMakeLists.txt(即子目录)中调用此函数,则所有文件都具有适当的属性并被正确添加。

    但是,如果从父级 CMakeLists.txt 调用该函数,则文件会丢失其属性,因此不会添加任何文件,并且 cppcheck 会传递一个空列表!


    现在进行可能的修复。可能有几种方法可以摆脱这个漏洞 - 我可以指出几个。

    您可以继续选择始终从顶级 CMake 文件调用 add_cppcheck 并避免使用源文件的属性。所以上面的问题代码块可以改成类似:

    set(CxxExtensions .cpp .CPP .cc .CC .cxx .CXX)
    foreach(_source ${_cppcheck_sources})
      get_filename_component(Extension "${_source}" EXT)
      list(FIND CxxExtensions "${Extension}" IsCxxFile)
      if(IsCxxFile GREATER -1)
        list(APPEND _files "${_source}")
      endif()
    endforeach()
    

    您甚至可以通过在函数开头添加类似这样的内容来强制仅从顶级 CMakeLists.txt 调用该函数:

    if(NOT "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
      message(FATAL_ERROR "This can only be called from the top-level CMakeLists.txt")
    endif()
    


    第二个修复(我个人喜欢)是将 add_cppcheck 调用留在子目录中,并让函数添加自定义 target 而不是命令。这些目标可以成功地应用为顶级目标ANALYZE_CODE 的依赖项。例如,将add_custom_command 更改为:

    add_custom_target(ANALYZE_${_name}
            ${CPPCHECK_EXECUTABLE}
            ${CPPCHECK_QUIET_ARG}
            ${CPPCHECK_TEMPLATE_ARG}
            ${_cppcheck_args}
            ${_files}
            WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
            COMMENT "ANALYZE_${_name}: Running cppcheck on target ${_name}..."
            VERBATIM)
    add_dependencies(ANALYZE_CODE ANALYZE_${_name})
    set_target_properties(ANALYZE_${_name} PROPERTIES FOLDER "Code Analysis")
    

    这应该会导致构建ANALYZE_CODE 触发构建每个从属ANALYZE_... 目标。

    它有很多额外目标“污染”解决方案的缺点,但好处是您可以add_test 调用中使用这些目标(尽管这可能是一个步骤太远了):

    # CMake 2.8.0 and newer
    add_test(NAME ${_name}_cppcheck_test
             COMMAND ${CMAKE_COMMAND}
                 --build ${CMAKE_BINARY_DIR}
                 --target ANALYZE_${_name})
    

    【讨论】:

    • 我想我可能不清楚我真正想要发生的事情并更新了我的原始帖子。但我打算只有一个目标,但在构建该目标时会运行多个自定义命令。
    • 啊-好的。我会删除这个。
    • @Max 我已经稍微重构了我的答案:-)
    • 哇,感谢您的精心回复!我永远不会想到 add_custom_command 有这些范围问题。我现在采用了您创建自定义子目标的解决方案。不过,这对 IDE 解决方案造成了相当大的污染,因此另一种解决方案可能会更好。您是否认为可以仅将所有子项目的文件添加到列表中,然后创建一个分析目标来针对所有文件运行?我看到的最大问题是分析目标必须最后创建,这很难保证。您认为这个想法还有其他问题吗?
    • @Max 我能看到的唯一可能是问题的另一件事是WORKING_DIRECTORY 将是所有文件的根源目录,但我不知道这是否真的是 cppcheck 的问题或不。如果您主要关心的是污染 IDE,您可以通过为所有自定义目标(包括顶级ANALYZE_CODE)提供一个通用FOLDER property 来稍微整理一下。我将添加一行来显示这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 2018-10-02
    相关资源
    最近更新 更多