VS 2017: Custom commands don't cascade correctly

I have a custom target which produces an end result using a two-step process similar to compilation & linking. My problem is that as soon as more than one source file is involved, updates do not cascade correctly in one build.

Here’s a minimal CMakeList to reproduce the issue:

cmake_minimum_required(VERSION 3.14)
project(WhyTwoStep)

set(ins "")
set(outs "")
foreach(source IN ITEMS a.in b.in)
  set(from ${CMAKE_CURRENT_SOURCE_DIR}/${source})
  list(APPEND ins ${from})
  set(to ${CMAKE_CURRENT_BINARY_DIR}/${source}.out)
  list(APPEND outs ${to})
  add_custom_command(
    OUTPUT ${to}
    COMMAND ${CMAKE_COMMAND} -E copy ${from} ${to}
    DEPENDS ${from}
    COMMENT "Pseudo-compilation of ${source}"
    VERBATIM
  )
endforeach()

set(final ${CMAKE_CURRENT_BINARY_DIR}/final.txt)
add_custom_command(
  OUTPUT ${final}
  COMMAND ${CMAKE_COMMAND} -E touch ${final}
  DEPENDS ${outs}
  COMMENT "Pseudo-linking"
  VERBATIM
)

add_custom_target(
  Finalise ALL
  DEPENDS ${final}
  SOURCES ${ins}
  COMMENT "Checking..."
)

Generate for VS 2017. Build Solution, everything runs fine; the output I get is as expected:

2>------ Build started: Project: Finalise, Configuration: Debug Win32 ------
2>Pseudo-compilation of a.in
2>Pseudo-compilation of b.in
2>Building Custom Rule H:/REDACTED/src/CMakeLists.txt
2>Pseudo-linking
2>Checking...

Then, Build solution again, nothing happens (only get the expected Checking... output).

Then, modify a.in. Build solution. The output I get is:

1>------ Build started: Project: Finalise, Configuration: Debug Win32 ------
1>Pseudo-compilation of a.in
1>Checking...

I would expect Pseudo-linking to happen too, but it doesn’t. Only if I Build solution again do I get this:

1>------ Build started: Project: Finalise, Configuration: Debug Win32 ------
1>Pseudo-linking
1>Checking...

So it takes two builds of the solution to get the final file correctly up to date. Is this expected, am I doing something wrong, or should I file a bug report?

Also note that curiously, if b.in is taken out of the equation and thus final.txt only depends on a.in.out, both Pseudo-compilation and Pseudo-linking happen during the first Build after modification (as I would expect to be the correct case).

(I’ve tested this with CMake 3.14.4 as well as 3.18.3, the behaviour is identical in both).

I think this might be a VS bug. I tried it with VS 2015 and I see what you see. I tried with 2019 and it works as it should. It also works fine with Ninja.

I can reproduce this with VS 2015 and VS 2017, but not VS 2019. The content CMake generates in the .vcxproj file looks correct. Somehow MSBuild is not recognizing when a.in.out is regenerated such that final.txt should be rebuilt.

I’m quite surprised this has never been noticed before, but I don’t know if CMake can do anything about it. Fortunately VS 2019 works.

I suspect I may have encountered it already, but in a context so complex that I didn’t have the time & energy to untangle it and see why it’s happening. It’s only now that I’ve introduced a much more localised case that I was able to notice it, pinpoint it, and distill it into the example above.

Thanks a lot for looking into it, I don’t think it would have occured to me to try different VS versions (and I don’t have handy access to VS2019 anyway). I will work around it in our codebase, with a note on how to streamline it once we move to VS2019.