try_run: Add RUN_OUTPUT_STDOUT_VARIABLE and RUN_OUTPUT_STDERR_VARIABLE.

This commit is contained in:
Patrick Northon 2022-07-08 15:49:02 -04:00
parent fc30196e76
commit a2cd0687db
13 changed files with 241 additions and 15 deletions

View File

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

View File

@ -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.

View File

@ -42,6 +42,8 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
this->RunResultVariable.clear(); this->RunResultVariable.clear();
this->OutputVariable.clear(); this->OutputVariable.clear();
this->RunOutputVariable.clear(); this->RunOutputVariable.clear();
this->RunOutputStdOutVariable.clear();
this->RunOutputStdErrVariable.clear();
this->CompileOutputVariable.clear(); this->CompileOutputVariable.clear();
std::string runArgs; std::string runArgs;
@ -76,6 +78,22 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
} }
i++; i++;
this->RunOutputVariable = 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") { } else if (argv[i] == "COMPILE_OUTPUT_VARIABLE") {
if (argv.size() <= (i + 1)) { if (argv.size() <= (i + 1)) {
cmSystemTools::Error( cmSystemTools::Error(
@ -102,11 +120,27 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
// using OUTPUT_VARIABLE makes crosscompiling harder // using OUTPUT_VARIABLE makes crosscompiling harder
if (!this->OutputVariable.empty() && if (!this->OutputVariable.empty() &&
(!this->RunOutputVariable.empty() || (!this->RunOutputVariable.empty() ||
!this->CompileOutputVariable.empty())) { !this->CompileOutputVariable.empty() ||
!this->RunOutputStdOutVariable.empty() ||
!this->RunOutputStdErrVariable.empty())) {
cmSystemTools::Error( cmSystemTools::Error(
"You cannot use OUTPUT_VARIABLE together with COMPILE_OUTPUT_VARIABLE " "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_STDOUT_VARIABLE or "
"RUN_OUTPUT_VARIABLE."); "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; return false;
} }
@ -119,6 +153,7 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
} }
bool captureRunOutput = false; bool captureRunOutput = false;
bool captureRunOutputStdOutErr = false;
if (!this->OutputVariable.empty()) { if (!this->OutputVariable.empty()) {
captureRunOutput = true; captureRunOutput = true;
tryCompile.emplace_back("OUTPUT_VARIABLE"); tryCompile.emplace_back("OUTPUT_VARIABLE");
@ -128,7 +163,10 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
tryCompile.emplace_back("OUTPUT_VARIABLE"); tryCompile.emplace_back("OUTPUT_VARIABLE");
tryCompile.push_back(this->CompileOutputVariable); tryCompile.push_back(this->CompileOutputVariable);
} }
if (!this->RunOutputVariable.empty()) { if (!this->RunOutputStdOutVariable.empty() ||
!RunOutputStdErrVariable.empty()) {
captureRunOutputStdOutErr = true;
} else if (!this->RunOutputVariable.empty()) {
captureRunOutput = true; captureRunOutput = true;
} }
@ -145,12 +183,27 @@ bool cmTryRunCommand::InitialPass(std::vector<std::string> const& argv,
} else { } else {
// "run" it and capture the output // "run" it and capture the output
std::string runOutputContents; std::string runOutputContents;
std::string runOutputStdOutContents;
std::string runOutputStdErrContents;
if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING") && if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING") &&
!this->Makefile->IsDefinitionSet("CMAKE_CROSSCOMPILING_EMULATOR")) { !this->Makefile->IsDefinitionSet("CMAKE_CROSSCOMPILING_EMULATOR")) {
this->DoNotRunExecutable( 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 { } 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 // 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, this->Makefile->AddDefinition(this->RunOutputVariable,
runOutputContents); 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 (!this->OutputVariable.empty()) {
// if the TryCompileCore saved output in this outputVariable then // 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, void cmTryRunCommand::RunExecutable(const std::string& runArgs,
std::string* out) std::string* out, std::string* stdOut,
std::string* stdErr)
{ {
int retVal = -1; int retVal = -1;
@ -204,7 +266,8 @@ void cmTryRunCommand::RunExecutable(const std::string& runArgs,
finalCommand += runArgs; finalCommand += runArgs;
} }
bool worked = cmSystemTools::RunSingleCommand( 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(), this->WorkingDirectory.empty() ? nullptr : this->WorkingDirectory.c_str(),
cmSystemTools::OUTPUT_NONE, cmDuration::zero()); cmSystemTools::OUTPUT_NONE, cmDuration::zero());
// set the run var // set the run var
@ -227,7 +290,8 @@ void cmTryRunCommand::RunExecutable(const std::string& runArgs,
*/ */
void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs, void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
const std::string& srcFile, 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 // 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 // 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 = std::string internalRunOutputName =
this->RunResultVariable + "__TRYRUN_OUTPUT"; this->RunResultVariable + "__TRYRUN_OUTPUT";
std::string internalRunOutputStdOutName =
this->RunResultVariable + "__TRYRUN_OUTPUT_STDOUT";
std::string internalRunOutputStdErrName =
this->RunResultVariable + "__TRYRUN_OUTPUT_STDERR";
bool error = false; bool error = false;
if (!this->Makefile->GetDefinition(this->RunResultVariable)) { if (!this->Makefile->GetDefinition(this->RunResultVariable)) {
@ -269,7 +337,51 @@ void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
} }
// is the output from the executable used ? // 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 (!this->Makefile->GetDefinition(internalRunOutputName)) {
// if the variables doesn't exist, create it with a helpful error text // if the variables doesn't exist, create it with a helpful error text
// and mark it as advanced // and mark it as advanced
@ -317,7 +429,34 @@ void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
" to\n" " to\n"
" the exit code (in many cases 0 for success), otherwise " " the exit code (in many cases 0 for success), otherwise "
"enter \"FAILED_TO_RUN\".\n"); "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 += internalRunOutputName;
comment += comment +=
"\n contains the text the executable " "\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 " " check what the source file would have printed when "
"called with the given arguments.\n"; "called with the given arguments.\n";
} }
comment += "The "; comment += "The ";
comment += this->CompileResultVariable; comment += this->CompileResultVariable;
comment += " variable holds the build result for this try_run().\n\n" comment += " variable holds the build result for this try_run().\n\n"
@ -370,7 +510,14 @@ void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
return; 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); (*out) = *this->Makefile->GetDefinition(internalRunOutputName);
} }
} }

View File

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

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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
)

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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
)

View File

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

View File

@ -259,11 +259,32 @@ endif()
if("${COMPILE_OUTPUT}" MATCHES "hello world") if("${COMPILE_OUTPUT}" MATCHES "hello world")
message(SEND_ERROR " COMPILE_OUT contains the run output: \"${COMPILE_OUTPUT}\"") message(SEND_ERROR " COMPILE_OUT contains the run output: \"${COMPILE_OUTPUT}\"")
endif() endif()
# check the run output, it should stdout # check the run output, it should contain stdout
if(NOT "${RUN_OUTPUT}" MATCHES "hello world") if(NOT "${RUN_OUTPUT}" MATCHES "hello world")
message(SEND_ERROR " RUN_OUTPUT didn't contain \"hello world\": \"${RUN_OUTPUT}\"") message(SEND_ERROR " RUN_OUTPUT didn't contain \"hello world\": \"${RUN_OUTPUT}\"")
endif() 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 # also test that the CHECK_C_SOURCE_COMPILES, CHECK_CXX_SOURCE_COMPILES

View File

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