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.
This commit is contained in:
parent
52692cccaa
commit
0847d825b3
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue