Browse Source

execute_process: Add option to get results of every child

Add a `RESULTS_VARIABLE` option to get the results of all children
in a pipeline of one or more `COMMAND`s.
Adam Weisi 8 years ago
parent
commit
d6051ca39e

+ 8 - 1
Help/command/execute_process.rst

@@ -10,6 +10,7 @@ Execute one or more child processes.
                   [WORKING_DIRECTORY <directory>]
                   [WORKING_DIRECTORY <directory>]
                   [TIMEOUT <seconds>]
                   [TIMEOUT <seconds>]
                   [RESULT_VARIABLE <variable>]
                   [RESULT_VARIABLE <variable>]
+                  [RESULTS_VARIABLE <variable>]
                   [OUTPUT_VARIABLE <variable>]
                   [OUTPUT_VARIABLE <variable>]
                   [ERROR_VARIABLE <variable>]
                   [ERROR_VARIABLE <variable>]
                   [INPUT_FILE <file>]
                   [INPUT_FILE <file>]
@@ -49,10 +50,16 @@ Options:
  specified number of seconds (fractions are allowed).
  specified number of seconds (fractions are allowed).
 
 
 ``RESULT_VARIABLE``
 ``RESULT_VARIABLE``
- The variable will be set to contain the result of running the processes.
+ The variable will be set to contain the result of last child process.
  This will be an integer return code from the last child or a string
  This will be an integer return code from the last child or a string
  describing an error condition.
  describing an error condition.
 
 
+``RESULTS_VARIABLE <variable>``
+ The variable will be set to contain the result of all processes as a
+ :ref:`;-list <CMake Language Lists>`, in order of the given ``COMMAND``
+ arguments.  Each entry will be an integer return code from the
+ corresponding child or a string describing an error condition.
+
 ``OUTPUT_VARIABLE``, ``ERROR_VARIABLE``
 ``OUTPUT_VARIABLE``, ``ERROR_VARIABLE``
  The variable named will be set with the contents of the standard output
  The variable named will be set with the contents of the standard output
  and standard error pipes, respectively.  If the same variable is named
  and standard error pipes, respectively.  If the same variable is named

+ 6 - 0
Help/release/dev/execute_process-pipeline-results.rst

@@ -0,0 +1,6 @@
+execute_process-pipeline-results
+--------------------------------
+
+* The :command:`execute_process` command gained a ``RESULTS_VARIABLE``
+  option to collect a list of results from all children in a pipeline
+  of processes when multiple ``COMMAND`` arguments are given.

+ 52 - 1
Source/cmExecuteProcessCommand.cxx

@@ -7,6 +7,7 @@
 #include <sstream>
 #include <sstream>
 #include <stdio.h>
 #include <stdio.h>
 
 
+#include "cmAlgorithms.h"
 #include "cmMakefile.h"
 #include "cmMakefile.h"
 #include "cmProcessOutput.h"
 #include "cmProcessOutput.h"
 #include "cmSystemTools.h"
 #include "cmSystemTools.h"
@@ -46,6 +47,7 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
   std::string output_variable;
   std::string output_variable;
   std::string error_variable;
   std::string error_variable;
   std::string result_variable;
   std::string result_variable;
+  std::string results_variable;
   std::string working_directory;
   std::string working_directory;
   cmProcessOutput::Encoding encoding = cmProcessOutput::None;
   cmProcessOutput::Encoding encoding = cmProcessOutput::None;
   for (size_t i = 0; i < args.size(); ++i) {
   for (size_t i = 0; i < args.size(); ++i) {
@@ -77,6 +79,14 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
         this->SetError(" called with no value for RESULT_VARIABLE.");
         this->SetError(" called with no value for RESULT_VARIABLE.");
         return false;
         return false;
       }
       }
+    } else if (args[i] == "RESULTS_VARIABLE") {
+      doing_command = false;
+      if (++i < args.size()) {
+        results_variable = args[i];
+      } else {
+        this->SetError(" called with no value for RESULTS_VARIABLE.");
+        return false;
+      }
     } else if (args[i] == "WORKING_DIRECTORY") {
     } else if (args[i] == "WORKING_DIRECTORY") {
       doing_command = false;
       doing_command = false;
       if (++i < args.size()) {
       if (++i < args.size()) {
@@ -287,7 +297,7 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
     switch (cmsysProcess_GetState(cp)) {
     switch (cmsysProcess_GetState(cp)) {
       case cmsysProcess_State_Exited: {
       case cmsysProcess_State_Exited: {
         int v = cmsysProcess_GetExitValue(cp);
         int v = cmsysProcess_GetExitValue(cp);
-        char buf[100];
+        char buf[16];
         sprintf(buf, "%d", v);
         sprintf(buf, "%d", v);
         this->Makefile->AddDefinition(result_variable, buf);
         this->Makefile->AddDefinition(result_variable, buf);
       } break;
       } break;
@@ -305,6 +315,47 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
         break;
         break;
     }
     }
   }
   }
+  // Store the result of running the processes.
+  if (!results_variable.empty()) {
+    switch (cmsysProcess_GetState(cp)) {
+      case cmsysProcess_State_Exited: {
+        std::vector<std::string> res;
+        for (size_t i = 0; i < cmds.size(); ++i) {
+          switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(i))) {
+            case kwsysProcess_StateByIndex_Exited: {
+              int exitCode =
+                cmsysProcess_GetExitValueByIndex(cp, static_cast<int>(i));
+              char buf[16];
+              sprintf(buf, "%d", exitCode);
+              res.push_back(buf);
+            } break;
+            case kwsysProcess_StateByIndex_Exception:
+              res.push_back(cmsysProcess_GetExceptionStringByIndex(
+                cp, static_cast<int>(i)));
+              break;
+            case kwsysProcess_StateByIndex_Error:
+            default:
+              res.push_back("Error getting the child return code");
+              break;
+          }
+        }
+        this->Makefile->AddDefinition(results_variable,
+                                      cmJoin(res, ";").c_str());
+      } break;
+      case cmsysProcess_State_Exception:
+        this->Makefile->AddDefinition(results_variable,
+                                      cmsysProcess_GetExceptionString(cp));
+        break;
+      case cmsysProcess_State_Error:
+        this->Makefile->AddDefinition(results_variable,
+                                      cmsysProcess_GetErrorString(cp));
+        break;
+      case cmsysProcess_State_Expired:
+        this->Makefile->AddDefinition(results_variable,
+                                      "Process terminated due to timeout");
+        break;
+    }
+  }
 
 
   // Delete the process instance.
   // Delete the process instance.
   cmsysProcess_Delete(cp);
   cmsysProcess_Delete(cp);

+ 3 - 1
Tests/RunCMake/CMakeLists.txt

@@ -181,8 +181,10 @@ add_RunCMake_test(add_custom_target)
 add_RunCMake_test(add_dependencies)
 add_RunCMake_test(add_dependencies)
 add_RunCMake_test(add_subdirectory)
 add_RunCMake_test(add_subdirectory)
 add_RunCMake_test(build_command)
 add_RunCMake_test(build_command)
+add_executable(exit_code exit_code.c)
+set(execute_process_ARGS -DEXIT_CODE_EXE=$<TARGET_FILE:exit_code>)
 if(NOT CMake_TEST_EXTERNAL_CMAKE)
 if(NOT CMake_TEST_EXTERNAL_CMAKE)
-  set(execute_process_ARGS -DTEST_ENCODING_EXE=$<TARGET_FILE:testEncoding>)
+  list(APPEND execute_process_ARGS -DTEST_ENCODING_EXE=$<TARGET_FILE:testEncoding>)
 endif()
 endif()
 add_RunCMake_test(execute_process)
 add_RunCMake_test(execute_process)
 add_RunCMake_test(export)
 add_RunCMake_test(export)

+ 14 - 0
Tests/RunCMake/execute_process/ExitValues-stdout.txt

@@ -0,0 +1,14 @@
+^--   1 - 1 RESULT_VARIABLE: 0
+--   1 - 2 RESULT_VARIABLE: [^0].*
+--   2 - 1 RESULT_VARIABLE: 0
+--   2 - 1 RESULTS_VARIABLE: 0
+--   2 - 2 RESULT_VARIABLE: [^0].*
+--   2 - 2 RESULTS_VARIABLE: [^0].*
+--   3 - 1 RESULTS_VARIABLE: 0
+--   3 - 2 RESULTS_VARIABLE: [^0].*
+--   4 - 1 RESULT_VARIABLE: 0
+--   4 - 1 RESULTS_VARIABLE: [^0].*;0;[^0].*;0;[^0].*;0
+--   4 - 1 RESULTS_VARIABLE_LENGTH: 6
+--   5 - 1 RESULT_VARIABLE: [^0].*
+--   5 - 1 RESULTS_VARIABLE: 0;0;[^0].*
+--   5 - 1 RESULTS_VARIABLE_LENGTH: 3$

+ 120 - 0
Tests/RunCMake/execute_process/ExitValues.cmake

@@ -0,0 +1,120 @@
+#1st TEST RESULT_VARIABLE ONLY
+execute_process(COMMAND ${EXIT_CODE_EXE} "zero_exit"
+                RESULT_VARIABLE r0
+                )
+message(STATUS "  1 - 1 RESULT_VARIABLE: ${r0}")
+if(NOT r0 EQUAL 0)
+    message(FATAL_ERROR "zero exit code expected")
+endif()
+execute_process(COMMAND ${EXIT_CODE_EXE} "non_zero_exit"
+                RESULT_VARIABLE r01
+                ERROR_QUIET
+                )
+message(STATUS "  1 - 2 RESULT_VARIABLE: ${r01}")
+if(r01 EQUAL 0)
+    message(FATAL_ERROR "non-zero exit code expected")
+endif()
+#2nd TEST RESULT_VARIABLE and RESULTS_VARIABLE
+execute_process(COMMAND ${EXIT_CODE_EXE} "zero_exit"
+                RESULT_VARIABLE r1
+                RESULTS_VARIABLE r1s
+                )
+message(STATUS "  2 - 1 RESULT_VARIABLE: ${r1}")
+message(STATUS "  2 - 1 RESULTS_VARIABLE: ${r1s}")
+if(NOT r1 EQUAL 0 OR NOT r1s EQUAL 0)
+    message(FATAL_ERROR "zero exit code expected")
+endif()
+execute_process(COMMAND ${EXIT_CODE_EXE} "non_zero_exit"
+                RESULT_VARIABLE r11
+                RESULTS_VARIABLE r11s
+                ERROR_QUIET
+                )
+message(STATUS "  2 - 2 RESULT_VARIABLE: ${r11}")
+message(STATUS "  2 - 2 RESULTS_VARIABLE: ${r11s}")
+if(r11 EQUAL 0 OR r11s EQUAL 0)
+    message(FATAL_ERROR "non-zero exit code expected")
+endif()
+#3rd TEST RESULTS_VARIABLE
+execute_process(COMMAND ${EXIT_CODE_EXE} "zero_exit"
+                RESULTS_VARIABLE r2s
+                )
+message(STATUS "  3 - 1 RESULTS_VARIABLE: ${r2s}")
+if(NOT r2s EQUAL 0)
+    message(FATAL_ERROR "zero exit code expected")
+endif()
+execute_process(COMMAND ${EXIT_CODE_EXE} "non_zero_exit"
+                RESULTS_VARIABLE r21s
+                ERROR_QUIET
+                )
+message(STATUS "  3 - 2 RESULTS_VARIABLE: ${r21s}")
+if(r21s EQUAL 0)
+    message(FATAL_ERROR "non-zero exit code expected")
+endif()
+#4th TEST RESULT_VARIABLE and RESULTS_VARIABLE WITH MULTICOMMAND
+execute_process(COMMAND ${EXIT_CODE_EXE} "non_zero_exit"
+                COMMAND ${EXIT_CODE_EXE} "zero_exit"
+                COMMAND ${EXIT_CODE_EXE} "non_zero_exit"
+                COMMAND ${EXIT_CODE_EXE} "zero_exit"
+                COMMAND ${EXIT_CODE_EXE} "non_zero_exit"
+                COMMAND ${EXIT_CODE_EXE} "zero_exit"
+                RESULT_VARIABLE  r31
+                RESULTS_VARIABLE r31s
+                OUTPUT_QUIET
+                ERROR_QUIET
+                )
+message(STATUS "  4 - 1 RESULT_VARIABLE: ${r31}")
+message(STATUS "  4 - 1 RESULTS_VARIABLE: ${r31s}")
+if(NOT r31 EQUAL 0)
+    message(FATAL_ERROR "zero exit code expected for last command")
+endif()
+list(LENGTH r31s r31sLen)
+message(STATUS "  4 - 1 RESULTS_VARIABLE_LENGTH: ${r31sLen}")
+if(NOT r31sLen EQUAL 6)
+    message(FATAL_ERROR "length of RESULTS_VARIABLE is not as expected")
+else()
+    foreach(loop_var RANGE 5)
+      list(GET r31s ${loop_var} rsLocal)
+      math(EXPR isOdd "${loop_var} % 2")
+      if(isOdd)
+        if(NOT rsLocal EQUAL 0)
+          message(FATAL_ERROR "zero exit code expected")
+        endif()
+      else()
+        if(rsLocal EQUAL 0)
+          message(FATAL_ERROR "non-zero exit code expected")
+        endif()
+      endif()
+    endforeach()
+endif()
+#5th TEST RESULT_VARIABLE and RESULTS_VARIABLE WITH MULTICOMMAND
+execute_process(COMMAND ${EXIT_CODE_EXE} "zero_exit"
+                COMMAND ${EXIT_CODE_EXE} "zero_exit"
+                COMMAND ${EXIT_CODE_EXE} "non_zero_exit"
+                RESULT_VARIABLE  r41
+                RESULTS_VARIABLE r41s
+                OUTPUT_QUIET
+                ERROR_QUIET
+                )
+message(STATUS "  5 - 1 RESULT_VARIABLE: ${r41}")
+message(STATUS "  5 - 1 RESULTS_VARIABLE: ${r41s}")
+if(r41 EQUAL 0)
+    message(FATAL_ERROR "non-zero exit code expected for last command")
+endif()
+list(LENGTH r41s r41sLen)
+message(STATUS "  5 - 1 RESULTS_VARIABLE_LENGTH: ${r41sLen}")
+if(NOT r31sLen EQUAL 6)
+    message(FATAL_ERROR "length of RESULTS_VARIABLE is not as expected")
+else()
+    list(GET r41s 0 rsLocal)
+    if(NOT rsLocal EQUAL 0)
+      message(FATAL_ERROR "zero exit code expected")
+    endif()
+    list(GET r41s 1 rsLocal)
+    if(NOT rsLocal EQUAL 0)
+      message(FATAL_ERROR "zero exit code expected")
+    endif()
+    list(GET r41s 2 rsLocal)
+    if(rsLocal EQUAL 0)
+      message(FATAL_ERROR "non-zero exit code expected")
+    endif()
+endif()

+ 4 - 0
Tests/RunCMake/execute_process/RunCMakeTest.cmake

@@ -11,3 +11,7 @@ run_cmake(EncodingMissing)
 if(TEST_ENCODING_EXE)
 if(TEST_ENCODING_EXE)
   run_cmake_command(EncodingUTF8 ${CMAKE_COMMAND} -DTEST_ENCODING=UTF8 -DTEST_ENCODING_EXE=${TEST_ENCODING_EXE} -P ${RunCMake_SOURCE_DIR}/Encoding.cmake)
   run_cmake_command(EncodingUTF8 ${CMAKE_COMMAND} -DTEST_ENCODING=UTF8 -DTEST_ENCODING_EXE=${TEST_ENCODING_EXE} -P ${RunCMake_SOURCE_DIR}/Encoding.cmake)
 endif()
 endif()
+
+if(EXIT_CODE_EXE)
+  run_cmake_command(ExitValues ${CMAKE_COMMAND} -DEXIT_CODE_EXE=${EXIT_CODE_EXE} -P ${RunCMake_SOURCE_DIR}/ExitValues.cmake)
+endif()

+ 30 - 0
Tests/RunCMake/exit_code.c

@@ -0,0 +1,30 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Usage:
+//
+//  /path/to/program arg1 [arg2 [...]]
+//
+// Return EXIT_SUCCESS if 'zero_exit'
+// string was found in <arg1>.
+// Return EXIT_FAILURE if 'non_zero_exit'
+// string was found in <arg1>.
+
+int main(int argc, const char* argv[])
+{
+  const char* substring_failure = "non_zero_exit";
+  const char* substring_success = "zero_exit";
+  const char* str = argv[1];
+  if (argc < 2) {
+    return EXIT_FAILURE;
+  }
+  if (strcmp(str, substring_success) == 0) {
+    return EXIT_SUCCESS;
+  } else if (strcmp(str, substring_failure) == 0) {
+    return EXIT_FAILURE;
+  }
+  fprintf(stderr, "Failed to find string '%s' in '%s'\n", substring_success,
+          str);
+  return EXIT_FAILURE;
+}