From eddf1cf39f50f9b06131ead26b5b84b8edfd60da Mon Sep 17 00:00:00 2001 From: Alexander Neundorf Date: Fri, 1 Jun 2007 11:16:29 -0400 Subject: [PATCH] ENH: improve TRY_RUN() for crosscompiling: instead of just failing, it now creates two cache variables, one for the RUN_RESULT, one for the RUN_OUTPUT (if required), which can be set or preset by the user. It has now also two new arguments: RUN_OUTPUT_VARIABLE and COMPILE_OUTPUT_VARIABLE (the old OUTPUT_VARIABLE merges both), so if only COMPILE_OUTPUT_VARIABLE is used the run time output of the TRY_RUN is unused and the user doesn't have to care about the output when crosscompiling. This is now used in FindThreads.cmake, CheckC/CXXSourceRuns.cmake and TestBigEndian.cmake, which used the output only for the logfile (compile output is still there). Test/TryCompile/ now also tests the behaviour of OUTPUT_VARIABLE, RUN_OUTPUT_VARIABLE and COMPILE_OUTPUT_VARIABLE. Alex --- Modules/CheckCSourceRuns.cmake | 4 +- Modules/CheckCXXSourceRuns.cmake | 5 +- Modules/FindThreads.cmake | 2 +- Modules/TestBigEndian.cmake | 2 +- Source/cmTryRunCommand.cxx | 283 ++++++++++++++++++++++++----- Source/cmTryRunCommand.h | 61 +++++-- Tests/TryCompile/CMakeLists.txt | 29 ++- Tests/TryCompile/exit_success.c | 3 + Tests/TryCompile/exit_with_error.c | 3 + 9 files changed, 329 insertions(+), 63 deletions(-) diff --git a/Modules/CheckCSourceRuns.cmake b/Modules/CheckCSourceRuns.cmake index d843e9a8a..555b352b8 100644 --- a/Modules/CheckCSourceRuns.cmake +++ b/Modules/CheckCSourceRuns.cmake @@ -2,7 +2,7 @@ # CHECK_C_SOURCE_RUNS(SOURCE VAR) # - macro which checks if the source code runs # SOURCE - source code to try to compile -# VAR - variable to store size if the type exists. +# VAR - variable to store the result, 1 for success, empty for failure # # The following variables may be set before calling this macro to # modify the way the check is run: @@ -39,7 +39,7 @@ MACRO(CHECK_C_SOURCE_RUNS SOURCE VAR) CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_FUNCTION_DEFINITIONS} "${CHECK_C_SOURCE_COMPILES_ADD_LIBRARIES}" "${CHECK_C_SOURCE_COMPILES_ADD_INCLUDES}" - OUTPUT_VARIABLE OUTPUT) + COMPILE_OUTPUT_VARIABLE OUTPUT) # if it did not compile make the return value fail code of 1 IF(NOT ${VAR}_COMPILED) SET(${VAR} 1) diff --git a/Modules/CheckCXXSourceRuns.cmake b/Modules/CheckCXXSourceRuns.cmake index 04ae7a964..f117977d0 100644 --- a/Modules/CheckCXXSourceRuns.cmake +++ b/Modules/CheckCXXSourceRuns.cmake @@ -2,7 +2,7 @@ # CHECK_CXX_SOURCE_RUNS(SOURCE VAR) # - macro which checks if the source code compiles # SOURCE - source code to try to compile -# VAR - variable to store size if the type exists. +# VAR - variable to store the result, 1 for success, empty for failure # # The following variables may be set before calling this macro to # modify the way the check is run: @@ -39,7 +39,8 @@ MACRO(CHECK_CXX_SOURCE_RUNS SOURCE VAR) CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_FUNCTION_DEFINITIONS} "${CHECK_CXX_SOURCE_COMPILES_ADD_LIBRARIES}" "${CHECK_CXX_SOURCE_COMPILES_ADD_INCLUDES}" - OUTPUT_VARIABLE OUTPUT) + COMPILE_OUTPUT_VARIABLE OUTPUT) + # if it did not compile make the return value fail code of 1 IF(NOT ${VAR}_COMPILED) SET(${VAR} 1) diff --git a/Modules/FindThreads.cmake b/Modules/FindThreads.cmake index 74405d6c5..f09fd5c87 100644 --- a/Modules/FindThreads.cmake +++ b/Modules/FindThreads.cmake @@ -56,7 +56,7 @@ ELSE(CMAKE_HAVE_SPROC_H) ${CMAKE_BINARY_DIR} ${CMAKE_ROOT}/Modules/CheckForPthreads.c CMAKE_FLAGS -DLINK_LIBRARIES:STRING=-pthread - OUTPUT_VARIABLE OUTPUT) + COMPILE_OUTPUT_VARIABLE OUTPUT) IF(THREADS_HAVE_PTHREAD_ARG) IF(THREADS_PTHREAD_ARG MATCHES "^2$") MESSAGE(STATUS "Check if compiler accepts -pthread - yes") diff --git a/Modules/TestBigEndian.cmake b/Modules/TestBigEndian.cmake index b1a65bf26..2d1853cc2 100644 --- a/Modules/TestBigEndian.cmake +++ b/Modules/TestBigEndian.cmake @@ -11,7 +11,7 @@ MACRO(TEST_BIG_ENDIAN VARIABLE) TRY_RUN(${VARIABLE} HAVE_${VARIABLE} ${CMAKE_BINARY_DIR} ${CMAKE_ROOT}/Modules/TestBigEndian.c - OUTPUT_VARIABLE OUTPUT) + COMPILE_OUTPUT_VARIABLE OUTPUT) IF("${VARIABLE}" STREQUAL "FAILED_TO_RUN") MESSAGE(SEND_ERROR "TestBigEndian Failed to run with output: ${OUTPUT}") ENDIF("${VARIABLE}" STREQUAL "FAILED_TO_RUN") diff --git a/Source/cmTryRunCommand.cxx b/Source/cmTryRunCommand.cxx index 54e7a2da0..1781e9c00 100644 --- a/Source/cmTryRunCommand.cxx +++ b/Source/cmTryRunCommand.cxx @@ -25,17 +25,16 @@ bool cmTryRunCommand::InitialPass(std::vector const& argv) { return false; } - - if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING")) - { - this->SetError("doesn't work when crosscompiling."); - cmSystemTools::SetFatalErrorOccured(); - return false; - } // build an arg list for TryCompile and extract the runArgs std::vector tryCompile; - std::string outputVariable; + + this->CompileResultVariable = ""; + this->RunResultVariable = ""; + this->OutputVariable = ""; + this->RunOutputVariable = ""; + this->CompileOutputVariable = ""; + std::string runArgs; unsigned int i; for (i = 1; i < argv.size(); ++i) @@ -57,19 +56,79 @@ bool cmTryRunCommand::InitialPass(std::vector const& argv) } else { - tryCompile.push_back(argv[i]); if (argv[i] == "OUTPUT_VARIABLE") - { + { if ( argv.size() <= (i+1) ) { cmSystemTools::Error( "OUTPUT_VARIABLE specified but there is no variable"); return false; } - outputVariable = argv[i+1]; + i++; + this->OutputVariable = argv[i]; + } + else if (argv[i] == "RUN_OUTPUT_VARIABLE") + { + if (argv.size() <= (i + 1)) + { + cmSystemTools::Error( + "RUN_OUTPUT_VARIABLE specified but there is no variable"); + return false; + } + i++; + this->RunOutputVariable = argv[i]; + } + else if (argv[i] == "COMPILE_OUTPUT_VARIABLE") + { + if (argv.size() <= (i + 1)) + { + cmSystemTools::Error( + "COMPILE_OUTPUT_VARIABLE specified but there is no variable"); + return false; + } + i++; + this->CompileOutputVariable = argv[i]; + } + else + { + tryCompile.push_back(argv[i]); } } } + + // although they could be used together, don't allow it, because + // using OUTPUT_VARIABLE makes crosscompiling harder + if (this->OutputVariable.size() + && ((this->RunOutputVariable.size()) + || (this->CompileOutputVariable.size()))) + { + 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."); + return false; + } + + bool captureRunOutput = false; + if (this->OutputVariable.size()) + { + captureRunOutput = true; + tryCompile.push_back("OUTPUT_VARIABLE"); + tryCompile.push_back(this->OutputVariable); + } + if (this->CompileOutputVariable.size()) + { + tryCompile.push_back("OUTPUT_VARIABLE"); + tryCompile.push_back(this->CompileOutputVariable); + } + if (this->RunOutputVariable.size()) + { + captureRunOutput = true; + } + + this->RunResultVariable = argv[0]; + this->CompileResultVariable = argv[1]; + // do the try compile int res = this->TryCompileCode(tryCompile); @@ -82,46 +141,42 @@ bool cmTryRunCommand::InitialPass(std::vector const& argv) } else { - int retVal = -1; - std::string output; - std::string finalCommand = cmSystemTools::ConvertToRunCommandPath( - this->OutputFile.c_str()); - if(runArgs.size()) + // "run" it and capture the output + std::string runOutputContents; + if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING")) { - finalCommand += runArgs; + this->DoNotRunExecutable(runArgs, + argv[3], + captureRunOutput ? &runOutputContents : 0); } - int timeout = 0; - bool worked = cmSystemTools::RunSingleCommand(finalCommand.c_str(), - &output, &retVal, - 0, false, timeout); - if(outputVariable.size()) + else + { + this->RunExecutable(runArgs, &runOutputContents); + } + + // now put the output into the variables + if(this->RunOutputVariable.size()) + { + this->Makefile->AddDefinition(this->RunOutputVariable.c_str(), + runOutputContents.c_str()); + } + + if(this->OutputVariable.size()) { // if the TryCompileCore saved output in this outputVariable then // prepend that output to this output const char* compileOutput - = this->Makefile->GetDefinition(outputVariable.c_str()); - if(compileOutput) + = this->Makefile->GetDefinition(this->OutputVariable.c_str()); + if (compileOutput) { - output = std::string(compileOutput) + output; + runOutputContents = std::string(compileOutput) + runOutputContents; } - this->Makefile->AddDefinition(outputVariable.c_str(), output.c_str()); + this->Makefile->AddDefinition(this->OutputVariable.c_str(), + runOutputContents.c_str()); } - // set the run var - char retChar[1000]; - if(worked) - { - sprintf(retChar,"%i",retVal); - } - else - { - strcpy(retChar, "FAILED_TO_RUN"); - } - this->Makefile->AddCacheDefinition(argv[0].c_str(), retChar, - "Result of TRY_RUN", - cmCacheManager::INTERNAL); } - } - + } + // if we created a directory etc, then cleanup after ourselves if(!this->Makefile->GetCMakeInstance()->GetDebugTryCompile()) { @@ -129,3 +184,149 @@ bool cmTryRunCommand::InitialPass(std::vector const& argv) } return true; } + +void cmTryRunCommand::RunExecutable(const std::string& runArgs, + std::string* out) +{ + int retVal = -1; + std::string finalCommand = cmSystemTools::ConvertToRunCommandPath( + this->OutputFile.c_str()); + if (runArgs.size()) + { + finalCommand += runArgs; + } + int timeout = 0; + bool worked = cmSystemTools::RunSingleCommand(finalCommand.c_str(), + out, &retVal, + 0, false, timeout); + // set the run var + char retChar[1000]; + if (worked) + { + sprintf(retChar, "%i", retVal); + } + else + { + strcpy(retChar, "FAILED_TO_RUN"); + } + this->Makefile->AddCacheDefinition(this->RunResultVariable.c_str(), retChar, + "Result of TRY_RUN", + cmCacheManager::INTERNAL); +} + +/* This is only used when cross compiling. Instead of running the + executable, two cache variables are created which will hold the results + the executable would have produced. +*/ +void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs, + const std::string& srcFile, + std::string* out + ) +{ + + std::string internalRunOutputName = this->RunResultVariable+"__" + +this->CompileResultVariable+"__TRYRUN_OUTPUT"; + bool error = false; + std::string info = "Source file: "; + info += srcFile + "\n"; + if (runArgs.size()) + { + info += "Run arguments: "; + info += runArgs; + info += "\n"; + } + info += "Current CMake stack: " + this->Makefile->GetListFileStack(); + + if (this->Makefile->GetDefinition(this->RunResultVariable.c_str()) == 0) + { + // if the variables doesn't exist, create it with a helpful error text + // and mark it as advanced + std::string comment; + comment += "Run result of TRY_RUN().\n" + "This variable should indicate whether the executable would " + "have been able to run if it was executed on its target " + "platform.\n" + "If it would have been able to run, enter the exit code here " + "(in many cases 0 for success). If not, enter " + "\"FAILED_TO_RUN\" here."; + if (out!=0) + { + comment += "If it was able to run, also check the variable "; + comment += internalRunOutputName; + comment += " and set it appropriately."; + } + comment += "\n"; + comment += info; + this->Makefile->AddCacheDefinition(this->RunResultVariable.c_str(), + "PLEASE_FILL_OUT-FAILED_TO_RUN", + comment.c_str(), + cmCacheManager::STRING); + + cmCacheManager::CacheIterator it = this->Makefile->GetCacheManager()-> + GetCacheIterator(this->RunResultVariable.c_str()); + if ( !it.IsAtEnd() ) + { + it.SetProperty("ADVANCED", "1"); + } + + error = true; + } + + if (out!=0) + { + if (this->Makefile->GetDefinition(internalRunOutputName.c_str()) == 0) + { + // if the variables doesn't exist, create it with a helpful error text + // and mark it as advanced + std::string comment; + comment += "Output of TRY_RUN().\n" + "This variable should contain the text, which the executable " + "run by TRY_RUN() would have printed on stdout and stderr, " + "if it was executed on its target platform.\n" + "The accompanying variable "; + comment += this->RunResultVariable; + comment += " indicates whether the executable would have been able to " + "run and its exit code." + "If the executable would not have been able to run, set "; + comment += internalRunOutputName; + comment += " empty. Otherwise check if the output is evaluated by the " + "calling CMake code. If this is the case, check the source " + "file what it would have printed if called with the given " + "arguments.\n"; + comment += info; + + this->Makefile->AddCacheDefinition(internalRunOutputName.c_str(), + "PLEASE_FILL_OUT-NOTFOUND", + comment.c_str(), + cmCacheManager::STRING); + cmCacheManager::CacheIterator it = this->Makefile->GetCacheManager()-> + GetCacheIterator(internalRunOutputName.c_str()); + if ( !it.IsAtEnd() ) + { + it.SetProperty("ADVANCED", "1"); + } + + error = true; + } + } + + if (error) + { + std::string errorMessage = "TRY_RUN() invoked in cross-compiling mode, " + "please set the following cache variables " + "appropriatly:\n"; + errorMessage += " " + this->RunResultVariable + " (advanced)\n"; + if (out!=0) + { + errorMessage += " " + internalRunOutputName + " (advanced)\n"; + } + errorMessage += info; + cmSystemTools::Error(errorMessage.c_str()); + return; + } + + if (out!=0) + { + (*out) = this->Makefile->GetDefinition(internalRunOutputName.c_str()); + } +} diff --git a/Source/cmTryRunCommand.h b/Source/cmTryRunCommand.h index b11a7c46a..8c4788c20 100644 --- a/Source/cmTryRunCommand.h +++ b/Source/cmTryRunCommand.h @@ -61,22 +61,55 @@ public: { return " TRY_RUN(RUN_RESULT_VAR COMPILE_RESULT_VAR\n" - " bindir srcfile >\n" - " >\n" - " \n" - " ...>)\n" - "Try compiling a srcfile. Return the success or failure in " - "COMPILE_RESULT_VAR. Then if the compile succeeded, run the " - "executable and return the result in RUN_RESULT_VAR. " - "If the executable was built, but failed to run for some" - "reason, then RUN_RESULT_VAR will be set to FAILED_TO_RUN, and " - "the output will be in the COMPILE_RESULT_VAR. OUTPUT_VARIABLE " - "specifies the name of the variable to put all of the standard " - "output and standard error into."; + " bindir srcfile [CMAKE_FLAGS ]\n" + " [COMPILE_DEFINITIONS ]\n" + " [COMPILE_OUTPUT_VARIABLE comp]\n" + " [RUN_OUTPUT_VARIABLE run]\n" + " [OUTPUT_VARIABLE var]\n" + " [ARGS ...])\n" + "Try compiling a srcfile. Return TRUE or FALSE for success or failure " + "in COMPILE_RESULT_VAR. Then if the compile succeeded, run the " + "executable and return its exit code in RUN_RESULT_VAR. " + "If the executable was built, but failed to run, then RUN_RESULT_VAR " + "will be set to FAILED_TO_RUN. " + "COMPILE_OUTPUT_VARIABLE specifies the variable where the output from " + "the compile step goes. RUN_OUTPUT_VARIABLE specifies the variable " + "where the output from the running executable goes.\n" + "For compatibility reasons OUTPUT_VARIABLE is still supported, which " + "gives you the output from the compile and run step combined.\n\n" + "Cross compiling issues\n" + "When cross compiling, the executable compiled in the first step " + "usually cannot be run on the build host. TRY_RUN() checks the " + "CMAKE_CROSSCOMPILING variable to detect whether CMake is in " + "crosscompiling mode. If that's the case, it will still try to compile " + "the executable, but it will not try to run the executable. Instead it " + "will create cache variables which must be filled by the user or by " + "presetting them in some CMake script file to the values the " + "executable would have produced if it would have been run on its actual " + "target platform. These variables are RUN_RESULT_VAR (explanation see " + "above) and if RUN_OUTPUT_VARIABLE (or OUTPUT_VARIABLE) was used, an " + "additional cache variable " + "RUN_RESULT_VAR__COMPILE_RESULT_VAR__TRYRUN_OUTPUT." + "This is intended to hold stdout and stderr from the executable.\n" + "In order to make cross compiling your project easier, use TRY_RUN " + "only if really required. If you use TRY_RUN, use RUN_OUTPUT_VARIABLE " + "(or OUTPUT_VARIABLE) only if really required. Using them will require " + "that when crosscompiling, the cache variables will have to be set " + "manually to the output of the executable. You can also \"guard\" the " + "calls to TRY_RUN with IF(CMAKE_CROSSCOMPILING) and provide an " + "easy-to-preset alternative for this case.\n"; } - - cmTypeMacro(cmTryRunCommand, cmCoreTryCompile); + cmTypeMacro(cmTryRunCommand, cmCoreTryCompile); +private: + void RunExecutable(const std::string& runArgs, std::string* runOutputContents); + void DoNotRunExecutable(const std::string& runArgs, const std::string& srcFile, std::string* runOutputContents); + + std::string CompileResultVariable; + std::string RunResultVariable; + std::string OutputVariable; + std::string RunOutputVariable; + std::string CompileOutputVariable; }; diff --git a/Tests/TryCompile/CMakeLists.txt b/Tests/TryCompile/CMakeLists.txt index 02359fe5e..a39f7c902 100644 --- a/Tests/TryCompile/CMakeLists.txt +++ b/Tests/TryCompile/CMakeLists.txt @@ -87,6 +87,8 @@ ADD_EXECUTABLE(TryCompile pass.c) # now two tests for TRY_RUN # try to run a file that should compile and run without error +# also check that OUTPUT_VARIABLE contains both the compile output +# and the run output TRY_RUN(SHOULD_RUN SHOULD_COMPILE ${TryCompile_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp ${TryCompile_SOURCE_DIR}/exit_success.c @@ -97,16 +99,39 @@ ENDIF(NOT SHOULD_COMPILE) IF(NOT "${SHOULD_RUN}" STREQUAL "0") MESSAGE(SEND_ERROR "exit_success failed running with exit code ${SHOULD_RUN}") ENDIF(NOT "${SHOULD_RUN}" STREQUAL "0") +# check the compile output for the filename +IF(NOT "${TRY_OUT}" MATCHES "exit_success") + MESSAGE(SEND_ERROR " TRY_OUT didn't contain \"exit_success\": \"${TRY_OUT}\"") +ENDIF(NOT "${TRY_OUT}" MATCHES "exit_success") +# check the run output +IF(NOT "${TRY_OUT}" MATCHES "hello world") + MESSAGE(SEND_ERROR " TRY_OUT didn't contain \"hello world\": \"${TRY_OUT}\"") +ENDIF(NOT "${TRY_OUT}" MATCHES "hello world") + # try to run a file that should compile and run, but return an error TRY_RUN(SHOULD_EXIT_WITH_ERROR SHOULD_COMPILE ${TryCompile_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp ${TryCompile_SOURCE_DIR}/exit_with_error.c - OUTPUT_VARIABLE TRY_OUT) + COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT + RUN_OUTPUT_VARIABLE RUN_OUTPUT) + IF(NOT SHOULD_COMPILE) - MESSAGE(STATUS " exit_with_error failed compiling: ${TRY_OUT}") + MESSAGE(STATUS " exit_with_error failed compiling: ${COMPILE_OUTPUT}") ENDIF(NOT SHOULD_COMPILE) IF("${SHOULD_EXIT_WITH_ERROR}" STREQUAL "0") MESSAGE(SEND_ERROR " exit_with_error passed with exit code ${SHOULD_EXIT_WITH_ERROR}") ENDIF("${SHOULD_EXIT_WITH_ERROR}" STREQUAL "0") +# check the compile output, it should contain the filename +IF(NOT "${COMPILE_OUTPUT}" MATCHES "exit_with_error") + MESSAGE(SEND_ERROR " COMPILE_OUT didn't contain \"exit_with_error\": \"${COMPILE_OUTPUT}\"") +ENDIF(NOT "${COMPILE_OUTPUT}" MATCHES "exit_with_error") +#... but not the run time output +IF("${COMPILE_OUTPUT}" MATCHES "hello world") + MESSAGE(SEND_ERROR " COMPILE_OUT contains the run output: \"${COMPILE_OUTPUT}\"") +ENDIF("${COMPILE_OUTPUT}" MATCHES "hello world") +# check the run output, it should stdout +IF(NOT "${RUN_OUTPUT}" MATCHES "hello world") + MESSAGE(SEND_ERROR " RUN_OUTPUT didn't contain \"hello world\": \"${RUN_OUTPUT}\"") +ENDIF(NOT "${RUN_OUTPUT}" MATCHES "hello world") diff --git a/Tests/TryCompile/exit_success.c b/Tests/TryCompile/exit_success.c index f8b643afb..82f5b5f82 100644 --- a/Tests/TryCompile/exit_success.c +++ b/Tests/TryCompile/exit_success.c @@ -1,4 +1,7 @@ +#include + int main() { + printf("hello world\n"); return 0; } diff --git a/Tests/TryCompile/exit_with_error.c b/Tests/TryCompile/exit_with_error.c index a9a283ddd..f3c523d37 100644 --- a/Tests/TryCompile/exit_with_error.c +++ b/Tests/TryCompile/exit_with_error.c @@ -1,4 +1,7 @@ +#include + int main() { + printf("hello world\n"); return -1; }