The CMake logo, licensed under CC BY 2.0

CMake, custom targets, and commands

Notes published the
5 - 6 minutes to read, 1178 words

CMake offers both add_custom_command and add_custom_target for executing user-defined actions, but from the documentation, it is not always clear how to use them, and when one is more appropriate than another.

TLDR

  • use add_custom_command(TARGET if action depends on a target (and output probably not used by someone else)

  • use add_custom_command(OUTPUT if the output needs to be consumed by someone else

  • add_custom_target(foo is always out-of-date. No one should depend on such targets. It could be used for automating some user-defined actions (like executing Cppcheck or test suite, copying some files that are needed sometimes…​), or providing a nicer API for the end-user.

Minimal example with add_custom_command

cmake_minimum_required(VERSION 3.10)
project(foo)

add_executable(foo main.cpp)

add_custom_command(TARGET foo
    PRE_BUILD
    COMMAND ${CMAKE_COMMAND} -E echo hello
    COMMENT "This command will be executed before building foo"
    VERBATIM
    # no support for depends
    # no support for output
)

# never executed, unless someone depends on OUTPUT
add_custom_command(
    OUTPUT ${CMAKE_BINARY_DIR}/file.txt
    COMMAND ${CMAKE_COMMAND} -E copy_if_different file.txt ${CMAKE_BINARY_DIR}/
    DEPENDS file.txt
    COMMENT "This command will never be executed unless someone depends on output"
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # instead of using absolute paths
)

In this example, add_executable is used for creating an executable. add_custom_command(TARGET foo is for bundling the execution of an external command together with the target foo, in this case, it’s a simple output to the console. Notice that this signature of add_custom_command does not support DEPENDS and OUTPUT, thus its dependencies must be at least those of foo, and it cannot be used for creating other targets that depend on it.

On the other hand add_custom_command(OUTPUT supports both DEPENDS and OUTPUT. Thus it will not be executed if no one depends on it (even when executing the target all), and it can be used for creating dependent targets.

Minimal example with add_custom_target

cmake_minimum_required(VERSION 3.10)
project(foo)

add_executable(foo main.cpp)

add_custom_target(bar
    COMMAND ${CMAKE_COMMAND} -E echo hello
    VERBATIM
    # no support for output
)

Similar to add_custom_command(TARGET, it does not support OUTPUT, but it supports DEPENDS. Thus add_custom_target is generally not a good choice if another target needs to depend on it, but it can depend on other targets and commands without issues.

This is probably the main reason why someone should avoid add_custom_target for building code. Thus add_custom_target is always executed when invoked (even if no file changed), and all targets that depend on it too!

This makes it unsuitable, for example, for a code generator, as other targets depending on it would always need to be rebuilt.

Some use-cases

There is not much more left to explain, the TLDR sums it up nicely

  • use add_custom_command(TARGET if action depends on a target (and output probably not used by someone else)

  • use add_custom_command(OUTPUT if the output needs to be consumed by someone else

  • add_custom_target(foo is always out-of-date. No one should depend on such targets. It could be used for automating some user-defined actions (like executing Cppcheck or test suite, copying some files that are needed sometimes…​)

I tried to come up with some use cases to show what is better to use in which situation.

It seems that for most tasks add_custom_command(OUTPUT is the right choice ™. add_custom_command(TARGET has similar use cases, but makes it impossible to reuse the output of the custom command (if there is any).

add_custom_target can be used for "user-defined" operations, that might depend on external resources or are (or should be not) not reproducible for other reasons.

Code generator with add_custom_command

This is the main use-case I’ve used add_custom_command so far:

cmake_minimum_required(VERSION 3.10)
project(foo)

add_executable(foo main.cpp)
target_link_libraries(foo bar)

set(GEN_SRC
    ${CMAKE_BINARY_DIR}/bar.hpp
    ${CMAKE_BINARY_DIR}/bar.cpp
)
add_custom_command(
    OUTPUT ${GEN_SRC}
    COMMAND ${CMAKE_COMMAND} -E copy bar.in.hpp ${CMAKE_BINARY_DIR}/bar.hpp
    COMMAND ${CMAKE_COMMAND} -E copy bar.in.cpp ${CMAKE_BINARY_DIR}/bar.cpp
    DEPENDS bar.in.hpp bar.in.cpp
    COMMENT "Generate source code"
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # instead of using absolute file paths
)
add_library(bar ${GEN_SRC})

While it is possible to mix the generated and hand-written source code, I prefer keeping them as separate targets. It is generally easier to disable compiler warnings for a whole library, as generally there is no interest in fixing those for generated code (it is better to improve the code generator), similarly to external libraries.

Manually execute static analysis

CMake added support for cppcheck, clang-tidy, and possibly other static analyzers. Nevertheless, there could be some hand-written static analyzers that do a very specific verification.

While add_custom_target seems the right choice, as there might be no output, doing the analysis a second time if no file changes is just a waste of time. It might be better to save/redirect the output to a file.

if(MSBUILD)
    set(PROJMAP ${CMAKE_BINARY_DIR}/${PROJ}.sln) # or create compile_commands.json with something else
else()
    set(PROJMAP ${CMAKE_BINARY_DIR}/compile_commands.json)
endif()
add_custom_command(
    OUTPUT ${CMAKE_BINARY_DIR}/cppcheck.xml
    COMMAND ${CPPCHECK} --inconclusive --xml --project=${PROJMAP} --enable=all --output-file ${CMAKE_BINARY_DIR}/cppcheck.xml
    COMMENT "Generate source code"
)

For the end-user, it is possible to use add_custom_target to provide an easier name to invoke

add_custom_target(check-with-cppcheck
    DEPENDS ${CMAKE_BINARY_DIR}/cppcheck.xml
)

as check-with-cppcheck does nothing, and nothing should depend on it (as other targets should depend on ${CMAKE_BINARY_DIR}/cppcheck.xml), it is not an issue if it is always out-of-date.

For example, another user-defined action that could depend on ${CMAKE_BINARY_DIR}/cppcheck.xml, is opening the output with the appropriate program

add_custom_target(check-with-cppcheckgui
    ${CPPCHECK_GUI} ${CMAKE_BINARY_DIR}/cppcheck.xml
    DEPENDS ${CMAKE_BINARY_DIR}/cppcheck.xml
)

In this case, check-with-cppcheckgui does something (executing an external program), but it is fine, as the program is used interactively by the user.

User-defined actions with add_custom_target

While I’ve used make to partially automate tasks, like creating an appropriate environment, downloading files, executing tests, opening documents, etc. etc. I’ve never used CMake until now for such tasks.

A possible example would be opening the browser to the generated or official documentation, thus something like

if(BROWSER)
    add_custom_target(open-gen-documentation
        ${BROWSER} path/to/gen-doxygen/index.html
        COMMENT "open documentation with firefox"
    )
    add_custom_target(open-spec
        ${BROWSER} path/or/url/to/specification
        COMMENT "open specification with firefox"
    )
endif()

while it might be a nice idea, this can increase the configuration time, unless the browser is already used for something else, as CMake needs to find it while configuring the project.


Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me anytime.