CMake中add_custom_command无法并行执行的原因分析
为什么Makefile生成器会串行执行无依赖的自定义目标?
问题场景
我编写了如下CMake文件:
cmake_minimum_required(VERSION 3.20) project(foo) set(srcs "foo.txt") add_custom_command( DEPENDS ${srcs} VERBATIM OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/foo1.txt" COMMENT "Copying to foo1.txt" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND sleep 5 COMMAND cp "foo.txt" "foo1.txt" ) add_custom_target(gen_foo1 DEPENDS "foo1.txt") add_custom_command( DEPENDS ${srcs} VERBATIM OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/foo2.txt" COMMENT "Copying to foo2.txt" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND sleep 5 COMMAND cp "foo.txt" "foo2.txt" ) add_custom_target(gen_foo2 DEPENDS "foo2.txt")
执行构建步骤:
> mkdir build > cmake .. -- Configuring done (0.0s) -- Generating done (0.0s) -- Build files have been written to: ..../build > time make -j2 gen_foo1 gen_foo2 [100%] Copying to foo1.txt [100%] Built target gen_foo1 [100%] Copying to foo2.txt [100%] Built target gen_foo2 real 0m10.088s user 0m0.030s sys 0m0.043s
两个自定义目标无依赖关系,但Makefile生成器下串行运行耗时约10秒,而Ninja构建仅需5秒,请问原因是什么?
核心原因:Make对源目录输出的隐含目录依赖
当自定义命令将输出文件生成到源代码目录(CMAKE_CURRENT_SOURCE_DIR)时,Makefile生成器会触发Make本身的隐含机制:
- Make会跟踪目录的修改状态,任何修改目录内容的目标都会被标记为更新了目录的时间戳。
- 其他需要修改同一目录的目标会被Make判定为依赖于该目录的最新状态,因此必须等待前一个目标执行完成后才能启动,最终导致串行执行。
而Ninja生成器的依赖跟踪逻辑更精准,它只会处理CMake明确声明的文件依赖,不会为目录添加这种跨目标的隐含依赖,因此可以并行执行两个独立任务。
解决办法
最直接且符合CMake最佳实践的方式是将输出文件放到构建目录,避免修改源代码目录:
修改两个add_custom_command的OUTPUT和cp命令路径:
# 第一个自定义命令 OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/foo1.txt" # ... COMMAND cp "foo.txt" "${CMAKE_CURRENT_BINARY_DIR}/foo1.txt" # 第二个自定义命令 OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/foo2.txt" # ... COMMAND cp "foo.txt" "${CMAKE_CURRENT_BINARY_DIR}/foo2.txt"
如果确实需要在源代码目录生成文件,可以尝试为自定义命令添加BYPRODUCTS属性,或者手动规避Make的目录依赖跟踪,但这种方式不推荐,容易引入其他构建问题。
内容的提问来源于stack exchange,提问作者Timmmm
相关产品推荐
相关产品推荐

