2009-09-28 19:43:28 +04:00
|
|
|
/*============================================================================
|
|
|
|
CMake - Cross Platform Makefile Generator
|
|
|
|
Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
|
2009-02-11 19:31:25 +03:00
|
|
|
|
2009-09-28 19:43:28 +04:00
|
|
|
Distributed under the OSI-approved BSD License (the "License");
|
|
|
|
see accompanying file Copyright.txt for details.
|
2009-02-11 19:31:25 +03:00
|
|
|
|
2009-09-28 19:43:28 +04:00
|
|
|
This software is distributed WITHOUT ANY WARRANTY; without even the
|
|
|
|
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the License for more information.
|
|
|
|
============================================================================*/
|
2009-02-11 19:31:25 +03:00
|
|
|
#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>
|
2014-01-04 09:47:13 +04:00
|
|
|
#include <cmsys/FStream.hxx>
|
2009-02-11 19:31:25 +03:00
|
|
|
|
2014-12-05 20:47:18 +03:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include <io.h> // for _setmode
|
|
|
|
#include <fcntl.h> // for _O_BINARY
|
|
|
|
#include <stdio.h> // for std{out,err} and fileno
|
|
|
|
#endif
|
|
|
|
|
2009-02-11 19:31:25 +03:00
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
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)
|
|
|
|
{
|
2014-10-15 16:54:05 +04:00
|
|
|
cmSystemTools::RemoveFile(this->LogOut);
|
|
|
|
cmSystemTools::RemoveFile(this->LogErr);
|
2009-02-11 19:31:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
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,
|
2013-12-02 02:16:06 +04:00
|
|
|
DoingCount,
|
|
|
|
DoingFilterPrefix };
|
2009-02-11 19:31:25 +03:00
|
|
|
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;
|
|
|
|
}
|
2013-12-02 02:16:06 +04:00
|
|
|
else if(strcmp(arg, "--filter-prefix") == 0)
|
|
|
|
{
|
|
|
|
doing = DoingFilterPrefix;
|
|
|
|
}
|
2009-02-11 19:31:25 +03:00
|
|
|
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;
|
|
|
|
}
|
2013-12-02 02:16:06 +04:00
|
|
|
else if(doing == DoingFilterPrefix)
|
|
|
|
{
|
|
|
|
this->OptionFilterPrefix = arg;
|
|
|
|
doing = DoingNone;
|
|
|
|
}
|
2009-02-11 19:31:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
{
|
2014-01-04 09:47:13 +04:00
|
|
|
cmsys::ifstream fin(arg+1);
|
2009-02-11 19:31:25 +03:00
|
|
|
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.
|
2009-02-11 19:57:24 +03:00
|
|
|
char hash[32];
|
2009-02-11 19:31:25 +03:00
|
|
|
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);
|
2009-02-11 19:57:24 +03:00
|
|
|
this->LogHash.assign(hash, 32);
|
2009-02-11 19:31:25 +03:00
|
|
|
|
|
|
|
// 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()
|
|
|
|
{
|
2011-01-06 21:00:18 +03:00
|
|
|
// Ignore noopt make rules
|
|
|
|
if(this->RealArgs.empty() || this->RealArgs[0] == ":")
|
|
|
|
{
|
|
|
|
this->ExitCode = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-02-11 19:31:25 +03:00
|
|
|
// Prepare to run the real command.
|
|
|
|
cmsysProcess* cp = this->Process;
|
|
|
|
cmsysProcess_SetCommand(cp, this->RealArgV);
|
|
|
|
|
2014-01-04 09:47:13 +04:00
|
|
|
cmsys::ofstream fout;
|
|
|
|
cmsys::ofstream ferr;
|
2009-02-11 19:31:25 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-12-05 20:47:18 +03:00
|
|
|
#ifdef _WIN32
|
|
|
|
// Do this so that newline transformation is not done when writing to cout
|
|
|
|
// and cerr below.
|
|
|
|
_setmode(fileno(stdout), _O_BINARY);
|
|
|
|
_setmode(fileno(stderr), _O_BINARY);
|
|
|
|
#endif
|
|
|
|
|
2009-02-11 19:31:25 +03:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2009-02-12 21:00:22 +03:00
|
|
|
this->LoadConfig();
|
2009-02-11 19:31:25 +03:00
|
|
|
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.
|
2014-01-04 09:47:13 +04:00
|
|
|
cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
|
2009-02-11 19:31:25 +03:00
|
|
|
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())
|
|
|
|
{
|
2009-02-12 21:00:22 +03:00
|
|
|
std::string source = this->OptionSource;
|
|
|
|
cmSystemTools::ConvertToUnixSlashes(source);
|
|
|
|
|
|
|
|
// If file is in source tree use its relative location.
|
|
|
|
if(cmSystemTools::FileIsFullPath(this->SourceDir.c_str()) &&
|
|
|
|
cmSystemTools::FileIsFullPath(source.c_str()) &&
|
2014-10-15 16:54:05 +04:00
|
|
|
cmSystemTools::IsSubDirectory(source,
|
|
|
|
this->SourceDir))
|
2009-02-12 21:00:22 +03:00
|
|
|
{
|
|
|
|
source = cmSystemTools::RelativePath(this->SourceDir.c_str(),
|
|
|
|
source.c_str());
|
|
|
|
}
|
|
|
|
|
2009-02-11 19:31:25 +03:00
|
|
|
fxml << "\t\t\t<SourceFile>"
|
2009-02-12 21:00:22 +03:00
|
|
|
<< cmXMLSafe(source)
|
2009-02-11 19:31:25 +03:00
|
|
|
<< "</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";
|
2014-02-10 09:21:34 +04:00
|
|
|
for(std::set<std::string>::const_iterator li = this->Labels.begin();
|
2009-02-11 19:31:25 +03:00
|
|
|
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)
|
|
|
|
{
|
2014-01-04 09:47:13 +04:00
|
|
|
cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
|
2009-02-11 19:31:25 +03:00
|
|
|
|
|
|
|
std::string line;
|
|
|
|
const char* sep = "";
|
2013-12-02 02:16:06 +04:00
|
|
|
|
2009-02-11 19:31:25 +03:00
|
|
|
while(cmSystemTools::GetLineFromStream(fin, line))
|
|
|
|
{
|
2014-02-25 14:25:41 +04:00
|
|
|
if(MatchesFilterPrefix(line))
|
2013-12-02 02:16:06 +04:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2009-02-11 19:31:25 +03:00
|
|
|
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";
|
2014-01-04 09:47:13 +04:00
|
|
|
cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
|
2009-02-11 19:31:25 +03:00
|
|
|
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.
|
2014-01-04 09:47:13 +04:00
|
|
|
cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
|
2009-02-11 19:31:25 +03:00
|
|
|
std::string line;
|
|
|
|
while(cmSystemTools::GetLineFromStream(fin, line))
|
|
|
|
{
|
2014-02-25 14:25:41 +04:00
|
|
|
if(MatchesFilterPrefix(line))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-03-11 03:04:11 +04:00
|
|
|
if(this->Match(line, this->RegexWarning) &&
|
|
|
|
!this->Match(line, this->RegexWarningSuppress))
|
2009-02-11 19:31:25 +03:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
2014-02-25 14:25:41 +04:00
|
|
|
bool cmCTestLaunch::MatchesFilterPrefix(std::string const& line) const
|
|
|
|
{
|
|
|
|
if(this->OptionFilterPrefix.size() && cmSystemTools::StringStartsWith(
|
|
|
|
line.c_str(), this->OptionFilterPrefix.c_str()))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
2009-02-11 19:31:25 +03:00
|
|
|
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();
|
|
|
|
}
|
2009-02-12 21:00:22 +03:00
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "cmGlobalGenerator.h"
|
|
|
|
#include "cmLocalGenerator.h"
|
|
|
|
#include "cmMakefile.h"
|
|
|
|
#include "cmake.h"
|
|
|
|
#include <cmsys/auto_ptr.hxx>
|
|
|
|
void cmCTestLaunch::LoadConfig()
|
|
|
|
{
|
|
|
|
cmake cm;
|
|
|
|
cmGlobalGenerator gg;
|
|
|
|
gg.SetCMakeInstance(&cm);
|
|
|
|
cmsys::auto_ptr<cmLocalGenerator> lg(gg.CreateLocalGenerator());
|
|
|
|
cmMakefile* mf = lg->GetMakefile();
|
|
|
|
std::string fname = this->LogDir;
|
|
|
|
fname += "CTestLaunchConfig.cmake";
|
|
|
|
if(cmSystemTools::FileExists(fname.c_str()) &&
|
|
|
|
mf->ReadListFile(0, fname.c_str()))
|
|
|
|
{
|
|
|
|
this->SourceDir = mf->GetSafeDefinition("CTEST_SOURCE_DIRECTORY");
|
|
|
|
cmSystemTools::ConvertToUnixSlashes(this->SourceDir);
|
|
|
|
}
|
|
|
|
}
|