Explorar o código

try_run: Add RUN_OUTPUT_STDOUT_VARIABLE and RUN_OUTPUT_STDERR_VARIABLE.

Patrick Northon %!s(int64=3) %!d(string=hai) anos
pai
achega
a2cd0687db

+ 13 - 0
Help/command/try_run.rst

@@ -19,6 +19,8 @@ Try Compiling and Running Source Files
           [LINK_LIBRARIES <libs>...]
           [COMPILE_OUTPUT_VARIABLE <var>]
           [RUN_OUTPUT_VARIABLE <var>]
+          [RUN_OUTPUT_STDOUT_VARIABLE <var>]
+          [RUN_OUTPUT_STDERR_VARIABLE <var>]
           [OUTPUT_VARIABLE <var>]
           [WORKING_DIRECTORY <var>]
           [ARGS <args>...])
@@ -70,6 +72,16 @@ The options are:
 ``RUN_OUTPUT_VARIABLE <var>``
   Report the output from running the executable in a given variable.
 
+``RUN_OUTPUT_STDOUT_VARIABLE <var>``
+  .. versionadded:: 3.25
+
+  Report the output of stdout from running the executable in a given variable.
+
+``RUN_OUTPUT_STDERR_VARIABLE <var>``
+  .. versionadded:: 3.25
+
+  Report the output of stderr from running the executable in a given variable.
+
 ``WORKING_DIRECTORY <var>``
   .. versionadded:: 3.20
 
@@ -110,6 +122,7 @@ These cache entries are:
 
 In order to make cross compiling your project easier, use ``try_run``
 only if really required.  If you use ``try_run``, use the
+``RUN_OUTPUT_STDOUT_VARIABLE``, ``RUN_OUTPUT_STDERR_VARIABLE``,
 ``RUN_OUTPUT_VARIABLE`` or ``OUTPUT_VARIABLE`` options only if really
 required.  Using them will require that when cross-compiling, the cache
 variables will have to be set manually to the output of the executable.

+ 6 - 0
Help/release/dev/try_run_split_output.rst

@@ -0,0 +1,6 @@
+try_run_split_output
+--------------------
+
+* The :command:`try_run` command gained ``RUN_OUTPUT_STDOUT_VARIABLE``
+  and ``RUN_OUTPUT_STDERR_VARIABLE`` options to capture stdout and stderr
+  separately from the output of the compiled program.

+ 159 - 12
Source/cmTryRunCommand.cxx

@@ -42,6 +42,8 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
   this->RunResultVariable.clear();
   this->OutputVariable.clear();
   this->RunOutputVariable.clear();
+  this->RunOutputStdOutVariable.clear();
+  this->RunOutputStdErrVariable.clear();
   this->CompileOutputVariable.clear();
 
   std::string runArgs;
@@ -76,6 +78,22 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
         }
         i++;
         this->RunOutputVariable = argv[i];
+      } else if (argv[i] == "RUN_OUTPUT_STDOUT_VARIABLE") {
+        if (argv.size() <= (i + 1)) {
+          cmSystemTools::Error(
+            "RUN_OUTPUT_STDOUT_VARIABLE specified but there is no variable");
+          return false;
+        }
+        i++;
+        this->RunOutputStdOutVariable = argv[i];
+      } else if (argv[i] == "RUN_OUTPUT_STDERR_VARIABLE") {
+        if (argv.size() <= (i + 1)) {
+          cmSystemTools::Error(
+            "RUN_OUTPUT_STDERR_VARIABLE specified but there is no variable");
+          return false;
+        }
+        i++;
+        this->RunOutputStdErrVariable = argv[i];
       } else if (argv[i] == "COMPILE_OUTPUT_VARIABLE") {
         if (argv.size() <= (i + 1)) {
           cmSystemTools::Error(
@@ -102,11 +120,27 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
   // using OUTPUT_VARIABLE makes crosscompiling harder
   if (!this->OutputVariable.empty() &&
       (!this->RunOutputVariable.empty() ||
-       !this->CompileOutputVariable.empty())) {
+       !this->CompileOutputVariable.empty() ||
+       !this->RunOutputStdOutVariable.empty() ||
+       !this->RunOutputStdErrVariable.empty())) {
     cmSystemTools::Error(
       "You cannot use OUTPUT_VARIABLE together with COMPILE_OUTPUT_VARIABLE "
-      "or RUN_OUTPUT_VARIABLE. Please use only COMPILE_OUTPUT_VARIABLE and/or "
-      "RUN_OUTPUT_VARIABLE.");
+      ", RUN_OUTPUT_VARIABLE, RUN_OUTPUT_STDOUT_VARIABLE or "
+      "RUN_OUTPUT_STDERR_VARIABLE. "
+      "Please use only COMPILE_OUTPUT_VARIABLE, RUN_OUTPUT_VARIABLE, "
+      "RUN_OUTPUT_STDOUT_VARIABLE "
+      "and/or RUN_OUTPUT_STDERR_VARIABLE.");
+    return false;
+  }
+
+  if ((!this->RunOutputStdOutVariable.empty() ||
+       !RunOutputStdErrVariable.empty()) &&
+      !this->RunOutputVariable.empty()) {
+    cmSystemTools::Error(
+      "You cannot use RUN_OUTPUT_STDOUT_VARIABLE or "
+      "RUN_OUTPUT_STDERR_VARIABLE together "
+      "with RUN_OUTPUT_VARIABLE. Please use only COMPILE_OUTPUT_VARIABLE or "
+      "RUN_OUTPUT_STDOUT_VARIABLE and/or RUN_OUTPUT_STDERR_VARIABLE.");
     return false;
   }
 
@@ -119,6 +153,7 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
   }
 
   bool captureRunOutput = false;
+  bool captureRunOutputStdOutErr = false;
   if (!this->OutputVariable.empty()) {
     captureRunOutput = true;
     tryCompile.emplace_back("OUTPUT_VARIABLE");
@@ -128,7 +163,10 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
     tryCompile.emplace_back("OUTPUT_VARIABLE");
     tryCompile.push_back(this->CompileOutputVariable);
   }
-  if (!this->RunOutputVariable.empty()) {
+  if (!this->RunOutputStdOutVariable.empty() ||
+      !RunOutputStdErrVariable.empty()) {
+    captureRunOutputStdOutErr = true;
+  } else if (!this->RunOutputVariable.empty()) {
     captureRunOutput = true;
   }
 
@@ -145,12 +183,27 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
     } else {
       // "run" it and capture the output
       std::string runOutputContents;
+      std::string runOutputStdOutContents;
+      std::string runOutputStdErrContents;
       if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING") &&
           !this->Makefile->IsDefinitionSet("CMAKE_CROSSCOMPILING_EMULATOR")) {
         this->DoNotRunExecutable(
-          runArgs, argv[3], captureRunOutput ? &runOutputContents : nullptr);
+          runArgs, argv[3], captureRunOutput ? &runOutputContents : nullptr,
+          captureRunOutputStdOutErr && !RunOutputStdOutVariable.empty()
+            ? &runOutputStdOutContents
+            : nullptr,
+          captureRunOutputStdOutErr && !RunOutputStdErrVariable.empty()
+            ? &runOutputStdErrContents
+            : nullptr);
       } else {
-        this->RunExecutable(runArgs, &runOutputContents);
+        this->RunExecutable(
+          runArgs, captureRunOutput ? &runOutputContents : nullptr,
+          captureRunOutputStdOutErr && !RunOutputStdOutVariable.empty()
+            ? &runOutputStdOutContents
+            : nullptr,
+          captureRunOutputStdOutErr && !RunOutputStdErrVariable.empty()
+            ? &runOutputStdErrContents
+            : nullptr);
       }
 
       // now put the output into the variables
@@ -158,6 +211,14 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
         this->Makefile->AddDefinition(this->RunOutputVariable,
                                       runOutputContents);
       }
+      if (!this->RunOutputStdOutVariable.empty()) {
+        this->Makefile->AddDefinition(this->RunOutputStdOutVariable,
+                                      runOutputStdOutContents);
+      }
+      if (!this->RunOutputStdErrVariable.empty()) {
+        this->Makefile->AddDefinition(this->RunOutputStdErrVariable,
+                                      runOutputStdErrContents);
+      }
 
       if (!this->OutputVariable.empty()) {
         // if the TryCompileCore saved output in this outputVariable then
@@ -180,7 +241,8 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
 }
 
 void cmTryRunCommand::RunExecutable(const std::string& runArgs,
-                                    std::string* out)
+                                    std::string* out, std::string* stdOut,
+                                    std::string* stdErr)
 {
   int retVal = -1;
 
@@ -204,7 +266,8 @@ void cmTryRunCommand::RunExecutable(const std::string& runArgs,
     finalCommand += runArgs;
   }
   bool worked = cmSystemTools::RunSingleCommand(
-    finalCommand, out, out, &retVal,
+    finalCommand, stdOut || stdErr ? stdOut : out,
+    stdOut || stdErr ? stdErr : out, &retVal,
     this->WorkingDirectory.empty() ? nullptr : this->WorkingDirectory.c_str(),
     cmSystemTools::OUTPUT_NONE, cmDuration::zero());
   // set the run var
@@ -227,7 +290,8 @@ void cmTryRunCommand::RunExecutable(const std::string& runArgs,
 */
 void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
                                          const std::string& srcFile,
-                                         std::string* out)
+                                         std::string* out, std::string* stdOut,
+                                         std::string* stdErr)
 {
   // copy the executable out of the CMakeFiles/ directory, so it is not
   // removed at the end of try_run() and the user can run it manually
@@ -246,6 +310,10 @@ void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
 
   std::string internalRunOutputName =
     this->RunResultVariable + "__TRYRUN_OUTPUT";
+  std::string internalRunOutputStdOutName =
+    this->RunResultVariable + "__TRYRUN_OUTPUT_STDOUT";
+  std::string internalRunOutputStdErrName =
+    this->RunResultVariable + "__TRYRUN_OUTPUT_STDERR";
   bool error = false;
 
   if (!this->Makefile->GetDefinition(this->RunResultVariable)) {
@@ -269,7 +337,51 @@ void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
   }
 
   // is the output from the executable used ?
-  if (out) {
+  if (stdOut || stdErr) {
+    if (!this->Makefile->GetDefinition(internalRunOutputStdOutName)) {
+      // if the variables doesn't exist, create it with a helpful error text
+      // and mark it as advanced
+      std::string comment = cmStrCat(
+        "Output of try_run(), contains the text, which the executable "
+        "would have printed on stdout on its target platform.\n",
+        detailsString);
+
+      this->Makefile->AddCacheDefinition(
+        internalRunOutputStdOutName, "PLEASE_FILL_OUT-NOTFOUND",
+        comment.c_str(), cmStateEnums::STRING);
+      cmState* state = this->Makefile->GetState();
+      cmValue existing =
+        state->GetCacheEntryValue(internalRunOutputStdOutName);
+      if (existing) {
+        state->SetCacheEntryProperty(internalRunOutputStdOutName, "ADVANCED",
+                                     "1");
+      }
+
+      error = true;
+    }
+
+    if (!this->Makefile->GetDefinition(internalRunOutputStdErrName)) {
+      // if the variables doesn't exist, create it with a helpful error text
+      // and mark it as advanced
+      std::string comment = cmStrCat(
+        "Output of try_run(), contains the text, which the executable "
+        "would have printed on stderr on its target platform.\n",
+        detailsString);
+
+      this->Makefile->AddCacheDefinition(
+        internalRunOutputStdErrName, "PLEASE_FILL_OUT-NOTFOUND",
+        comment.c_str(), cmStateEnums::STRING);
+      cmState* state = this->Makefile->GetState();
+      cmValue existing =
+        state->GetCacheEntryValue(internalRunOutputStdErrName);
+      if (existing) {
+        state->SetCacheEntryProperty(internalRunOutputStdErrName, "ADVANCED",
+                                     "1");
+      }
+
+      error = true;
+    }
+  } else if (out) {
     if (!this->Makefile->GetDefinition(internalRunOutputName)) {
       // if the variables doesn't exist, create it with a helpful error text
       // and mark it as advanced
@@ -317,7 +429,34 @@ void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
                  " to\n"
                  "   the exit code (in many cases 0 for success), otherwise "
                  "enter \"FAILED_TO_RUN\".\n");
-      if (out) {
+      if (stdOut || stdErr) {
+        if (stdOut) {
+          comment += internalRunOutputStdOutName;
+          comment +=
+            "\n   contains the text the executable "
+            "would have printed on stdout.\n"
+            "   If the executable would not have been able to run, set ";
+          comment += internalRunOutputStdOutName;
+          comment += " empty.\n"
+                     "   Otherwise check if the output is evaluated by the "
+                     "calling CMake code. If so,\n"
+                     "   check what the source file would have printed when "
+                     "called with the given arguments.\n";
+        }
+        if (stdErr) {
+          comment += internalRunOutputStdErrName;
+          comment +=
+            "\n   contains the text the executable "
+            "would have printed on stderr.\n"
+            "   If the executable would not have been able to run, set ";
+          comment += internalRunOutputStdErrName;
+          comment += " empty.\n"
+                     "   Otherwise check if the output is evaluated by the "
+                     "calling CMake code. If so,\n"
+                     "   check what the source file would have printed when "
+                     "called with the given arguments.\n";
+        }
+      } else if (out) {
         comment += internalRunOutputName;
         comment +=
           "\n   contains the text the executable "
@@ -330,6 +469,7 @@ void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
                    "   check what the source file would have printed when "
                    "called with the given arguments.\n";
       }
+
       comment += "The ";
       comment += this->CompileResultVariable;
       comment += " variable holds the build result for this try_run().\n\n"
@@ -370,7 +510,14 @@ void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
     return;
   }
 
-  if (out) {
+  if (stdOut || stdErr) {
+    if (stdOut) {
+      (*stdOut) = *this->Makefile->GetDefinition(internalRunOutputStdOutName);
+    }
+    if (stdErr) {
+      (*stdErr) = *this->Makefile->GetDefinition(internalRunOutputStdErrName);
+    }
+  } else if (out) {
     (*out) = *this->Makefile->GetDefinition(internalRunOutputName);
   }
 }

+ 8 - 2
Source/cmTryRunCommand.h

@@ -39,15 +39,21 @@ public:
 
 private:
   void RunExecutable(const std::string& runArgs,
-                     std::string* runOutputContents);
+                     std::string* runOutputContents,
+                     std::string* runOutputStdOutContents,
+                     std::string* runOutputStdErrContents);
   void DoNotRunExecutable(const std::string& runArgs,
                           const std::string& srcFile,
-                          std::string* runOutputContents);
+                          std::string* runOutputContents,
+                          std::string* runOutputStdOutContents,
+                          std::string* runOutputStdErrContents);
 
   std::string CompileResultVariable;
   std::string RunResultVariable;
   std::string OutputVariable;
   std::string RunOutputVariable;
+  std::string RunOutputStdOutVariable;
+  std::string RunOutputStdErrVariable;
   std::string CompileOutputVariable;
   std::string WorkingDirectory;
 };

+ 1 - 0
Tests/RunCMake/try_run/BadStdErrVariable-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/try_run/BadStdErrVariable-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error: RUN_OUTPUT_STDERR_VARIABLE specified but there is no variable
+CMake Error at BadStdErrVariable.cmake:1 \(try_run\):
+  try_run unknown error.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 5 - 0
Tests/RunCMake/try_run/BadStdErrVariable.cmake

@@ -0,0 +1,5 @@
+try_run(RUN_RESULT COMPILE_RESULT
+  ${CMAKE_CURRENT_BINARY_DIR}/CMakeTmp ${CMAKE_CURRENT_SOURCE_DIR}/src.c
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CMakeTmp/workdir
+  RUN_OUTPUT_STDERR_VARIABLE
+  )

+ 1 - 0
Tests/RunCMake/try_run/BadStdOutVariable-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/try_run/BadStdOutVariable-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error: RUN_OUTPUT_STDOUT_VARIABLE specified but there is no variable
+CMake Error at BadStdOutVariable.cmake:1 \(try_run\):
+  try_run unknown error.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 5 - 0
Tests/RunCMake/try_run/BadStdOutVariable.cmake

@@ -0,0 +1,5 @@
+try_run(RUN_RESULT COMPILE_RESULT
+  ${CMAKE_CURRENT_BINARY_DIR}/CMakeTmp ${CMAKE_CURRENT_SOURCE_DIR}/src.c
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CMakeTmp/workdir
+  RUN_OUTPUT_STDOUT_VARIABLE
+  )

+ 3 - 0
Tests/RunCMake/try_run/RunCMakeTest.cmake

@@ -10,3 +10,6 @@ if (CMAKE_SYSTEM_NAME MATCHES "^(Linux|Darwin|Windows)$" AND
 endif()
 
 run_cmake(WorkingDirArg)
+
+run_cmake(BadStdOutVariable)
+run_cmake(BadStdErrVariable)

+ 22 - 1
Tests/TryCompile/CMakeLists.txt

@@ -259,11 +259,32 @@ endif()
 if("${COMPILE_OUTPUT}" MATCHES "hello world")
   message(SEND_ERROR " COMPILE_OUT contains the run output: \"${COMPILE_OUTPUT}\"")
 endif()
-# check the run output, it should stdout
+# check the run output, it should contain stdout
 if(NOT "${RUN_OUTPUT}" MATCHES "hello world")
   message(SEND_ERROR " RUN_OUTPUT didn't contain \"hello world\": \"${RUN_OUTPUT}\"")
 endif()
 
+# try to run a file and parse stdout and stderr separately
+try_run(SHOULD_EXIT_WITH_ERROR SHOULD_COMPILE
+    ${TryCompile_BINARY_DIR}
+    ${TryCompile_SOURCE_DIR}/stdout_and_stderr.c
+    COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT
+    RUN_OUTPUT_STDOUT_VARIABLE RUN_OUTPUT_STDOUT
+    RUN_OUTPUT_STDERR_VARIABLE RUN_OUTPUT_STDERR)
+
+if(NOT SHOULD_COMPILE)
+  message(STATUS " exit_with_error failed compiling: ${COMPILE_OUTPUT}")
+endif()
+
+# check the run stdout output
+if(NOT "${RUN_OUTPUT_STDOUT}" MATCHES "hello world")
+  message(SEND_ERROR " RUN_OUTPUT_STDOUT didn't contain \"hello world\": \"${RUN_OUTPUT_STDOUT}\"")
+endif()
+# check the run stderr output
+if(NOT "${RUN_OUTPUT_STDERR}" MATCHES "error")
+  message(SEND_ERROR " RUN_OUTPUT_STDERR didn't contain \"error\": \"${RUN_OUTPUT_STDERR}\"")
+endif()
+
 #######################################################################
 #
 # also test that the CHECK_C_SOURCE_COMPILES, CHECK_CXX_SOURCE_COMPILES

+ 8 - 0
Tests/TryCompile/stdout_and_stderr.c

@@ -0,0 +1,8 @@
+#include <stdio.h>
+
+int main()
+{
+  fputs("error\n", stderr);
+  puts("hello world\n");
+  return 0;
+}