CMake, custom targets, and commands
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.