16

It's often necessary to ensure that CMake build projects end up in a certain place after compilation, and the add_custom_command(..POST_BUILD...) command is a common design pattern to accomplish that:

add_custom_command(
  TARGET mytarget
  POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mytarget> ${CMAKE_BINARY_DIR}/final_destination
)

Sadly it does not work when the target in question is in a subdirectory relative to the file containing the add_custom_command call, which was compiled recursively via the add_subdirectory() command. Attempting to do so leads to the following error message:

CMake Warning (dev) at CMakeLists.txt:4 (add_custom_command):
  Policy CMP0040 is not set: The target in the TARGET signature of
  add_custom_command() must exist.  Run "cmake --help-policy CMP0040" for
  policy details.  Use the cmake_policy command to set the policy and
  suppress this warning.

  TARGET 'mytarget' was not created in this directory.
This warning is for project developers.  Use -Wno-dev to suppress it.

In many cases, there is a straightforward workaround: simply ensure that the add_custom_command() call occurs in the subdirectory's CMakeLists.txt file, and everything will work.

However, this is not always possible! The subdirectory could be the CMake project of an external dependency over which we have no control. For instance, it's fairly common to combine CMake recursive compilations with Git submodules, in which case there is no way to permanently store modifications of the child projects's build system.

My question then boils down to the following: Does CMake offer another mechanism to create a target that will be automatically triggered when a child project's target is rebuilt, and which can be used to copy the final executable or shared library to some other location?

My goal is that this happens automatically without needing to specifically call 'make'/'ninja' with another target. Also, the copy should only be executed when it is actually necessary (according to the cmake docs, some of the add_custom_* commands don't track whether they actually need to be run and conservatively assume that the target is always stale).

Wenzel Jakob
  • 555
  • 6
  • 14
  • Are all the sub-directories `CMakeLists.txt` files of the same/your project? And do we talk about all targets of e.g. executable type or do you want to apply this to specific targets only? In my project I've made my own `function()` for all standard CMake calls, so I can modify the standard behavior of e.g. `add_executable()` for my project (like automatically adding a post-build step to all such calls). – Florian Oct 28 '16 at 06:58
  • The subdirectories will generally be shared libraries distributed as git submodules cloned from github (e.g. ``pugixml``). These projects have their own CMake projects that I can't (and don't want to) change. The parent project could generate a mixture of executable or shared libraries -- I don't thin it particularly matters for this question. – Wenzel Jakob Oct 28 '16 at 10:24

1 Answers1

11

Just use common combination of add_custom_command and add_custom_target, when the first one produces file for the second one:

# Because OUTPUT option may not use generator expressions,
# extract name of file from target's properties.
get_target_property(mytarget_basename mytarget OUTPUT_NAME)
get_target_property(mytarget_suffix mytarget SUFFIX)
set(mytarget_filename ${mytarget_basename}${mytarget_suffix})
# make copied file be dependent from one which is build.
# Note, that DEPENDS here creates dependencies both from the target
# and from the file it creates.
add_custom_command(OUTPUT
        ${CMAKE_BINARY_DIR}/final_destination/${mytarget_filename}
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mytarget>
        ${CMAKE_BINARY_DIR}/final_destination
    DEPENDS mytarget
)
# Create target which consume the command via DEPENDS.
add_custom_target(copy_files ALL
    DEPENDS ${CMAKE_BINARY_DIR}/final_destination/${mytarget_filename}
)

Comparing with POST_BUILD using, this code uses additional target. But you have no other choice: add_custom_command cannot be attached to the target created in other directory.


Usually, instead of copiing executable/library into other binary directory it is simpler to specify this directory via CMAKE_<TYPE>_OUTPUT_DIRECTORY variable.

OznOg
  • 4,440
  • 2
  • 26
  • 35
Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • This doesn't quite fit the bill: my goal is that the copy is run by default even when not specifying a target to "make" or "ninja". Also, the copy should only be executed when it is actually necessary (according to the cmake docs, some of the add_custom_* commands don't track whether they actually need to be run and conservatively assume that the target is always stale). – Wenzel Jakob Oct 27 '16 at 22:53
  • 1
    `my goal is that the copy is run by default` - After adding *ALL* option, the target will be built default. I have fixed the answer. `copy should only be executed when it is actually necessary` - This is what `add_custom_command` does. It is *target* (added with `add_custom_target`) which is assumed to be always stale. But since it doesn't contain *COMMAND* option, nothing is done by it. – Tsyvarev Oct 28 '16 at 06:55
  • does this assume you call set_target_properties() in your original target? seems like this works if you give ${mytarget_filename} any value – sean ng pack Sep 11 '20 at 15:32
  • "does this assume you call set_target_properties() in your original target?" - No, there is no such assumption. CMake fills `OUTPUT_NAME` and `SUFFIX` properties automatically. "seems like this works if you give ${mytarget_filename} any value" - With correct value specified for `mytarget_filename` variable, `COMMAND` won't be executed if destination file exists and original executable isn't changed. With incorrect value for `mytarget_filename` variable COMMAND will be executed **every time**, the same will be achieved by specifying COMMAND directly in `add_custom_target`. – Tsyvarev Sep 11 '20 at 15:50