Question

I'm running into a problem with add_custom_command for a custom target (created using add_custom_target).

My general idea is to incorporate a static code analysis tool into the cmake tool-chain. My solution is based on the one described here: https://github.com/rpavlik/cmake-modules/blob/master/CppcheckTargets.cmake

In a nutshell, each project that I want to run static code analysis for has the following two lines of code:

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

The module has this at the top of the file:

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 ()

and later on in the function the custom command is added:

 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)

The problem I'm seeing is that the command is only added for the project that included the file first. I'm not sure why and what is going on. I verified the following using message() commands:

  • The target is only created once
  • The add_custom_command is run for every project that calls the function, with proper arguments

But when I actually look at the target in visual studio, only the first include / function call command is added.

If the file is only included without calling the function, no custom commands are added at all.

Desired behavior:

I would like ONE target named "ANALYZE_CODE" to run all commands added by calls to the function.

I.e. if 3 projects include the two lines from above, the target ANALYZE_CODE is created once but 3 custom commands are added to it, one for each project.

Was it helpful?

Solution

It turns out you're somewhat stuck between a rock and a hard place here. The issue I think boils down to a couple of factors.

Firstly, although the docs don't make it clear, add_custom_command(TARGET ...) only works for targets created in the same directory. So the first subproject to call include(cppcheck) is the only one which can effectively add custom commands to the target ANALYZE_CODE.

A workaround for this might seem to be to move all calls to add_cppcheck from their respective subdirectories up to the top-level CMakeLists file.

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

This isn't a great solution, since these really belong inside their own subdirs. But a bigger issue is that properties on source files only persist in the scope of the CMakeLists.txt in which they are added. This isn't obvious at all, but from the docs for set_source_files_properties:

Source file properties are visible only to targets added in the same directory (CMakeLists.txt).

The innards of the add_cppcheck have the following block of code:

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()

So this is checking that each source file for the given target is designated as a C++ file before adding it to the list of files to be given to cppcheck. If this function is invoked from within the CMakeLists.txt where the target is defined (i.e. the subdir) then the files all have the appropriate property and are correctly added.

However, if the function is called from the parent CMakeLists.txt, the files have lost their properties, and so none are added and cppcheck is passed an empty list!


Now for the possible fixes. There are probably few ways to get out of this hole - I can point to a couple.

You could continue with the option to always call add_cppcheck from the top-level CMake file and avoid using the source files' properties. So the problem codeblock above could be changed to something more like:

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()

You could even enforce that the function is only called from the top-level CMakeLists.txt by adding something like this at the start of the function:

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()


The second fix (which I'd personally favour) is to leave the add_cppcheck calls inside the subdirs and have the function add a custom target rather than command. These targets can successfully be applied as dependencies of the top-level target ANALYZE_CODE. So for example, change the add_custom_command to something like:

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")

This should cause building ANALYZE_CODE to trigger building each of the subordinate ANALYZE_... targets.

It has the downside of "polluting" the solution with a lot of extra targets, but an upside is that you could use these targets in the add_test calls (although this may be a step too far):

# CMake 2.8.0 and newer
add_test(NAME ${_name}_cppcheck_test
         COMMAND ${CMAKE_COMMAND}
             --build ${CMAKE_BINARY_DIR}
             --target ANALYZE_${_name})
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top