Browse Source

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.

David Cole 16 years ago
parent
commit
0b38bb4c53

+ 11 - 6
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)

+ 1 - 0
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"

+ 14 - 2
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,

+ 29 - 7
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<unsigned int>(this->StartBuildTime)
      << "</StartBuildTime>\n"
      << "<BuildCommand>"
-     << cmXMLSafe(
-       this->CTest->GetCTestConfiguration("MakeCommand"))
+     << cmXMLSafe(this->GetMakeCommand())
      << "</BuildCommand>" << std::endl;
 }
 

+ 3 - 0
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,

+ 32 - 3
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;
       }
     }

+ 11 - 0
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 )

+ 1 - 1
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");
 }
-

+ 112 - 3
Source/cmBuildCommand.cxx

@@ -14,25 +14,135 @@
 #include "cmLocalGenerator.h"
 #include "cmGlobalGenerator.h"
 
-// cmBuildCommand
+//----------------------------------------------------------------------
 bool cmBuildCommand
 ::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+  // Support the legacy signature of the command:
+  //
+  if(2 == args.size())
+    {
+    return this->TwoArgsSignature(args);
+    }
+
+  return this->MainSignature(args);
+}
+
+//----------------------------------------------------------------------
+bool cmBuildCommand
+::MainSignature(std::vector<std::string> 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<std::string> const& args)
 {
   if(args.size() < 2 )
     {
-    this->SetError("called with incorrect number of arguments");
+    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;
 }
-

+ 37 - 12
Source/cmBuildCommand.h

@@ -37,36 +37,61 @@ public:
   virtual bool InitialPass(std::vector<std::string> const& args,
                            cmExecutionStatus &status);
 
+  /**
+   * The primary command signature with optional, KEYWORD-based args.
+   */
+  virtual bool MainSignature(std::vector<std::string> const& args);
+
+  /**
+   * Legacy "exactly 2 args required" signature.
+   */
+  virtual bool TwoArgsSignature(std::vector<std::string> 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(<variable> <makecommand>)\n"
-      "Sets the given <variable> to a string containing the command that "
-      "will build this project from the root of the build tree using the "
-      "build tool given by <makecommand>.  <makecommand> should be msdev, "
-      "nmake, make or one of the end user build tools.  "
-      "This is useful for configuring testing systems.";
+      "  build_command(<variable>\n"
+      "                [CONFIGURATION <config>]\n"
+      "                [PROJECT_NAME <projname>]\n"
+      "                [TARGET <target>])\n"
+      "Sets the given <variable> 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(<cachevariable> <makecommand>)\n"
+      "This second signature is deprecated, but still available for "
+      "backwards compatibility. Use the first signature instead.\n"
+      "Sets the given <cachevariable> to a string containing the command "
+      "to build this project from the root of the build tree using "
+      "the build tool given by <makecommand>.  <makecommand> should be "
+      "the full path to msdev, devenv, nmake, make or one of the end "
+      "user build tools."
+      ;
     }
-  
+
   cmTypeMacro(cmBuildCommand, cmCommand);
 };
 
-
-
 #endif

+ 4 - 0
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<cmLocalGenerator*> GeneratorVector;
   // for a project collect all its targets by following depend

+ 5 - 0
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();
 

+ 15 - 0
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;
+}

+ 5 - 0
Source/cmGlobalXCodeGenerator.h

@@ -80,6 +80,11 @@ public:
                                       std::vector<std::string>& 
                                       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);

+ 58 - 0
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}'")

+ 86 - 0
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 ")

+ 48 - 4
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.

+ 47 - 0
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 $<CONFIGURATION>)
+
+  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()

+ 20 - 0
Tests/CTestConfig/CTestConfig.cxx

@@ -0,0 +1,20 @@
+#include <stdio.h>
+
+int main(int argc, const char* argv[])
+{
+  int i = 0;
+  for (; i<argc; ++i)
+  {
+    fprintf(stdout, "%s\n", argv[i]);
+  }
+
+#ifdef CMAKE_BUILD_TYPE
+  fprintf(stdout, "CMAKE_BUILD_TYPE is %s\n", CMAKE_BUILD_TYPE);
+#endif
+
+#ifdef CMAKE_INTDIR
+  fprintf(stdout, "CMAKE_INTDIR is %s\n", CMAKE_INTDIR);
+#endif
+
+  return 0;
+}

+ 43 - 0
Tests/CTestConfig/dashboard.cmake.in

@@ -0,0 +1,43 @@
+set(CMAKE_CONFIGURATION_TYPES "@CMAKE_CONFIGURATION_TYPES@")
+set(CTEST_SOURCE_DIRECTORY "@CMake_SOURCE_DIR@/Tests/CTestConfig")
+set(CTEST_BINARY_DIRECTORY "@CMake_BINARY_DIR@/Tests/CTestConfig/@cfg@-dashboard")
+
+file(MAKE_DIRECTORY "${CTEST_BINARY_DIRECTORY}")
+
+get_filename_component(dir "${CMAKE_COMMAND}" PATH)
+set(CMAKE_CTEST_COMMAND "${dir}/ctest")
+
+message("CMAKE_COMMAND='${CMAKE_COMMAND}'")
+message("CMAKE_CTEST_COMMAND='${CMAKE_CTEST_COMMAND}'")
+
+set(arg "")
+if(NOT CMAKE_CONFIGURATION_TYPES)
+  set(arg "-DCMAKE_BUILD_TYPE:STRING=@cfg@")
+endif()
+
+message("cmake initial configure")
+execute_process(COMMAND ${CMAKE_COMMAND}
+  ${arg} -G "@CMAKE_TEST_GENERATOR@" ${CTEST_SOURCE_DIRECTORY}
+  WORKING_DIRECTORY ${CTEST_BINARY_DIRECTORY}
+  RESULT_VARIABLE rv)
+if(NOT rv STREQUAL 0)
+  message(FATAL_ERROR "error calling cmake: rv='${rv}'")
+endif()
+
+
+function(call_ctest arg)
+  message("call_ctest ${arg}")
+  execute_process(COMMAND ${CMAKE_CTEST_COMMAND}
+    -C "@cfg@" -D ${arg} -VV
+    WORKING_DIRECTORY ${CTEST_BINARY_DIRECTORY}
+    RESULT_VARIABLE rv)
+  if(NOT rv STREQUAL 0)
+    message(FATAL_ERROR "error calling ctest: rv='${rv}'")
+  endif()
+endfunction()
+
+
+call_ctest(ExperimentalStart)
+call_ctest(ExperimentalConfigure)
+call_ctest(ExperimentalBuild)
+call_ctest(ExperimentalTest)

+ 21 - 0
Tests/CTestConfig/script.cmake.in

@@ -0,0 +1,21 @@
+set(CTEST_CMAKE_GENERATOR "@CMAKE_TEST_GENERATOR@")
+set(CTEST_PROJECT_NAME "CTestConfig")
+set(CTEST_SOURCE_DIRECTORY "@CMake_SOURCE_DIR@/Tests/CTestConfig")
+set(CTEST_BINARY_DIRECTORY "@CMake_BINARY_DIR@/Tests/CTestConfig/@cfg@-script")
+
+ctest_start(Experimental)
+
+ctest_configure(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE rv)
+if(NOT rv STREQUAL 0)
+  message(FATAL_ERROR "*** error in ctest_configure ***")
+endif()
+
+ctest_build(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE rv)
+if(NOT rv STREQUAL 0)
+  message(FATAL_ERROR "*** error in ctest_build ***")
+endif()
+
+ctest_test(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE rv)
+if(NOT rv STREQUAL 0)
+  message(FATAL_ERROR "*** error in ctest_test ***")
+endif()