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 from the cmGlobalGenerator::Build 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 theint* 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) {