浏览代码

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 年之前
父节点
当前提交
d6051ca39e

+ 8 - 1
Help/command/execute_process.rst

@@ -10,6 +10,7 @@ Execute one or more child processes.
                   [WORKING_DIRECTORY <directory>]
                   [TIMEOUT <seconds>]
                   [RESULT_VARIABLE <variable>]
+                  [RESULTS_VARIABLE <variable>]
                   [OUTPUT_VARIABLE <variable>]
                   [ERROR_VARIABLE <variable>]
                   [INPUT_FILE <file>]
@@ -49,10 +50,16 @@ Options:
  specified number of seconds (fractions are allowed).
 
 ``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
  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``
  The variable named will be set with the contents of the standard output
  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 <stdio.h>
 
+#include "cmAlgorithms.h"
 #include "cmMakefile.h"
 #include "cmProcessOutput.h"
 #include "cmSystemTools.h"
@@ -46,6 +47,7 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
   std::string output_variable;
   std::string error_variable;
   std::string result_variable;
+  std::string results_variable;
   std::string working_directory;
   cmProcessOutput::Encoding encoding = cmProcessOutput::None;
   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.");
         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") {
       doing_command = false;
       if (++i < args.size()) {
@@ -287,7 +297,7 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
     switch (cmsysProcess_GetState(cp)) {
       case cmsysProcess_State_Exited: {
         int v = cmsysProcess_GetExitValue(cp);
-        char buf[100];
+        char buf[16];
         sprintf(buf, "%d", v);
         this->Makefile->AddDefinition(result_variable, buf);
       } break;
@@ -305,6 +315,47 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
         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.
   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_subdirectory)
 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)
-  set(execute_process_ARGS -DTEST_ENCODING_EXE=$<TARGET_FILE:testEncoding>)
+  list(APPEND execute_process_ARGS -DTEST_ENCODING_EXE=$<TARGET_FILE:testEncoding>)
 endif()
 add_RunCMake_test(execute_process)
 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)
   run_cmake_command(EncodingUTF8 ${CMAKE_COMMAND} -DTEST_ENCODING=UTF8 -DTEST_ENCODING_EXE=${TEST_ENCODING_EXE} -P ${RunCMake_SOURCE_DIR}/Encoding.cmake)
 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;
+}