mirror of
https://github.com/reactos/CMake.git
synced 2025-02-26 06:55:34 +00:00
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/cmCTestEmptyBinaryDirectoryCommand.cxx
|
||||||
CTest/cmCTestGenericHandler.cxx
|
CTest/cmCTestGenericHandler.cxx
|
||||||
CTest/cmCTestHandlerCommand.cxx
|
CTest/cmCTestHandlerCommand.cxx
|
||||||
|
CTest/cmCTestLaunch.cxx
|
||||||
CTest/cmCTestMemCheckCommand.cxx
|
CTest/cmCTestMemCheckCommand.cxx
|
||||||
CTest/cmCTestMemCheckHandler.cxx
|
CTest/cmCTestMemCheckHandler.cxx
|
||||||
CTest/cmCTestMultiProcessHandler.cxx
|
CTest/cmCTestMultiProcessHandler.cxx
|
||||||
|
680
Source/CTest/cmCTestLaunch.cxx
Normal file
680
Source/CTest/cmCTestLaunch.cxx
Normal file
@ -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
Source/CTest/cmCTestLaunch.h
Normal file
105
Source/CTest/cmCTestLaunch.h
Normal file
@ -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 "cmDocumentation.h"
|
||||||
|
|
||||||
#include "CTest/cmCTestScriptHandler.h"
|
#include "CTest/cmCTestScriptHandler.h"
|
||||||
|
#include "CTest/cmCTestLaunch.h"
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
static const char * cmDocumentationName[][3] =
|
static const char * cmDocumentationName[][3] =
|
||||||
{
|
{
|
||||||
@ -217,6 +219,13 @@ int main (int argc, char *argv[])
|
|||||||
cmSystemTools::DoNotInheritStdPipes();
|
cmSystemTools::DoNotInheritStdPipes();
|
||||||
cmSystemTools::EnableMSVCDebugHook();
|
cmSystemTools::EnableMSVCDebugHook();
|
||||||
cmSystemTools::FindExecutableDirectory(argv[0]);
|
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;
|
int nocwd = 0;
|
||||||
cmCTest inst;
|
cmCTest inst;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user