From a2cd0687db1afec58d5f42a94bb85b2e2ce301fe Mon Sep 17 00:00:00 2001 From: Patrick Northon Date: Fri, 8 Jul 2022 15:49:02 -0400 Subject: try_run: Add RUN_OUTPUT_STDOUT_VARIABLE and RUN_OUTPUT_STDERR_VARIABLE. --- Help/command/try_run.rst | 13 ++ Help/release/dev/try_run_split_output.rst | 6 + Source/cmTryRunCommand.cxx | 171 +++++++++++++++++++-- Source/cmTryRunCommand.h | 10 +- .../RunCMake/try_run/BadStdErrVariable-result.txt | 1 + .../RunCMake/try_run/BadStdErrVariable-stderr.txt | 5 + Tests/RunCMake/try_run/BadStdErrVariable.cmake | 5 + .../RunCMake/try_run/BadStdOutVariable-result.txt | 1 + .../RunCMake/try_run/BadStdOutVariable-stderr.txt | 5 + Tests/RunCMake/try_run/BadStdOutVariable.cmake | 5 + Tests/RunCMake/try_run/RunCMakeTest.cmake | 3 + Tests/TryCompile/CMakeLists.txt | 23 ++- Tests/TryCompile/stdout_and_stderr.c | 8 + 13 files changed, 241 insertions(+), 15 deletions(-) create mode 100644 Help/release/dev/try_run_split_output.rst create mode 100644 Tests/RunCMake/try_run/BadStdErrVariable-result.txt create mode 100644 Tests/RunCMake/try_run/BadStdErrVariable-stderr.txt create mode 100644 Tests/RunCMake/try_run/BadStdErrVariable.cmake create mode 100644 Tests/RunCMake/try_run/BadStdOutVariable-result.txt create mode 100644 Tests/RunCMake/try_run/BadStdOutVariable-stderr.txt create mode 100644 Tests/RunCMake/try_run/BadStdOutVariable.cmake create mode 100644 Tests/TryCompile/stdout_and_stderr.c diff --git a/Help/command/try_run.rst b/Help/command/try_run.rst index fc41cdd..125b0ac 100644 --- a/Help/command/try_run.rst +++ b/Help/command/try_run.rst @@ -19,6 +19,8 @@ Try Compiling and Running Source Files [LINK_LIBRARIES ...] [COMPILE_OUTPUT_VARIABLE ] [RUN_OUTPUT_VARIABLE ] + [RUN_OUTPUT_STDOUT_VARIABLE ] + [RUN_OUTPUT_STDERR_VARIABLE ] [OUTPUT_VARIABLE ] [WORKING_DIRECTORY ] [ARGS ...]) @@ -70,6 +72,16 @@ The options are: ``RUN_OUTPUT_VARIABLE `` Report the output from running the executable in a given variable. +``RUN_OUTPUT_STDOUT_VARIABLE `` + .. versionadded:: 3.25 + + Report the output of stdout from running the executable in a given variable. + +``RUN_OUTPUT_STDERR_VARIABLE `` + .. versionadded:: 3.25 + + Report the output of stderr from running the executable in a given variable. + ``WORKING_DIRECTORY `` .. 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. diff --git a/Help/release/dev/try_run_split_output.rst b/Help/release/dev/try_run_split_output.rst new file mode 100644 index 0000000..98aedd6 --- /dev/null +++ b/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. diff --git a/Source/cmTryRunCommand.cxx b/Source/cmTryRunCommand.cxx index c82ac64..4cd0adc 100644 --- a/Source/cmTryRunCommand.cxx +++ b/Source/cmTryRunCommand.cxx @@ -42,6 +42,8 @@ bool cmTryRunCommand::InitialPass(std::vector 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 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 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 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 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 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 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 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); } } diff --git a/Source/cmTryRunCommand.h b/Source/cmTryRunCommand.h index d45acd8..ccf678e 100644 --- a/Source/cmTryRunCommand.h +++ b/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; }; diff --git a/Tests/RunCMake/try_run/BadStdErrVariable-result.txt b/Tests/RunCMake/try_run/BadStdErrVariable-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/try_run/BadStdErrVariable-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/try_run/BadStdErrVariable-stderr.txt b/Tests/RunCMake/try_run/BadStdErrVariable-stderr.txt new file mode 100644 index 0000000..5d7e5e9 --- /dev/null +++ b/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\) diff --git a/Tests/RunCMake/try_run/BadStdErrVariable.cmake b/Tests/RunCMake/try_run/BadStdErrVariable.cmake new file mode 100644 index 0000000..88c2a72 --- /dev/null +++ b/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 + ) diff --git a/Tests/RunCMake/try_run/BadStdOutVariable-result.txt b/Tests/RunCMake/try_run/BadStdOutVariable-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/try_run/BadStdOutVariable-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/try_run/BadStdOutVariable-stderr.txt b/Tests/RunCMake/try_run/BadStdOutVariable-stderr.txt new file mode 100644 index 0000000..df60658 --- /dev/null +++ b/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\) diff --git a/Tests/RunCMake/try_run/BadStdOutVariable.cmake b/Tests/RunCMake/try_run/BadStdOutVariable.cmake new file mode 100644 index 0000000..691e881 --- /dev/null +++ b/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 + ) diff --git a/Tests/RunCMake/try_run/RunCMakeTest.cmake b/Tests/RunCMake/try_run/RunCMakeTest.cmake index d74add0..5fa5b2f 100644 --- a/Tests/RunCMake/try_run/RunCMakeTest.cmake +++ b/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) diff --git a/Tests/TryCompile/CMakeLists.txt b/Tests/TryCompile/CMakeLists.txt index 000fd2c..7c6f970 100644 --- a/Tests/TryCompile/CMakeLists.txt +++ b/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 diff --git a/Tests/TryCompile/stdout_and_stderr.c b/Tests/TryCompile/stdout_and_stderr.c new file mode 100644 index 0000000..84ded1f --- /dev/null +++ b/Tests/TryCompile/stdout_and_stderr.c @@ -0,0 +1,8 @@ +#include + +int main() +{ + fputs("error\n", stderr); + puts("hello world\n"); + return 0; +} -- cgit v0.12