Browse Source

ENH: Create internal 'ctest --launch' tool

This creates an undocumented 'ctest --launch' mode.  It launches a
specified command and optionally records a failure in an xml fragment.
We will optionally use this in CTest's Build stage to record per-rule
build failure information when using Makefile generators.
Brad King 16 năm trước cách đây
mục cha
commit
0847d825b3
4 tập tin đã thay đổi với 795 bổ sung0 xóa
  1. 1 0
      Source/CMakeLists.txt
  2. 680 0
      Source/CTest/cmCTestLaunch.cxx
  3. 105 0
      Source/CTest/cmCTestLaunch.h
  4. 9 0
      Source/ctest.cxx

+ 1 - 0
Source/CMakeLists.txt

@@ -327,6 +327,7 @@ SET(CTEST_SRCS cmCTest.cxx
   CTest/cmCTestEmptyBinaryDirectoryCommand.cxx
   CTest/cmCTestGenericHandler.cxx
   CTest/cmCTestHandlerCommand.cxx
+  CTest/cmCTestLaunch.cxx
   CTest/cmCTestMemCheckCommand.cxx
   CTest/cmCTestMemCheckHandler.cxx
   CTest/cmCTestMultiProcessHandler.cxx

+ 680 - 0
Source/CTest/cmCTestLaunch.cxx

@@ -0,0 +1,680 @@
+/*=========================================================================
+
+  Program:   CMake - Cross-Platform Makefile Generator
+  Module:    $RCSfile$
+  Language:  C++
+  Date:      $Date$
+  Version:   $Revision$
+
+  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
+  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#include "cmCTestLaunch.h"
+
+#include "cmGeneratedFileStream.h"
+#include "cmSystemTools.h"
+#include "cmXMLSafe.h"
+#include "cmake.h"
+
+#include <cmsys/MD5.h>
+#include <cmsys/Process.h>
+#include <cmsys/RegularExpression.hxx>
+
+//----------------------------------------------------------------------------
+cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv)
+{
+  this->Passthru = true;
+  this->Process = 0;
+  this->ExitCode = 1;
+  this->CWD = cmSystemTools::GetCurrentWorkingDirectory();
+
+  if(!this->ParseArguments(argc, argv))
+    {
+    return;
+    }
+
+  this->ComputeFileNames();
+
+  this->ScrapeRulesLoaded = false;
+  this->HaveOut = false;
+  this->HaveErr = false;
+  this->Process = cmsysProcess_New();
+}
+
+//----------------------------------------------------------------------------
+cmCTestLaunch::~cmCTestLaunch()
+{
+  cmsysProcess_Delete(this->Process);
+  if(!this->Passthru)
+    {
+    cmSystemTools::RemoveFile(this->LogOut.c_str());
+    cmSystemTools::RemoveFile(this->LogErr.c_str());
+    }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestLaunch::ParseArguments(int argc, const char* const* argv)
+{
+  // Launcher options occur first and are separated from the real
+  // command line by a '--' option.
+  enum Doing { DoingNone,
+               DoingOutput,
+               DoingSource,
+               DoingLanguage,
+               DoingTargetName,
+               DoingTargetType,
+               DoingBuildDir,
+               DoingCount };
+  Doing doing = DoingNone;
+  int arg0 = 0;
+  for(int i=1; !arg0 && i < argc; ++i)
+    {
+    const char* arg = argv[i];
+    if(strcmp(arg, "--") == 0)
+      {
+      arg0 = i+1;
+      }
+    else if(strcmp(arg, "--output") == 0)
+      {
+      doing = DoingOutput;
+      }
+    else if(strcmp(arg, "--source") == 0)
+      {
+      doing = DoingSource;
+      }
+    else if(strcmp(arg, "--language") == 0)
+      {
+      doing = DoingLanguage;
+      }
+    else if(strcmp(arg, "--target-name") == 0)
+      {
+      doing = DoingTargetName;
+      }
+    else if(strcmp(arg, "--target-type") == 0)
+      {
+      doing = DoingTargetType;
+      }
+    else if(strcmp(arg, "--build-dir") == 0)
+      {
+      doing = DoingBuildDir;
+      }
+    else if(doing == DoingOutput)
+      {
+      this->OptionOutput = arg;
+      doing = DoingNone;
+      }
+    else if(doing == DoingSource)
+      {
+      this->OptionSource = arg;
+      doing = DoingNone;
+      }
+    else if(doing == DoingLanguage)
+      {
+      this->OptionLanguage = arg;
+      if(this->OptionLanguage == "CXX")
+        {
+        this->OptionLanguage = "C++";
+        }
+      doing = DoingNone;
+      }
+    else if(doing == DoingTargetName)
+      {
+      this->OptionTargetName = arg;
+      doing = DoingNone;
+      }
+    else if(doing == DoingTargetType)
+      {
+      this->OptionTargetType = arg;
+      doing = DoingNone;
+      }
+    else if(doing == DoingBuildDir)
+      {
+      this->OptionBuildDir = arg;
+      doing = DoingNone;
+      }
+    }
+
+  // Extract the real command line.
+  if(arg0)
+    {
+    this->RealArgC = argc - arg0;
+    this->RealArgV = argv + arg0;
+    for(int i=0; i < this->RealArgC; ++i)
+      {
+      this->HandleRealArg(this->RealArgV[i]);
+      }
+    return true;
+    }
+  else
+    {
+    this->RealArgC = 0;
+    this->RealArgV = 0;
+    std::cerr << "No launch/command separator ('--') found!\n";
+    return false;
+    }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::HandleRealArg(const char* arg)
+{
+#ifdef _WIN32
+  // Expand response file arguments.
+  if(arg[0] == '@' && cmSystemTools::FileExists(arg+1))
+    {
+    std::ifstream fin(arg+1);
+    std::string line;
+    while(cmSystemTools::GetLineFromStream(fin, line))
+      {
+      cmSystemTools::ParseWindowsCommandLine(line.c_str(), this->RealArgs);
+      }
+    return;
+    }
+#endif
+  this->RealArgs.push_back(arg);
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::ComputeFileNames()
+{
+  // We just passthru the behavior of the real command unless the
+  // CTEST_LAUNCH_LOGS environment variable is set.
+  const char* d = getenv("CTEST_LAUNCH_LOGS");
+  if(!(d && *d))
+    {
+    return;
+    }
+  this->Passthru = false;
+
+  // The environment variable specifies the directory into which we
+  // generate build logs.
+  this->LogDir = d;
+  cmSystemTools::ConvertToUnixSlashes(this->LogDir);
+  this->LogDir += "/";
+
+  // We hash the input command working dir and command line to obtain
+  // a repeatable and (probably) unique name for log files.
+  char hash[33] = {};
+  cmsysMD5* md5 = cmsysMD5_New();
+  cmsysMD5_Initialize(md5);
+  cmsysMD5_Append(md5, (unsigned char const*)(this->CWD.c_str()), -1);
+  for(std::vector<std::string>::const_iterator ai = this->RealArgs.begin();
+      ai != this->RealArgs.end(); ++ai)
+    {
+    cmsysMD5_Append(md5, (unsigned char const*)ai->c_str(), -1);
+    }
+  cmsysMD5_FinalizeHex(md5, hash);
+  cmsysMD5_Delete(md5);
+  this->LogHash = hash;
+
+  // We store stdout and stderr in temporary log files.
+  this->LogOut = this->LogDir;
+  this->LogOut += "launch-";
+  this->LogOut += this->LogHash;
+  this->LogOut += "-out.txt";
+  this->LogErr = this->LogDir;
+  this->LogErr += "launch-";
+  this->LogErr += this->LogHash;
+  this->LogErr += "-err.txt";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::RunChild()
+{
+  // Prepare to run the real command.
+  cmsysProcess* cp = this->Process;
+  cmsysProcess_SetCommand(cp, this->RealArgV);
+
+  std::ofstream fout;
+  std::ofstream ferr;
+  if(this->Passthru)
+    {
+    // In passthru mode we just share the output pipes.
+    cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1);
+    cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1);
+    }
+  else
+    {
+    // In full mode we record the child output pipes to log files.
+    fout.open(this->LogOut.c_str(),
+              std::ios::out | std::ios::binary);
+    ferr.open(this->LogErr.c_str(),
+              std::ios::out | std::ios::binary);
+    }
+
+  // Run the real command.
+  cmsysProcess_Execute(cp);
+
+  // Record child stdout and stderr if necessary.
+  if(!this->Passthru)
+    {
+    char* data = 0;
+    int length = 0;
+    while(int p = cmsysProcess_WaitForData(cp, &data, &length, 0))
+      {
+      if(p == cmsysProcess_Pipe_STDOUT)
+        {
+        fout.write(data, length);
+        std::cout.write(data, length);
+        this->HaveOut = true;
+        }
+      else if(p == cmsysProcess_Pipe_STDERR)
+        {
+        ferr.write(data, length);
+        std::cerr.write(data, length);
+        this->HaveErr = true;
+        }
+      }
+    }
+
+  // Wait for the real command to finish.
+  cmsysProcess_WaitForExit(cp, 0);
+  this->ExitCode = cmsysProcess_GetExitValue(cp);
+}
+
+//----------------------------------------------------------------------------
+int cmCTestLaunch::Run()
+{
+  if(!this->Process)
+    {
+    std::cerr << "Could not allocate cmsysProcess instance!\n";
+    return -1;
+    }
+
+  this->RunChild();
+
+  if(this->CheckResults())
+    {
+    return this->ExitCode;
+    }
+
+  this->WriteXML();
+
+  return this->ExitCode;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::LoadLabels()
+{
+  if(this->OptionBuildDir.empty() || this->OptionTargetName.empty())
+    {
+    return;
+    }
+
+  // Labels are listed in per-target files.
+  std::string fname = this->OptionBuildDir;
+  fname += cmake::GetCMakeFilesDirectory();
+  fname += "/";
+  fname += this->OptionTargetName;
+  fname += ".dir/Labels.txt";
+
+  // We are interested in per-target labels for this source file.
+  std::string source = this->OptionSource;
+  cmSystemTools::ConvertToUnixSlashes(source);
+
+  // Load the labels file.
+  std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
+  if(!fin) { return; }
+  bool inTarget = true;
+  bool inSource = false;
+  std::string line;
+  while(cmSystemTools::GetLineFromStream(fin, line))
+    {
+    if(line.empty() || line[0] == '#')
+      {
+      // Ignore blank and comment lines.
+      continue;
+      }
+    else if(line[0] == ' ')
+      {
+      // Label lines appear indented by one space.
+      if(inTarget || inSource)
+        {
+        this->Labels.insert(line.c_str()+1);
+        }
+      }
+    else if(!this->OptionSource.empty() && !inSource)
+      {
+      // Non-indented lines specify a source file name.  The first one
+      // is the end of the target-wide labels.  Use labels following a
+      // matching source.
+      inTarget = false;
+      inSource = this->SourceMatches(line, source);
+      }
+    else
+      {
+      return;
+      }
+    }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestLaunch::SourceMatches(std::string const& lhs,
+                                  std::string const& rhs)
+{
+  // TODO: Case sensitivity, UseRelativePaths, etc.  Note that both
+  // paths in the comparison get generated by CMake.  This is done for
+  // every source in the target, so it should be efficient (cannot use
+  // cmSystemTools::IsSameFile).
+  return lhs == rhs;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestLaunch::IsError() const
+{
+  return this->ExitCode != 0;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXML()
+{
+  // Name the xml file.
+  std::string logXML = this->LogDir;
+  logXML += this->IsError()? "error-" : "warning-";
+  logXML += this->LogHash;
+  logXML += ".xml";
+
+  // Use cmGeneratedFileStream to atomically create the report file.
+  cmGeneratedFileStream fxml(logXML.c_str());
+  fxml << "\t<Failure type=\""
+       << (this->IsError()? "Error" : "Warning") << "\">\n";
+  this->WriteXMLAction(fxml);
+  this->WriteXMLCommand(fxml);
+  this->WriteXMLResult(fxml);
+  this->WriteXMLLabels(fxml);
+  fxml << "\t</Failure>\n";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXMLAction(std::ostream& fxml)
+{
+  fxml << "\t\t<!-- Meta-information about the build action -->\n";
+  fxml << "\t\t<Action>\n";
+
+  // TargetName
+  if(!this->OptionTargetName.empty())
+    {
+    fxml << "\t\t\t<TargetName>"
+         << cmXMLSafe(this->OptionTargetName)
+         << "</TargetName>\n";
+    }
+
+  // Language
+  if(!this->OptionLanguage.empty())
+    {
+    fxml << "\t\t\t<Language>"
+         << cmXMLSafe(this->OptionLanguage)
+         << "</Language>\n";
+    }
+
+  // SourceFile
+  if(!this->OptionSource.empty())
+    {
+    fxml << "\t\t\t<SourceFile>"
+         << cmXMLSafe(this->OptionSource)
+         << "</SourceFile>\n";
+    }
+
+  // OutputFile
+  if(!this->OptionOutput.empty())
+    {
+    fxml << "\t\t\t<OutputFile>"
+         << cmXMLSafe(this->OptionOutput)
+         << "</OutputFile>\n";
+    }
+
+  // OutputType
+  const char* outputType = 0;
+  if(!this->OptionTargetType.empty())
+    {
+    if(this->OptionTargetType == "EXECUTABLE")
+      {
+      outputType = "executable";
+      }
+    else if(this->OptionTargetType == "SHARED_LIBRARY")
+      {
+      outputType = "shared library";
+      }
+    else if(this->OptionTargetType == "MODULE_LIBRARY")
+      {
+      outputType = "module library";
+      }
+    else if(this->OptionTargetType == "STATIC_LIBRARY")
+      {
+      outputType = "static library";
+      }
+    }
+  else if(!this->OptionSource.empty())
+    {
+    outputType = "object file";
+    }
+  if(outputType)
+    {
+    fxml << "\t\t\t<OutputType>"
+         << cmXMLSafe(outputType)
+         << "</OutputType>\n";
+    }
+
+  fxml << "\t\t</Action>\n";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXMLCommand(std::ostream& fxml)
+{
+  fxml << "\n";
+  fxml << "\t\t<!-- Details of command -->\n";
+  fxml << "\t\t<Command>\n";
+  if(!this->CWD.empty())
+    {
+    fxml << "\t\t\t<WorkingDirectory>"
+         << cmXMLSafe(this->CWD)
+         << "</WorkingDirectory>\n";
+    }
+  for(std::vector<std::string>::const_iterator ai = this->RealArgs.begin();
+      ai != this->RealArgs.end(); ++ai)
+    {
+    fxml << "\t\t\t<Argument>"
+         << cmXMLSafe(ai->c_str())
+         << "</Argument>\n";
+    }
+  fxml << "\t\t</Command>\n";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXMLResult(std::ostream& fxml)
+{
+  fxml << "\n";
+  fxml << "\t\t<!-- Result of command -->\n";
+  fxml << "\t\t<Result>\n";
+
+  // StdOut
+  fxml << "\t\t\t<StdOut>";
+  this->DumpFileToXML(fxml, this->LogOut);
+  fxml << "</StdOut>\n";
+
+  // StdErr
+  fxml << "\t\t\t<StdErr>";
+  this->DumpFileToXML(fxml, this->LogErr);
+  fxml << "</StdErr>\n";
+
+  // ExitCondition
+  fxml << "\t\t\t<ExitCondition>";
+  cmsysProcess* cp = this->Process;
+  switch (cmsysProcess_GetState(cp))
+    {
+    case cmsysProcess_State_Starting:
+      fxml << "No process has been executed"; break;
+    case cmsysProcess_State_Executing:
+      fxml << "The process is still executing"; break;
+    case cmsysProcess_State_Disowned:
+      fxml << "Disowned"; break;
+    case cmsysProcess_State_Killed:
+      fxml << "Killed by parent"; break;
+
+    case cmsysProcess_State_Expired:
+      fxml << "Killed when timeout expired"; break;
+    case cmsysProcess_State_Exited:
+      fxml << this->ExitCode; break;
+    case cmsysProcess_State_Exception:
+      fxml << "Terminated abnormally: "
+           << cmXMLSafe(cmsysProcess_GetExceptionString(cp)); break;
+    case cmsysProcess_State_Error:
+      fxml << "Error administrating child process: "
+           << cmXMLSafe(cmsysProcess_GetErrorString(cp)); break;
+    };
+  fxml << "</ExitCondition>\n";
+
+  fxml << "\t\t</Result>\n";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXMLLabels(std::ostream& fxml)
+{
+  this->LoadLabels();
+  if(!this->Labels.empty())
+    {
+    fxml << "\n";
+    fxml << "\t\t<!-- Interested parties -->\n";
+    fxml << "\t\t<Labels>\n";
+    for(std::set<cmStdString>::const_iterator li = this->Labels.begin();
+        li != this->Labels.end(); ++li)
+      {
+      fxml << "\t\t\t<Label>" << cmXMLSafe(*li) << "</Label>\n";
+      }
+    fxml << "\t\t</Labels>\n";
+    }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::DumpFileToXML(std::ostream& fxml,
+                                  std::string const& fname)
+{
+  std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
+
+  std::string line;
+  const char* sep = "";
+  while(cmSystemTools::GetLineFromStream(fin, line))
+    {
+    fxml << sep << cmXMLSafe(line).Quotes(false);
+    sep = "\n";
+    }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestLaunch::CheckResults()
+{
+  // Skip XML in passthru mode.
+  if(this->Passthru)
+    {
+    return true;
+    }
+
+  // We always report failure for error conditions.
+  if(this->IsError())
+    {
+    return false;
+    }
+
+  // Scrape the output logs to look for warnings.
+  if((this->HaveErr && this->ScrapeLog(this->LogErr)) ||
+     (this->HaveOut && this->ScrapeLog(this->LogOut)))
+    {
+    return false;
+    }
+  return true;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::LoadScrapeRules()
+{
+  if(this->ScrapeRulesLoaded)
+    {
+    return;
+    }
+  this->ScrapeRulesLoaded = true;
+
+  // Common compiler warning formats.  These are much simpler than the
+  // full log-scraping expressions because we do not need to extract
+  // file and line information.
+  this->RegexWarning.push_back("(^|[ :])[Ww][Aa][Rr][Nn][Ii][Nn][Gg]");
+  this->RegexWarning.push_back("(^|[ :])[Rr][Ee][Mm][Aa][Rr][Kk]");
+  this->RegexWarning.push_back("(^|[ :])[Nn][Oo][Tt][Ee]");
+
+  // Load custom match rules given to us by CTest.
+  this->LoadScrapeRules("Warning", this->RegexWarning);
+  this->LoadScrapeRules("WarningSuppress", this->RegexWarningSuppress);
+}
+
+//----------------------------------------------------------------------------
+void
+cmCTestLaunch
+::LoadScrapeRules(const char* purpose,
+                  std::vector<cmsys::RegularExpression>& regexps)
+{
+  std::string fname = this->LogDir;
+  fname += "Custom";
+  fname += purpose;
+  fname += ".txt";
+  std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
+  std::string line;
+  cmsys::RegularExpression rex;
+  while(cmSystemTools::GetLineFromStream(fin, line))
+    {
+    if(rex.compile(line.c_str()))
+      {
+      regexps.push_back(rex);
+      }
+    }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestLaunch::ScrapeLog(std::string const& fname)
+{
+  this->LoadScrapeRules();
+
+  // Look for log file lines matching warning expressions but not
+  // suppression expressions.
+  std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
+  std::string line;
+  while(cmSystemTools::GetLineFromStream(fin, line))
+    {
+    if(this->Match(line.c_str(), this->RegexWarning) &&
+       !this->Match(line.c_str(), this->RegexWarningSuppress))
+      {
+      return true;
+      }
+    }
+  return false;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestLaunch::Match(std::string const& line,
+                          std::vector<cmsys::RegularExpression>& regexps)
+{
+  for(std::vector<cmsys::RegularExpression>::iterator ri = regexps.begin();
+      ri != regexps.end(); ++ri)
+    {
+    if(ri->find(line.c_str()))
+      {
+      return true;
+      }
+    }
+  return false;
+}
+
+//----------------------------------------------------------------------------
+int cmCTestLaunch::Main(int argc, const char* const argv[])
+{
+  if(argc == 2)
+    {
+    std::cerr << "ctest --launch: this mode is for internal CTest use only"
+              << std::endl;
+    return 1;
+    }
+  cmCTestLaunch self(argc, argv);
+  return self.Run();
+}

+ 105 - 0
Source/CTest/cmCTestLaunch.h

@@ -0,0 +1,105 @@
+/*=========================================================================
+
+  Program:   CMake - Cross-Platform Makefile Generator
+  Module:    $RCSfile$
+  Language:  C++
+  Date:      $Date$
+  Version:   $Revision$
+
+  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
+  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#ifndef cmCTestLaunch_h
+#define cmCTestLaunch_h
+
+#include "cmStandardIncludes.h"
+#include <cmsys/RegularExpression.hxx>
+
+/** \class cmCTestLaunch
+ * \brief Launcher for make rules to report results for ctest
+ *
+ * This implements the 'ctest --launch' tool.
+ */
+class cmCTestLaunch
+{
+public:
+  /** Entry point from ctest executable main().  */
+  static int Main(int argc, const char* const* argv);
+private:
+  // Initialize the launcher from its command line.
+  cmCTestLaunch(int argc, const char* const* argv);
+  ~cmCTestLaunch();
+
+  // Run the real command.
+  int Run();
+  void RunChild();
+
+  // Methods to check the result of the real command.
+  bool IsError() const;
+  bool CheckResults();
+
+  // Launcher options specified before the real command.
+  std::string OptionOutput;
+  std::string OptionSource;
+  std::string OptionLanguage;
+  std::string OptionTargetName;
+  std::string OptionTargetType;
+  std::string OptionBuildDir;
+  bool ParseArguments(int argc, const char* const* argv);
+
+  // The real command line appearing after launcher arguments.
+  int RealArgC;
+  const char* const* RealArgV;
+  std::string CWD;
+
+  // The real command line after response file expansion.
+  std::vector<std::string> RealArgs;
+  void HandleRealArg(const char* arg);
+
+  // A hash of the real command line is unique and unlikely to collide.
+  std::string LogHash;
+  void ComputeFileNames();
+
+  bool Passthru;
+  struct cmsysProcess_s* Process;
+  int ExitCode;
+
+  // Temporary log files for stdout and stderr of real command.
+  std::string LogDir;
+  std::string LogOut;
+  std::string LogErr;
+  bool HaveOut;
+  bool HaveErr;
+
+  // Labels associated with the build rule.
+  std::set<cmStdString> Labels;
+  void LoadLabels();
+  bool SourceMatches(std::string const& lhs,
+                     std::string const& rhs);
+
+  // Regular expressions to match warnings and their exceptions.
+  bool ScrapeRulesLoaded;
+  std::vector<cmsys::RegularExpression> RegexWarning;
+  std::vector<cmsys::RegularExpression> RegexWarningSuppress;
+  void LoadScrapeRules();
+  void LoadScrapeRules(const char* purpose,
+                       std::vector<cmsys::RegularExpression>& regexps);
+  bool ScrapeLog(std::string const& fname);
+  bool Match(std::string const& line,
+             std::vector<cmsys::RegularExpression>& regexps);
+
+  // Methods to generate the xml fragment.
+  void WriteXML();
+  void WriteXMLAction(std::ostream& fxml);
+  void WriteXMLCommand(std::ostream& fxml);
+  void WriteXMLResult(std::ostream& fxml);
+  void WriteXMLLabels(std::ostream& fxml);
+  void DumpFileToXML(std::ostream& fxml, std::string const& fname);
+};
+
+#endif

+ 9 - 0
Source/ctest.cxx

@@ -22,6 +22,8 @@
 #include "cmDocumentation.h"
 
 #include "CTest/cmCTestScriptHandler.h"
+#include "CTest/cmCTestLaunch.h"
+
 //----------------------------------------------------------------------------
 static const char * cmDocumentationName[][3] =
 {
@@ -217,6 +219,13 @@ int main (int argc, char *argv[])
   cmSystemTools::DoNotInheritStdPipes();
   cmSystemTools::EnableMSVCDebugHook();
   cmSystemTools::FindExecutableDirectory(argv[0]);
+
+  // Dispatch 'ctest --launch' mode directly.
+  if(argc >= 2 && strcmp(argv[1], "--launch") == 0)
+    {
+    return cmCTestLaunch::Main(argc, argv);
+    }
+
   int nocwd = 0;
   cmCTest inst;