Let us have a closer look at the invocation of add_custom_command:
add_custom_command(
OUTPUT
${wrap_BLAS_LAPACK_sources}
COMMAND
${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
COMMAND
${CMAKE_COMMAND} -E touch ${wrap_BLAS_LAPACK_sources}
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
COMMENT
"Unpacking C++ wrappers for BLAS/LAPACK"
VERBATIM
)
add_custom_command adds rules to targets so that they know how to generate the output by executing the commands. Any target declared within the same directory of add_custom_command, that is, in the same CMakeLists.txt, and that uses any file in the output as its source file, will be given a rule to generate those files at build time. Dependencies between targets and custom commands are thus automatically handled at build system generation, while the actual generation of source files happens at build time.
In our specific case, the outputs are the sources contained in the zipped tar archive. To retrieve and use those files, the archive will have to be extracted at build time. This is achieved by using the CMake command itself with the -E flag, to achieve platform independence. The next command updates the timestamps of the extracted files. We do this to make sure we are not dealing with stale source files. WORKING_DIRECTORY specifies where to execute the commands. In our case, this is CMAKE_CURRENT_BINARY_DIR, which is the build directory currently being processed. The argument to the DEPENDS keyword lists dependencies to the custom command. In our case, the zipped tar archive is a dependency. The COMMENT field will be used by CMake to print status messages at build time. Finally, VERBATIM tells CMake to generate the right command for the specific generator and platform, thus ensuring full platform independence.
Let us also have a closer look at the way the library with the wrappers is created:
add_library(math "")
target_sources(math
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.cpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.cpp
PUBLIC
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.hpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.hpp
)
target_include_directories(math
INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK
)
target_link_libraries(math
PUBLIC
${LAPACK_LIBRARIES}
)
We declare a library target with no sources. This is because we then use target_sources to populate the sources of the target. This achieves the very important task of letting dependents on this target know what include directories and header files they need, in order to successfully use the library. The C++ source files are PRIVATE to the target, and hence only used in building the library. The header files are PUBLIC because both the target and its dependents will need to use them to successfully compile. The include directories are specified using target_include_directories with wrap_BLAS_LAPACK declared as INTERFACE, since only dependents on the math target will need it.
This form of the add_custom_command has two limitations:
- It will only be valid if all of the targets depending on its output are specified in the same CMakeLists.txt.
- Using the same output as add_custom_command for different, independent targets might re-execute the custom commands rule. This may cause conflicts and should be avoided.
The second limitation can be avoided by carefully introducing dependencies with add_dependencies, but the proper approach to circumvent both would be to use the add_custom_target command, as we will detail in the next recipe.