Running cmake--build command with multiple targets supplied to the --target option only uses the return value of the last target that ran
When cmake is used to build a project via the --build option and is supplied multiple targets via the --targets switch, it creates command to pass to the native build system for each target and runs each of those commands regardless of whether previous commands failed. Furthermore the return code of the cmake build command is always the return code of the final native build command that ran. It doesn't take into account the return values of previous build targets This can result in scenarios where the cmake --build command returns true when building multiple targets when one of earlier build targets has failed. This causes issues for example such as attempting to use cmake on automated build system that runs tests via multiple custom targets. A minimum repro for this issue is below. It involves just having a single CMakeLists.txt with multiple targets, followed by invoking the cmake --build command with those multiple targets CMakeLists.txt --- ``` cmake_minimum_required(VERSION 3.0) project(BuildCommandFailRepro) add_custom_target(ReturnsFalse COMMAND ${CMAKE_COMMAND} -E false) add_custom_target(ReturnsTrue COMMAND ${CMAKE_COMMAND} -E true) ``` Running cmake --build against the targets two targets in the order of - ReturnsFalse - ReturnsTrue Results in a return code of 0 for the cmake --build command Invalid zero return code --- ``` cmake . cmake --build . --target ReturnsFalse ReturnsTrue echo %errorlevel% ``` Swapping the order of the targets results in a return code 1 for the cmake --build command Valid zero return code --- ``` cmake . cmake --build . --target ReturnsTrue ReturnsFalse echo %errorlevel% ``` I have tracked the issue down to the call of [cmSystemTools::RunSingleCommand](https://gitlab.kitware.com/cmake/cmake/-/blob/v3.17.3/Source/cmSystemTools.cxx#L625-634) from the [cmGlobalGenerator::Build](https://gitlab.kitware.com/cmake/cmake/-/blob/v3.17.3/Source/cmGlobalGenerator.cxx#L1975-1977) function. The issue occurs in cmSystemTools::RunSingleCommand, after the a command completes from the `cmsysProcess_WaitForExit(cp, nullptr);` call, the return code is then captured in the following code if the process exits without crashing ``` bool result = true; if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exited) { if (retVal) { *retVal = cmsysProcess_GetExitValue(cp); } else { if (cmsysProcess_GetExitValue(cp) != 0) { result = false; } } } else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exception) { ``` The issue with the above code is that when the`int* retVal` variable is non-nullptr it is populated with the return value of the process, but the `result` variable isn't set to `result = *retVal != 0` This means that the command can return a non-zero exit code, while the cmSystemTools::RunSingleCommand function returns true. Now this could also be avoided in the cmGlobalGenerator::Build function by checking the `retVal != 0` as a logical-or in the if statement that invokes the cmSystemTools::RunSingleCommand function ``` if (!cmSystemTools::RunSingleCommand(command->PrimaryCommand, outputPtr, outputPtr, &retVal, nullptr, outputflag, timeout)) { ``` Changing the above to the following would also resolve the issue ``` if (!cmSystemTools::RunSingleCommand(command->PrimaryCommand, outputPtr, outputPtr, &retVal, nullptr, outputflag, timeout) || retVal != 0) { ```
issue