Wednesday, March 25, 2015

CMake: Always running the post-build step

As the name suggests, the POST_BUILD option in CMake's add_custom_command() runs after building is complete and as a consequence, if the project target does not need to be re-built (as it has no changes), the post-build step will not be run. However, in some projects you may want to always run the post-build step; for example if you need to copy module libraries (i.e. which do not depend on the main project target but have been modified) to the main target's output directory so the application can run. Luckily there is a simple workaround to achieve this:

1. Add an empty header file (.h) to your project with the following name: always_do_post_build.h
2. Ensure that this file is monitored by CMake by including it in add_executable():
add_executable(${CMAKE_PROJECT_NAME}
  ${SOURCE_FILES}
  ${HEADER_FILES}
  always_do_post_build.h
)
3. Also add the following to your CMakeLists.txt file:
add_custom_target(ALWAYS_DO_POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E echo touching always_do_post_build.h
  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_SOURCE_DIR}/always_do_post_build.h
)
add_dependencies(${CMAKE_PROJECT_NAME} ALWAYS_DO_POST_BUILD)

How does it work?

An extra dummy target (ALWAYS_DO_POST_BUILD) is created which depends on the main project target (CMAKE_PROJECT_NAME). Due to this dependency, the dummy target will run before the main project target and ensures that the always_do_post_build.h file (which is monitored by the main project target) is always "out of date" via the touch command. This will trigger the main project target to perform a re-build and thus run the post-build step as required. NOTE: A header file (.h) is used so that no code is compiled in the process.

Update (August 16, 2015)

An alternative to the above is to simply add the following to each module library's CMakeLists.txt file:

# Post-Build Step: Create required directory
add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E make_directory
  $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/path/to/where/module/should/be
  COMMENT "Creating directory: " $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/path/to/where/module/should/be
)
# Post-Build Step: Copy library DLL to runtime directory
add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy_if_different
  $<TARGET_FILE:${LIBRARY_NAME}>
  $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/path/to/where/module/should/be
  COMMENT "Copying file to Runtime directory: " $<TARGET_FILE:${LIBRARY_NAME}>
)