add_custom_command dependencies are unexpectedly invoked multiple times with msbuild
Problem description
In our application we have a documentation generation target to produce HTML, PDF and other files. This documentation depends on some fixed source files and some auto-generated stuff from add_custom_command(OUTPUT ...)
. In order to ensure that these files are rebuilt as needed, the auto-generated files are added as dependency to the generation target (see scenario A below).
That has one problem, the output might be generated multiple times. To solve that, add another synchronization target (using add_custom_target
as described in section 4 of this post. This works great with the Makefile and Ninja generators, but fails terribly with msbuild (e.g. "Visual Studio 12"): the auto-generation commands are executed multiple times. It seems that the Visual Studio generator did not notice that the output is up-to-date and regenerates the output (rather treating it as a "create me if out-of-date" which should happen due to the synchronization target).
In order to solve the dependency issue, let's try to remove the original dependency since the output does not need that (scenario C). This indeed fixes the multiple generation issue, but whenever the files are auto-generated again, the documentation targets are not considered out-of-date.
Steps to reproduce
- Create CMakeListst.txt as described below.
- Linux (Ninja):
mkdir b && cd b && cmake -G Ninja .. && ninja -v docs && sleep 1 && echo | tee [abc].css && ninja -v docs
- Linux (Unix Makefiles):
mkdir ../m && cd ../m && cmake .. && make docs V=1 && tee [abc].css<<<x && sleep 1 && echo | tee [abc].css && make docs V=1
- Windows:
mkdir win; cd win; cmake ..; msbuild /maxcpucount docs.vcxproj; echo 1 > a.css; echo 1 > b.css; echo 3 > b.css; msbuild /maxcpucount docs.vcxproj
Expected results
In the first make/ninja/msbuild invocation, a.css, b.css and c.css should be created exactly once. In the second invocation (after marking the css files out-of-date), the "a" and "b" HTML files should be updated.
Actual results
Ninja and Makefile on Linux works as expected. msbuild on Windows creates a.css and b.css multiple times for the first invocation. On the second invocation, the "a" and "b" HTML files are updated as expected though.
Other information
Linux tests were done on Arch Linux with CMake 3.7.2, Ninja 1.7.2 and 4/8 cores/threads.
Windows tests were done on Windows 7 with CMake 3.1.3, Visual Studio 2013 Community and 2 cores (virtualized).
CMakeLists.txt
Synthethic CMakeLists.txt
that models the documentation generation target:
cmake_minimum_required(VERSION 3.1.3)
project(docu LANGUAGES)
# Scenario A: depend on generated object (so it is built when outdated/missing).
# Problem: command might be executed multiple times.
add_custom_command(OUTPUT a.css COMMAND "${CMAKE_COMMAND}" -E touch a.css)
add_custom_command(OUTPUT a1.html COMMAND "${CMAKE_COMMAND}" -E touch a1.html DEPENDS a.css)
add_custom_command(OUTPUT a2.html COMMAND "${CMAKE_COMMAND}" -E touch a2.html DEPENDS a.css)
add_custom_target(generate-a1 DEPENDS a1.html)
add_custom_target(generate-a2 DEPENDS a2.html)
add_custom_target(generate-a DEPENDS generate-a1 generate-a2)
# Scenario B: depend on generator target (for synchronization) and generated
# object (for rebuilding when outdated).
add_custom_command(OUTPUT b.css COMMAND "${CMAKE_COMMAND}" -E touch b.css)
add_custom_target(generate-b.css DEPENDS b.css)
add_custom_command(OUTPUT b1.html COMMAND "${CMAKE_COMMAND}" -E touch b1.html DEPENDS generate-b.css b.css)
add_custom_command(OUTPUT b2.html COMMAND "${CMAKE_COMMAND}" -E touch b2.html DEPENDS generate-b.css b.css)
add_custom_target(generate-b1 DEPENDS b1.html)
add_custom_target(generate-b2 DEPENDS b2.html)
add_custom_target(generate-b DEPENDS generate-b1 generate-b2)
# Scenario C: depend on target (no direct dependency on generated output).
# Problem: no rebuilds when the dependency is out-of-date.
add_custom_command(OUTPUT c.css COMMAND "${CMAKE_COMMAND}" -E touch c.css)
add_custom_target(generate-c.css DEPENDS c.css)
add_custom_command(OUTPUT c1.html COMMAND "${CMAKE_COMMAND}" -E touch c1.html DEPENDS generate-c.css)
add_custom_command(OUTPUT c2.html COMMAND "${CMAKE_COMMAND}" -E touch c2.html DEPENDS generate-c.css)
add_custom_target(generate-c1 DEPENDS c1.html)
add_custom_target(generate-c2 DEPENDS c2.html)
add_custom_target(generate-c DEPENDS generate-c1 generate-c2)
add_custom_target(docs DEPENDS generate-a generate-b generate-c)