cmake的add_custom_command如何处理多输出+多依赖
intro
在一个复杂的项目中,免不了需要动态生成文件,此时可能就需要用到cmake的add_custom_command命令,这个命令可以生成cmake识别的输出文件,并作为构建过程中其它命令的依赖和输出。
add_custom_command(OUTPUT output1 [output2 ...]
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[MAIN_DEPENDENCY depend]
[DEPENDS [depends...]]
[IMPLICIT_DEPENDSdepend1
[depend2] ...]
[WORKING_DIRECTORY dir]
[COMMENT comment] [VERBATIM] [APPEND])
这个命令里有有一个很好用的功能:可以同时有多个个输(OUTPUT)出和多个依赖(DEPENDS),并且公用的是同一个命令。
这个功能翻译成Makefile看起来应该也很直观:
output1 output2: depend1 depend1
command1
command2
但是这里有一个隐藏的问题:make是支持多任务并发构建的
-j [jobs], --jobs[=jobs] Specifies the number of jobs (commands) to run simultaneously. If there is more than one -j option, the last one is effective. If the -j option is given without an argument, make will not limit the number of jobs t can run simultaneously.
在多任务构建时,假设output1和output2都需要重新生成,那么相同的命令会被多次执行。多次执行的冗余可能还是次要的,关键是这些命令可能都会修改相同的文件,很容易因为并发写而导致输出文件错乱。
cmake的处理
下面是一个简单的测试脚本:
cmake_minimum_required (VERSION 3.10)
project (tsecer)
add_executable(main main.cpp sub.cpp)
add_custom_command(OUTPUT main.cpp sub.cpp
COMMAND touch main.cpp touch sub.cpp
DEPENDS main.h sub.h)
依赖关系在Makefile中的表示为
main.cpp: main.hmain.cpp: sub.h
@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --blue --bold --progress-dir=/home/tsecer/cmake/add_custom_command_multiple_output/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Generating main.cpp, sub.cpp" touch main.cpp touch sub.cpp
sub.cpp: main.cpp
@$(CMAKE_COMMAND) -E touch_nocreate sub.cpp
其中比较巧妙的地方在于sub.cpp并不是和main.cpp有相同的依赖,而是sub.cpp会依赖于main.cpp,而main.cpp依赖cmake脚本描述的所有依赖。并且sub.cpp的脚本命令不会真正执行构建动作。而是假设main.cpp和sub.cpp有相同的时间戳,让main.cpp来替换sub.cpp的依赖。
由于make在多任务调度时会考虑这种同一个目标的依赖关系,所以避免了多任务执行时的并发写问题。
而其中的一个关键是cmake添加的,看似平平无奇的touch_nocreate命令
touch_nocreate <file>... Touch a file if it exists but do not create it. If a file does not exist it will be silently ignored.
outro
个人认为cmake使用简单的方法,优雅的解决了构建并发问题,是一个巧妙的思路。