Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative to CMake POST_BUILD command when target is in subdirectory

Tags:

cmake

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

like image 675
Wenzel Jakob Avatar asked Oct 27 '16 21:10

Wenzel Jakob


1 Answers

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.

like image 181
Tsyvarev Avatar answered Nov 16 '22 09:11

Tsyvarev