From 0b38bb4c535ae972d7f973e3e69945a6d0c14d75 Mon Sep 17 00:00:00 2001 From: David Cole Date: Fri, 4 Dec 2009 12:09:01 -0500 Subject: [PATCH] Fix issue #2336 - honor the -C arg to ctest. Honor it for all stages of running -D dashboards from the command line and running ctest_configure, ctest_build and ctest_test commands in -S scripts. Also, allow a script to change it by setting the CTEST_CONFIGURATION_TYPE variable: allows for multiple configuration build/test cycles within one script. Add a new signature for the cmake command build_command that accepts CONFIGURATION as one argument. The original build_command signature is still there, but now marked as deprecated in the documentation. Of course... also add CTestConfig tests to verify that -C is honored for -D dashboards and -S scripts. --- Modules/CTest.cmake | 17 ++- Modules/DartConfiguration.tcl.in | 1 + Source/CTest/cmCTestBuildCommand.cxx | 16 ++- Source/CTest/cmCTestBuildHandler.cxx | 36 ++++-- Source/CTest/cmCTestBuildHandler.h | 3 + Source/CTest/cmCTestConfigureCommand.cxx | 35 +++++- Source/CTest/cmCTestHandlerCommand.cxx | 11 ++ Source/CTest/cmCTestTestCommand.cxx | 2 +- Source/cmBuildCommand.cxx | 117 +++++++++++++++++- Source/cmBuildCommand.h | 49 ++++++-- Source/cmGlobalGenerator.h | 4 + Source/cmGlobalVisualStudioGenerator.h | 5 + Source/cmGlobalXCodeGenerator.cxx | 15 +++ Source/cmGlobalXCodeGenerator.h | 5 + .../build_command/CMakeLists.txt | 58 +++++++++ .../build_command/RunCMake.cmake | 86 +++++++++++++ Tests/CMakeLists.txt | 52 +++++++- Tests/CTestConfig/CMakeLists.txt | 47 +++++++ Tests/CTestConfig/CTestConfig.cxx | 20 +++ Tests/CTestConfig/dashboard.cmake.in | 43 +++++++ Tests/CTestConfig/script.cmake.in | 21 ++++ 21 files changed, 604 insertions(+), 39 deletions(-) create mode 100644 Tests/CMakeCommands/build_command/CMakeLists.txt create mode 100644 Tests/CMakeCommands/build_command/RunCMake.cmake create mode 100644 Tests/CTestConfig/CMakeLists.txt create mode 100644 Tests/CTestConfig/CTestConfig.cxx create mode 100644 Tests/CTestConfig/dashboard.cmake.in create mode 100644 Tests/CTestConfig/script.cmake.in diff --git a/Modules/CTest.cmake b/Modules/CTest.cmake index aaa604064..fb430a74d 100644 --- a/Modules/CTest.cmake +++ b/Modules/CTest.cmake @@ -84,10 +84,6 @@ IF(BUILD_TESTING) ENDIF(EXISTS "${PROJECT_SOURCE_DIR}/DartConfig.cmake") SET_IF_NOT_SET (NIGHTLY_START_TIME "00:00:00 EDT") - # make program just needs to use CMAKE_MAKE_PROGRAM which is required - # to be defined by cmake - SET(MAKEPROGRAM ${CMAKE_MAKE_PROGRAM}) - FIND_PROGRAM(CVSCOMMAND cvs ) SET(CVS_UPDATE_OPTIONS "-d -A -P" CACHE STRING "Options passed to the cvs update command.") @@ -202,8 +198,17 @@ IF(BUILD_TESTING) ENDIF(DART_CXX_NAME MATCHES "devenv") SET(BUILDNAME "${BUILD_NAME_SYSTEM_NAME}-${DART_CXX_NAME}") ENDIF(NOT BUILDNAME) - # set the build command - BUILD_COMMAND(MAKECOMMAND ${MAKEPROGRAM} ) + + # the build command + BUILD_COMMAND(MAKECOMMAND CONFIGURATION "\${CTEST_CONFIGURATION_TYPE}") + SET(MAKECOMMAND ${MAKECOMMAND} CACHE STRING "Command to build the project") + + # the default build configuration the ctest build handler will use + # if there is no -C arg given to ctest: + SET(DEFAULT_CTEST_CONFIGURATION_TYPE "$ENV{CMAKE_CONFIG_TYPE}") + IF(DEFAULT_CTEST_CONFIGURATION_TYPE STREQUAL "") + SET(DEFAULT_CTEST_CONFIGURATION_TYPE "Release") + ENDIF(DEFAULT_CTEST_CONFIGURATION_TYPE STREQUAL "") IF(NOT "${CMAKE_GENERATOR}" MATCHES "Make") SET(CTEST_USE_LAUNCHERS 0) diff --git a/Modules/DartConfiguration.tcl.in b/Modules/DartConfiguration.tcl.in index 2eea45cd0..00e8af68f 100644 --- a/Modules/DartConfiguration.tcl.in +++ b/Modules/DartConfiguration.tcl.in @@ -30,6 +30,7 @@ NightlyStartTime: @NIGHTLY_START_TIME@ # Commands for the build/test/submit cycle ConfigureCommand: "@CMAKE_COMMAND@" "@PROJECT_SOURCE_DIR@" MakeCommand: @MAKECOMMAND@ +DefaultCTestConfigurationType: @DEFAULT_CTEST_CONFIGURATION_TYPE@ # CVS options # Default is "-d -P -A" diff --git a/Source/CTest/cmCTestBuildCommand.cxx b/Source/CTest/cmCTestBuildCommand.cxx index af21e0de0..7a4d877c1 100644 --- a/Source/CTest/cmCTestBuildCommand.cxx +++ b/Source/CTest/cmCTestBuildCommand.cxx @@ -53,6 +53,7 @@ cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler() return 0; } this->Handler = (cmCTestBuildHandler*)handler; + const char* ctestBuildCommand = this->Makefile->GetDefinition("CTEST_BUILD_COMMAND"); if ( ctestBuildCommand && *ctestBuildCommand ) @@ -67,10 +68,21 @@ cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler() = (this->Values[ctb_PROJECT_NAME] && *this->Values[ctb_PROJECT_NAME]) ? this->Values[ctb_PROJECT_NAME] : this->Makefile->GetDefinition("CTEST_PROJECT_NAME"); + + // Build configuration is determined by: CONFIGURATION argument, + // or CTEST_BUILD_CONFIGURATION script variable, or + // CTEST_CONFIGURATION_TYPE script variable, or ctest -C command + // line argument... in that order. + // + const char* ctestBuildConfiguration + = this->Makefile->GetDefinition("CTEST_BUILD_CONFIGURATION"); const char* cmakeBuildConfiguration = (this->Values[ctb_CONFIGURATION] && *this->Values[ctb_CONFIGURATION]) ? this->Values[ctb_CONFIGURATION] - : this->Makefile->GetDefinition("CTEST_BUILD_CONFIGURATION"); + : ((ctestBuildConfiguration && *ctestBuildConfiguration) + ? ctestBuildConfiguration + : this->CTest->GetConfigType().c_str()); + const char* cmakeBuildAdditionalFlags = (this->Values[ctb_FLAGS] && *this->Values[ctb_FLAGS]) ? this->Values[ctb_FLAGS] @@ -117,7 +129,7 @@ cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler() } cmakeBuildConfiguration = config; } - + std::string buildCommand = this->GlobalGenerator-> GenerateBuildCommand(cmakeMakeProgram, diff --git a/Source/CTest/cmCTestBuildHandler.cxx b/Source/CTest/cmCTestBuildHandler.cxx index 0095bbccb..a125459a0 100644 --- a/Source/CTest/cmCTestBuildHandler.cxx +++ b/Source/CTest/cmCTestBuildHandler.cxx @@ -264,6 +264,32 @@ void cmCTestBuildHandler::PopulateCustomVectors(cmMakefile *mf) } } +//---------------------------------------------------------------------- +std::string cmCTestBuildHandler::GetMakeCommand() +{ + std::string makeCommand + = this->CTest->GetCTestConfiguration("MakeCommand"); + cmCTestLog(this->CTest, + HANDLER_VERBOSE_OUTPUT, "MakeCommand:" << makeCommand << + "\n"); + + std::string configType = this->CTest->GetConfigType(); + if (configType == "") + { + configType + = this->CTest->GetCTestConfiguration("DefaultCTestConfigurationType"); + } + if (configType == "") + { + configType = "Release"; + } + + cmSystemTools::ReplaceString(makeCommand, + "${CTEST_CONFIGURATION_TYPE}", configType.c_str()); + + return makeCommand; +} + //---------------------------------------------------------------------- //clearly it would be nice if this were broken up into a few smaller //functions and commented... @@ -300,11 +326,7 @@ int cmCTestBuildHandler::ProcessHandler() } // Determine build command and build directory - const std::string &makeCommand - = this->CTest->GetCTestConfiguration("MakeCommand"); - cmCTestLog(this->CTest, - HANDLER_VERBOSE_OUTPUT, "MakeCommand:" << makeCommand << - "\n"); + std::string makeCommand = this->GetMakeCommand(); if ( makeCommand.size() == 0 ) { cmCTestLog(this->CTest, ERROR_MESSAGE, @@ -312,6 +334,7 @@ int cmCTestBuildHandler::ProcessHandler() << std::endl); return -1; } + const std::string &buildDirectory = this->CTest->GetCTestConfiguration("BuildDirectory"); if ( buildDirectory.size() == 0 ) @@ -519,8 +542,7 @@ void cmCTestBuildHandler::GenerateXMLHeader(std::ostream& os) static_cast(this->StartBuildTime) << "\n" << "" - << cmXMLSafe( - this->CTest->GetCTestConfiguration("MakeCommand")) + << cmXMLSafe(this->GetMakeCommand()) << "" << std::endl; } diff --git a/Source/CTest/cmCTestBuildHandler.h b/Source/CTest/cmCTestBuildHandler.h index 7ee50be0b..439efd604 100644 --- a/Source/CTest/cmCTestBuildHandler.h +++ b/Source/CTest/cmCTestBuildHandler.h @@ -46,7 +46,10 @@ public: int GetTotalErrors() { return this->TotalErrors;} int GetTotalWarnings() { return this->TotalWarnings;} + private: + std::string GetMakeCommand(); + //! Run command specialized for make and configure. Returns process status // and retVal is return value or exception. int RunMakeCommand(const char* command, diff --git a/Source/CTest/cmCTestConfigureCommand.cxx b/Source/CTest/cmCTestConfigureCommand.cxx index 00d9ec564..1ad4e24b9 100644 --- a/Source/CTest/cmCTestConfigureCommand.cxx +++ b/Source/CTest/cmCTestConfigureCommand.cxx @@ -11,6 +11,7 @@ ============================================================================*/ #include "cmCTestConfigureCommand.h" +#include "cmGlobalGenerator.h" #include "cmCTest.h" #include "cmCTestGenericHandler.h" @@ -66,6 +67,7 @@ cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler() const char* ctestConfigureCommand = this->Makefile->GetDefinition("CTEST_CONFIGURE_COMMAND"); + if ( ctestConfigureCommand && *ctestConfigureCommand ) { this->CTest->SetCTestConfiguration("ConfigureCommand", @@ -86,6 +88,19 @@ cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler() "variable"); return 0; } + + bool multiConfig = false; + bool cmakeBuildTypeInOptions = false; + + cmGlobalGenerator *gg = + this->Makefile->GetCMakeInstance()->CreateGlobalGenerator( + cmakeGeneratorName); + if(gg) + { + multiConfig = gg->IsMultiConfig(); + delete gg; + } + std::string cmakeConfigureCommand = "\""; cmakeConfigureCommand += this->CTest->GetCMakeExecutable(); cmakeConfigureCommand += "\""; @@ -95,9 +110,23 @@ cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler() for (it= options.begin(); it!=options.end(); ++it) { option = *it; + cmakeConfigureCommand += " \""; cmakeConfigureCommand += option; cmakeConfigureCommand += "\""; + + if ((0 != strstr(option.c_str(), "CMAKE_BUILD_TYPE=")) || + (0 != strstr(option.c_str(), "CMAKE_BUILD_TYPE:STRING="))) + { + cmakeBuildTypeInOptions = true; + } + } + + if (!multiConfig && !cmakeBuildTypeInOptions) + { + cmakeConfigureCommand += " \"-DCMAKE_BUILD_TYPE:STRING="; + cmakeConfigureCommand += this->CTest->GetConfigType(); + cmakeConfigureCommand += "\""; } cmakeConfigureCommand += " \"-G"; @@ -113,9 +142,9 @@ cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler() } else { - this->SetError("Configure command is not specified. If this is a CMake " - "project, specify CTEST_CMAKE_GENERATOR, or if this is not CMake " - "project, specify CTEST_CONFIGURE_COMMAND."); + this->SetError("Configure command is not specified. If this is a " + "\"built with CMake\" project, set CTEST_CMAKE_GENERATOR. If not, " + "set CTEST_CONFIGURE_COMMAND."); return 0; } } diff --git a/Source/CTest/cmCTestHandlerCommand.cxx b/Source/CTest/cmCTestHandlerCommand.cxx index 1c9f08061..1957e04e3 100644 --- a/Source/CTest/cmCTestHandlerCommand.cxx +++ b/Source/CTest/cmCTestHandlerCommand.cxx @@ -59,6 +59,17 @@ bool cmCTestHandlerCommand } } + // Set the config type of this ctest to the current value of the + // CTEST_CONFIGURATION_TYPE script variable if it is defined. + // The current script value trumps the -C argument on the command + // line. + const char* ctestConfigType = + this->Makefile->GetDefinition("CTEST_CONFIGURATION_TYPE"); + if (ctestConfigType) + { + this->CTest->SetConfigType(ctestConfigType); + } + cmCTestLog(this->CTest, DEBUG, "Initialize handler" << std::endl;); cmCTestGenericHandler* handler = this->InitializeHandler(); if ( !handler ) diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx index 23cc20e23..b0adf224f 100644 --- a/Source/CTest/cmCTestTestCommand.cxx +++ b/Source/CTest/cmCTestTestCommand.cxx @@ -33,6 +33,7 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler() { const char* ctestTimeout = this->Makefile->GetDefinition("CTEST_TEST_TIMEOUT"); + double timeout = this->CTest->GetTimeOut(); if ( ctestTimeout ) { @@ -104,4 +105,3 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeActualHandler() { return this->CTest->GetInitializedHandler("test"); } - diff --git a/Source/cmBuildCommand.cxx b/Source/cmBuildCommand.cxx index b9ce5612a..3722ab635 100644 --- a/Source/cmBuildCommand.cxx +++ b/Source/cmBuildCommand.cxx @@ -14,25 +14,135 @@ #include "cmLocalGenerator.h" #include "cmGlobalGenerator.h" -// cmBuildCommand +//---------------------------------------------------------------------- bool cmBuildCommand ::InitialPass(std::vector const& args, cmExecutionStatus &) { - if(args.size() < 2 ) + // Support the legacy signature of the command: + // + if(2 == args.size()) { - this->SetError("called with incorrect number of arguments"); + return this->TwoArgsSignature(args); + } + + return this->MainSignature(args); +} + +//---------------------------------------------------------------------- +bool cmBuildCommand +::MainSignature(std::vector const& args) +{ + if(args.size() < 1) + { + this->SetError("requires at least one argument naming a CMake variable"); return false; } + + // The cmake variable in which to store the result. + const char* variable = args[0].c_str(); + + // Parse remaining arguments. + const char* configuration = 0; + const char* project_name = 0; + const char* target = 0; + enum Doing { DoingNone, DoingConfiguration, DoingProjectName, DoingTarget }; + Doing doing = DoingNone; + for(unsigned int i=1; i < args.size(); ++i) + { + if(args[i] == "CONFIGURATION") + { + doing = DoingConfiguration; + } + else if(args[i] == "PROJECT_NAME") + { + doing = DoingProjectName; + } + else if(args[i] == "TARGET") + { + doing = DoingTarget; + } + else if(doing == DoingConfiguration) + { + doing = DoingNone; + configuration = args[i].c_str(); + } + else if(doing == DoingProjectName) + { + doing = DoingNone; + project_name = args[i].c_str(); + } + else if(doing == DoingTarget) + { + doing = DoingNone; + target = args[i].c_str(); + } + else + { + cmOStringStream e; + e << "unknown argument \"" << args[i] << "\""; + this->SetError(e.str().c_str()); + return false; + } + } + + const char* makeprogram + = this->Makefile->GetDefinition("CMAKE_MAKE_PROGRAM"); + + // If null/empty CONFIGURATION argument, GenerateBuildCommand uses 'Debug' + // in the currently implemented multi-configuration global generators... + // so we put this code here to end up with the same default configuration + // as the original 2-arg build_command signature: + // + if(!configuration || !*configuration) + { + configuration = getenv("CMAKE_CONFIG_TYPE"); + } + if(!configuration || !*configuration) + { + configuration = "Release"; + } + + // If null/empty PROJECT_NAME argument, use the Makefile's project name: + // + if(!project_name || !*project_name) + { + project_name = this->Makefile->GetProjectName(); + } + + // If null/empty TARGET argument, GenerateBuildCommand omits any mention + // of a target name on the build command line... + // + std::string makecommand = this->Makefile->GetLocalGenerator() + ->GetGlobalGenerator()->GenerateBuildCommand + (makeprogram, project_name, 0, target, configuration, true, false); + + this->Makefile->AddDefinition(variable, makecommand.c_str()); + + return true; +} + +//---------------------------------------------------------------------- +bool cmBuildCommand +::TwoArgsSignature(std::vector const& args) +{ + if(args.size() < 2 ) + { + this->SetError("called with less than two arguments"); + return false; + } + const char* define = args[0].c_str(); const char* cacheValue = this->Makefile->GetDefinition(define); std::string makeprogram = args[1]; + std::string configType = "Release"; const char* cfg = getenv("CMAKE_CONFIG_TYPE"); if ( cfg ) { configType = cfg; } + std::string makecommand = this->Makefile->GetLocalGenerator() ->GetGlobalGenerator()->GenerateBuildCommand (makeprogram.c_str(), this->Makefile->GetProjectName(), 0, @@ -49,4 +159,3 @@ bool cmBuildCommand cmCacheManager::STRING); return true; } - diff --git a/Source/cmBuildCommand.h b/Source/cmBuildCommand.h index 9d494e7dc..703ff882c 100644 --- a/Source/cmBuildCommand.h +++ b/Source/cmBuildCommand.h @@ -37,36 +37,61 @@ public: virtual bool InitialPass(std::vector const& args, cmExecutionStatus &status); + /** + * The primary command signature with optional, KEYWORD-based args. + */ + virtual bool MainSignature(std::vector const& args); + + /** + * Legacy "exactly 2 args required" signature. + */ + virtual bool TwoArgsSignature(std::vector const& args); + /** * The name of the command as specified in CMakeList.txt. */ virtual const char* GetName() {return "build_command";} - + /** * Succinct documentation. */ virtual const char* GetTerseDocumentation() { - return "Get the command line that will build this project."; + return "Get the command line to build this project."; } - + /** * More documentation. */ virtual const char* GetFullDocumentation() { return - " build_command( )\n" - "Sets the given to a string containing the command that " - "will build this project from the root of the build tree using the " - "build tool given by . should be msdev, " - "nmake, make or one of the end user build tools. " - "This is useful for configuring testing systems."; + " build_command(\n" + " [CONFIGURATION ]\n" + " [PROJECT_NAME ]\n" + " [TARGET ])\n" + "Sets the given to a string containing the command line " + "for building one configuration of a target in a project using the " + "build tool appropriate for the current CMAKE_GENERATOR.\n" + "If CONFIGURATION is omitted, CMake chooses a reasonable default " + "value for multi-configuration generators. CONFIGURATION is " + "ignored for single-configuration generators.\n" + "If PROJECT_NAME is omitted, the resulting command line will build " + "the top level PROJECT in the current build tree.\n" + "If TARGET is omitted, the resulting command line will build " + "everything, effectively using build target 'all' or 'ALL_BUILD'.\n" + " build_command( )\n" + "This second signature is deprecated, but still available for " + "backwards compatibility. Use the first signature instead.\n" + "Sets the given to a string containing the command " + "to build this project from the root of the build tree using " + "the build tool given by . should be " + "the full path to msdev, devenv, nmake, make or one of the end " + "user build tools." + ; } - + cmTypeMacro(cmBuildCommand, cmCommand); }; - - #endif diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index c9d07906b..b7b4324be 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -258,6 +258,10 @@ public: /** Supported systems creates a GUID for the given name */ virtual void CreateGUID(const char*) {} + /** Return true if the generated build tree may contain multiple builds. + i.e. "Can I build Debug and Release in the same tree?" */ + virtual bool IsMultiConfig() { return false; } + protected: typedef std::vector GeneratorVector; // for a project collect all its targets by following depend diff --git a/Source/cmGlobalVisualStudioGenerator.h b/Source/cmGlobalVisualStudioGenerator.h index 0c7cf7fed..b0be0871d 100644 --- a/Source/cmGlobalVisualStudioGenerator.h +++ b/Source/cmGlobalVisualStudioGenerator.h @@ -65,6 +65,11 @@ public: /** Get the top-level registry key for this VS version. */ std::string GetRegistryBase(); + + /** Return true if the generated build tree may contain multiple builds. + i.e. "Can I build Debug and Release in the same tree?" */ + virtual bool IsMultiConfig() { return true; } + protected: void FixUtilityDepends(); diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index a409e5f93..05c684895 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -3302,3 +3302,18 @@ cmGlobalXCodeGenerator::ComputeInfoPListLocation(cmTarget& target) plist += ".dir/Info.plist"; return plist; } + +//---------------------------------------------------------------------------- +// Return true if the generated build tree may contain multiple builds. +// i.e. "Can I build Debug and Release in the same tree?" +bool cmGlobalXCodeGenerator::IsMultiConfig() +{ + // Old Xcode 1.5 is single config: + if(this->XcodeVersion == 15) + { + return false; + } + + // Newer Xcode versions are multi config: + return true; +} diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index 02ac1b523..b4de80538 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/Source/cmGlobalXCodeGenerator.h @@ -80,6 +80,11 @@ public: std::vector& dirs); void SetCurrentLocalGenerator(cmLocalGenerator*); + + /** Return true if the generated build tree may contain multiple builds. + i.e. "Can I build Debug and Release in the same tree?" */ + virtual bool IsMultiConfig(); + private: cmXCodeObject* CreateOrGetPBXGroup(cmTarget& cmtarget, cmSourceGroup* sg); diff --git a/Tests/CMakeCommands/build_command/CMakeLists.txt b/Tests/CMakeCommands/build_command/CMakeLists.txt new file mode 100644 index 000000000..990ac90ce --- /dev/null +++ b/Tests/CMakeCommands/build_command/CMakeLists.txt @@ -0,0 +1,58 @@ +# This CMakeLists file is *sometimes expected* to result in a configure error. +# +# expect this to succeed: +# ../bin/Release/cmake -G Xcode +# ../../CMake/Tests/CMakeCommands/build_command +# +# expect this to fail: +# ../bin/Release/cmake -DTEST_ERROR_CONDITIONS:BOOL=ON -G Xcode +# ../../CMake/Tests/CMakeCommands/build_command +# +# This project exists merely to test the CMake command 'build_command'... +# ...even purposefully calling it with known-bad argument lists to cover +# error handling code. +# +cmake_minimum_required(VERSION 2.8) +project(test_build_command) + +set(cmd "initial") + +message("CTEST_FULL_OUTPUT") +message("0. begin") + +if(TEST_ERROR_CONDITIONS) + # Test with no arguments (an error): + build_command() + message("1. cmd='${cmd}'") + + # Test with unknown arguments (also an error): + build_command(cmd BOGUS STUFF) + message("2. cmd='${cmd}'") + + build_command(cmd STUFF BOGUS) + message("3. cmd='${cmd}'") +else() + message("(skipping cases 1, 2 and 3 because TEST_ERROR_CONDITIONS is OFF)") +endif() + +# Test the one arg signature with none of the optional KEYWORD arguments: +build_command(cmd) +message("4. cmd='${cmd}'") + +# Test the two-arg legacy signature: +build_command(legacy_cmd ${CMAKE_BUILD_TOOL}) +message("5. legacy_cmd='${legacy_cmd}'") +message(" CMAKE_BUILD_TOOL='${CMAKE_BUILD_TOOL}'") + +# Test the optional KEYWORDs: +build_command(cmd CONFIGURATION hoohaaConfig) +message("6. cmd='${cmd}'") + +build_command(cmd PROJECT_NAME hoohaaProject) +message("7. cmd='${cmd}'") + +build_command(cmd TARGET hoohaaTarget) +message("8. cmd='${cmd}'") + +set(cmd "final") +message("9. cmd='${cmd}'") diff --git a/Tests/CMakeCommands/build_command/RunCMake.cmake b/Tests/CMakeCommands/build_command/RunCMake.cmake new file mode 100644 index 000000000..55d935960 --- /dev/null +++ b/Tests/CMakeCommands/build_command/RunCMake.cmake @@ -0,0 +1,86 @@ +if(NOT DEFINED CMake_SOURCE_DIR) + message(FATAL_ERROR "CMake_SOURCE_DIR not defined") +endif() + +if(NOT DEFINED dir) + message(FATAL_ERROR "dir not defined") +endif() + +if(NOT DEFINED gen) + message(FATAL_ERROR "gen not defined") +endif() + +message(STATUS "CTEST_FULL_OUTPUT (Avoid ctest truncation of output)") + +# Run cmake: +# +function(run_cmake build_dir extra_args expected_result expected_output expected_error) + message(STATUS "run_cmake build_dir='${build_dir}' extra_args='${extra_args}'") + + # Ensure build_dir exists: + # + execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${build_dir}) + + # Run cmake: + # + execute_process(COMMAND ${CMAKE_COMMAND} + ${extra_args} + -G ${gen} ${CMake_SOURCE_DIR}/Tests/CMakeCommands/build_command + RESULT_VARIABLE result + OUTPUT_VARIABLE stdout + ERROR_VARIABLE stderr + WORKING_DIRECTORY ${build_dir} + ) + + message(STATUS "result='${result}'") + message(STATUS "stdout='${stdout}'") + message(STATUS "stderr='${stderr}'") + message(STATUS "") + + # Verify result and output match expectations: + # + if("0" STREQUAL "${expected_result}") + if(NOT "${result}" STREQUAL "0") + message(FATAL_ERROR + "error: result='${result}' is non-zero and different than expected_result='${expected_result}'") + endif() + else() + if("${result}" STREQUAL "0") + message(FATAL_ERROR + "error: result='${result}' is zero and different than expected_result='${expected_result}'") + endif() + endif() + + foreach(e ${expected_output}) + if(NOT stdout MATCHES "${e}") + message(FATAL_ERROR + "error: stdout does not match expected_output item e='${e}'") + else() + message(STATUS "info: stdout matches '${e}'") + endif() + endforeach() + + foreach(e ${expected_error}) + if(NOT stderr MATCHES "${e}") + message(FATAL_ERROR + "error: stderr does not match expected_error item e='${e}'") + else() + message(STATUS "info: stderr matches '${e}'") + endif() + endforeach() + + message(STATUS "result, stdout and stderr match all expectations: test passes") + message(STATUS "") +endfunction() + + +# Expect this case to succeed: +run_cmake("${dir}/b1" "" 0 + "Build files have been written to:" + "skipping cases 1, 2 and 3 because TEST_ERROR_CONDITIONS is OFF") + + +# Expect this one to fail: +run_cmake("${dir}/b2" "-DTEST_ERROR_CONDITIONS:BOOL=ON" 1 + "Configuring incomplete, errors occurred!" + "build_command requires at least one argument naming a CMake variable;build_command unknown argument ") diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index a9b584bb6..fe8caeb6b 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1181,7 +1181,7 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=CVS -P ${CMake_SOURCE_DIR}/Utilities/Rel ) SET_TESTS_PROPERTIES(CTestTestNoBuild PROPERTIES FAIL_REGULAR_EXPRESSION "Error" WILL_FAIL true) - + CONFIGURE_FILE( "${CMake_SOURCE_DIR}/Tests/CTestTestFailure/testNoExe.cmake.in" "${CMake_BINARY_DIR}/Tests/CTestTestFailure/testNoExe.cmake" @@ -1194,6 +1194,50 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=CVS -P ${CMake_SOURCE_DIR}/Utilities/Rel PASS_REGULAR_EXPRESSION "Could not find executable" FAIL_REGULAR_EXPRESSION "SegFault") + + # Use macro, not function so that build can still be driven by CMake 2.4. + # After 2.6 is required, this could be a function without the extra 'set' + # calls. + # + macro(add_config_tests cfg) + set(cfg "${cfg}") + set(base "${CMake_BINARY_DIR}/Tests/CTestConfig") + + # Test -S script with a -C config arg to ctest: + configure_file( + "${CMake_SOURCE_DIR}/Tests/CTestConfig/script.cmake.in" + "${base}/${cfg}-script.cmake" + @ONLY ESCAPE_QUOTES) + add_test(CTestConfig.Script.${cfg} ${CMAKE_CTEST_COMMAND} + -C ${cfg} + -S "${base}/${cfg}-script.cmake" -VV + --output-log "${base}/${cfg}-script.log" + ) + + # Test -D dashboard with a -C config arg to ctest. + # (Actual commands inside a cmake -P script because we need to be able to set + # the working directory reliably...) + configure_file( + "${CMake_SOURCE_DIR}/Tests/CTestConfig/dashboard.cmake.in" + "${base}/${cfg}-dashboard.cmake" + @ONLY ESCAPE_QUOTES) + add_test(CTestConfig.Dashboard.${cfg} ${CMAKE_CMAKE_COMMAND} + -P "${base}/${cfg}-dashboard.cmake" -VV + ) + endmacro() + + add_config_tests(Debug) + add_config_tests(MinSizeRel) + add_config_tests(Release) + add_config_tests(RelWithDebInfo) + + add_test(CMakeCommands.build_command ${CMAKE_CMAKE_COMMAND} + -DCMake_SOURCE_DIR=${CMake_SOURCE_DIR} + -Ddir=${CMake_BINARY_DIR}/Tests/CMakeCommands/build_command + -Dgen=${CMAKE_TEST_GENERATOR} + -P "${CMake_SOURCE_DIR}/Tests/CMakeCommands/build_command/RunCMake.cmake" + ) + CONFIGURE_FILE( "${CMake_SOURCE_DIR}/Tests/CTestTestCrash/test.cmake.in" "${CMake_BINARY_DIR}/Tests/CTestTestCrash/test.cmake" @@ -1256,11 +1300,11 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=CVS -P ${CMake_SOURCE_DIR}/Utilities/Rel -S "${CMake_BINARY_DIR}/Tests/CTestTestRunScript/test.cmake" -V --output-log "${CMake_BINARY_DIR}/Tests/CTestTestRunScript/testOutput.log" ) - + ADD_TEST(CTestTestShowOnly ${CMAKE_CTEST_COMMAND} -N) - + ADD_TEST(CTestBatchTest ${CMAKE_CTEST_COMMAND} -B) - + # Use macro, not function so that build can still be driven by CMake 2.4. # After 2.6 is required, this could be a function without the extra 'set' # calls. diff --git a/Tests/CTestConfig/CMakeLists.txt b/Tests/CTestConfig/CMakeLists.txt new file mode 100644 index 000000000..f46d89a55 --- /dev/null +++ b/Tests/CTestConfig/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 2.8) +project(CTestConfig) + +include(CTest) + + +# We expect this configure to occur through a 'ctest -D Experimental' or a +# 'ctest -S script.cmake' call. +# +# In either case, we expect CMAKE_BUILD_TYPE to be defined for single-configuration +# build trees and not defined for multi-configuration build trees. +# +if(CMAKE_CONFIGURATION_TYPES) + # multi-configuration: expect not defined, error if defined + if(DEFINED CMAKE_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE STREQUAL "") + message(FATAL_ERROR "CMAKE_CONFIGURATION_TYPES='${CMAKE_CONFIGURATION_TYPES}' CMAKE_BUILD_TYPE='${CMAKE_BUILD_TYPE}' is defined and non-empty (but should not be for a multi-configuration generator)") + endif() +else() + # single-configuration: expect defined, error if not defined + if(NOT DEFINED CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") + message(FATAL_ERROR "CMAKE_BUILD_TYPE is not defined or is empty (but should be defined and non-empty for a single-configuration generator)") + endif() +endif() + + +if(DEFINED CMAKE_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE STREQUAL "") + add_definitions(-DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}") +endif() + +add_executable(ctc CTestConfig.cxx) + + +foreach(cfg ${CMAKE_CONFIGURATION_TYPES} ${CMAKE_BUILD_TYPE}) + add_test(NAME ctc-${cfg} CONFIGURATIONS ${cfg} COMMAND ctc --config $) + + if(CMAKE_CONFIGURATION_TYPES) + set_property(TEST ctc-${cfg} + PROPERTY PASS_REGULAR_EXPRESSION "CMAKE_INTDIR is ${cfg}") + set_property(TEST ctc-${cfg} + PROPERTY FAIL_REGULAR_EXPRESSION "CMAKE_BUILD_TYPE is") + else() + set_property(TEST ctc-${cfg} + PROPERTY PASS_REGULAR_EXPRESSION "CMAKE_BUILD_TYPE is ${cfg}") + set_property(TEST ctc-${cfg} + PROPERTY FAIL_REGULAR_EXPRESSION "CMAKE_INTDIR is") + endif() +endforeach() diff --git a/Tests/CTestConfig/CTestConfig.cxx b/Tests/CTestConfig/CTestConfig.cxx new file mode 100644 index 000000000..49c53241a --- /dev/null +++ b/Tests/CTestConfig/CTestConfig.cxx @@ -0,0 +1,20 @@ +#include + +int main(int argc, const char* argv[]) +{ + int i = 0; + for (; i