/*========================================================================= 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 "cmCTestBuildHandler.h" #include "cmCTest.h" #include "cmake.h" #include "cmMakefile.h" #include "cmLocalGenerator.h" #include "cmGlobalGenerator.h" #include "cmGeneratedFileStream.h" #include "cmXMLSafe.h" #include "cmFileTimeComparison.h" //#include #include #include // used for sleep #ifdef _WIN32 #include "windows.h" #endif #include #include #include #include #if defined(__BORLANDC__) # pragma warn -8060 /* possibly incorrect assignment */ #endif static const char* cmCTestErrorMatches[] = { "^[Bb]us [Ee]rror", "^[Ss]egmentation [Vv]iolation", "^[Ss]egmentation [Ff]ault", "([^ :]+):([0-9]+): ([^ \\t])", "([^:]+): error[ \\t]*[0-9]+[ \\t]*:", "^Error ([0-9]+):", "^Fatal", "^Error: ", "^Error ", "[0-9] ERROR: ", "^\"[^\"]+\", line [0-9]+: [^Ww]", "^cc[^C]*CC: ERROR File = ([^,]+), Line = ([0-9]+)", "^ld([^:])*:([ \\t])*ERROR([^:])*:", "^ild:([ \\t])*\\(undefined symbol\\)", "([^ :]+) : (error|fatal error|catastrophic error)", "([^:]+): (Error:|error|undefined reference|multiply defined)", "([^:]+)\\(([^\\)]+)\\) : (error|fatal error|catastrophic error)", "^fatal error C[0-9]+:", ": syntax error ", "^collect2: ld returned 1 exit status", "ld terminated with signal", "Unsatisfied symbols:", "^Unresolved:", "Undefined symbols:", "^Undefined[ \\t]+first referenced", "^CMake Error:", ":[ \\t]cannot find", ":[ \\t]can't find", ": \\*\\*\\* No rule to make target \\`.*\\'. Stop", ": \\*\\*\\* No targets specified and no makefile found", ": Invalid loader fixup for symbol", ": Invalid fixups exist", ": Can't find library for", ": internal link edit command failed", ": Unrecognized option \\`.*\\'", "\", line [0-9]+\\.[0-9]+: [0-9]+-[0-9]+ \\([^WI]\\)", "ld: 0706-006 Cannot find or open library file: -l ", "ild: \\(argument error\\) can't find library argument ::", "^could not be found and will not be loaded.", "s:616 string too big", "make: Fatal error: ", "ld: 0711-993 Error occurred while writing to the output file:", "ld: fatal: ", "final link failed:", "make: \\*\\*\\*.*Error", "make\\[.*\\]: \\*\\*\\*.*Error", "\\*\\*\\* Error code", "nternal error:", "Makefile:[0-9]+: \\*\\*\\* .* Stop\\.", ": No such file or directory", ": Invalid argument", "^The project cannot be built\\.", 0 }; static const char* cmCTestErrorExceptions[] = { "instantiated from ", "candidates are:", ": warning", ": \\(Warning\\)", ": note", "makefile:", "Makefile:", ":[ \\t]+Where:", "([^ :]+):([0-9]+): Warning", "------ Build started: .* ------", 0 }; static const char* cmCTestWarningMatches[] = { "([^ :]+):([0-9]+): warning:", "([^ :]+):([0-9]+): note:", "^cc[^C]*CC: WARNING File = ([^,]+), Line = ([0-9]+)", "^ld([^:])*:([ \\t])*WARNING([^:])*:", "([^:]+): warning ([0-9]+):", "^\"[^\"]+\", line [0-9]+: [Ww](arning|arnung)", "([^:]+): warning[ \\t]*[0-9]+[ \\t]*:", "^(Warning|Warnung) ([0-9]+):", "^(Warning|Warnung) ", "WARNING: ", "([^ :]+) : warning", "([^:]+): warning", "\", line [0-9]+\\.[0-9]+: [0-9]+-[0-9]+ \\([WI]\\)", "^cxx: Warning:", ".*file: .* has no symbols", "([^ :]+):([0-9]+): (Warning|Warnung)", "\\([0-9]*\\): remark #[0-9]*", "\".*\", line [0-9]+: remark\\([0-9]*\\):", "cc-[0-9]* CC: REMARK File = .*, Line = [0-9]*", 0 }; static const char* cmCTestWarningExceptions[] = { "/usr/.*/X11/Xlib\\.h:[0-9]+: war.*: ANSI C\\+\\+ forbids declaration", "/usr/.*/X11/Xutil\\.h:[0-9]+: war.*: ANSI C\\+\\+ forbids declaration", "/usr/.*/X11/XResource\\.h:[0-9]+: war.*: ANSI C\\+\\+ forbids declaration", "WARNING 84 :", "WARNING 47 :", "makefile:", "Makefile:", "warning: Clock skew detected. Your build may be incomplete.", "/usr/openwin/include/GL/[^:]+:", "bind_at_load", "XrmQGetResource", "IceFlush", "warning LNK4089: all references to [^ \\t]+ discarded by .OPT:REF", "ld32: WARNING 85: definition of dataKey in", "cc: warning 422: Unknown option \"\\+b", "_with_warning_C", 0 }; struct cmCTestBuildCompileErrorWarningRex { const char* RegularExpressionString; int FileIndex; int LineIndex; }; static cmCTestBuildCompileErrorWarningRex cmCTestWarningErrorFileLine[] = { { "^Warning W[0-9]+ ([a-zA-Z.\\:/0-9_+ ~-]+) ([0-9]+):", 1, 2 }, { "^([a-zA-Z./0-9_+ ~-]+):([0-9]+):", 1, 2 }, { "^([a-zA-Z.\\:/0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, { "^[0-9]+>([a-zA-Z.\\:/0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, { "^([a-zA-Z./0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, { "\"([a-zA-Z./0-9_+ ~-]+)\", line ([0-9]+)", 1, 2 }, { "File = ([a-zA-Z./0-9_+ ~-]+), Line = ([0-9]+)", 1, 2 }, { 0, 0, 0 } }; //---------------------------------------------------------------------- cmCTestBuildHandler::cmCTestBuildHandler() { this->MaxPreContext = 6; this->MaxPostContext = 6; this->MaxErrors = 50; this->MaxWarnings = 50; this->LastErrorOrWarning = this->ErrorsAndWarnings.end(); this->UseCTestLaunch = false; } //---------------------------------------------------------------------- void cmCTestBuildHandler::Initialize() { this->Superclass::Initialize(); this->StartBuild = ""; this->EndBuild = ""; this->CustomErrorMatches.clear(); this->CustomErrorExceptions.clear(); this->CustomWarningMatches.clear(); this->CustomWarningExceptions.clear(); this->ReallyCustomWarningMatches.clear(); this->ReallyCustomWarningExceptions.clear(); this->ErrorWarningFileLineRegex.clear(); this->ErrorMatchRegex.clear(); this->ErrorExceptionRegex.clear(); this->WarningMatchRegex.clear(); this->WarningExceptionRegex.clear(); this->BuildProcessingQueue.clear(); this->BuildProcessingErrorQueue.clear(); this->BuildOutputLogSize = 0; this->CurrentProcessingLine.clear(); this->SimplifySourceDir = ""; this->SimplifyBuildDir = ""; this->OutputLineCounter = 0; this->ErrorsAndWarnings.clear(); this->LastErrorOrWarning = this->ErrorsAndWarnings.end(); this->PostContextCount = 0; this->MaxPreContext = 6; this->MaxPostContext = 6; this->PreContext.clear(); this->TotalErrors = 0; this->TotalWarnings = 0; this->LastTickChar = 0; this->ErrorQuotaReached = false; this->WarningQuotaReached = false; this->MaxErrors = 50; this->MaxWarnings = 50; this->UseCTestLaunch = false; } //---------------------------------------------------------------------- void cmCTestBuildHandler::PopulateCustomVectors(cmMakefile *mf) { this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_ERROR_MATCH", this->CustomErrorMatches); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_ERROR_EXCEPTION", this->CustomErrorExceptions); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_WARNING_MATCH", this->CustomWarningMatches); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_WARNING_EXCEPTION", this->CustomWarningExceptions); this->CTest->PopulateCustomInteger(mf, "CTEST_CUSTOM_MAXIMUM_NUMBER_OF_ERRORS", this->MaxErrors); this->CTest->PopulateCustomInteger(mf, "CTEST_CUSTOM_MAXIMUM_NUMBER_OF_WARNINGS", this->MaxWarnings); // Record the user-specified custom warning rules. if(const char* customWarningMatchers = mf->GetDefinition("CTEST_CUSTOM_WARNING_MATCH")) { cmSystemTools::ExpandListArgument(customWarningMatchers, this->ReallyCustomWarningMatches); } if(const char* customWarningExceptions = mf->GetDefinition("CTEST_CUSTOM_WARNING_EXCEPTION")) { cmSystemTools::ExpandListArgument(customWarningExceptions, this->ReallyCustomWarningExceptions); } } //---------------------------------------------------------------------- //clearly it would be nice if this were broken up into a few smaller //functions and commented... int cmCTestBuildHandler::ProcessHandler() { cmCTestLog(this->CTest, HANDLER_OUTPUT, "Build project" << std::endl); // do we have time for this if (this->CTest->GetRemainingTimeAllowed() < 120) { return 0; } int entry; for ( entry = 0; cmCTestWarningErrorFileLine[entry].RegularExpressionString; ++ entry ) { cmCTestBuildHandler::cmCTestCompileErrorWarningRex r; if ( r.RegularExpression.compile( cmCTestWarningErrorFileLine[entry].RegularExpressionString) ) { r.FileIndex = cmCTestWarningErrorFileLine[entry].FileIndex; r.LineIndex = cmCTestWarningErrorFileLine[entry].LineIndex; this->ErrorWarningFileLineRegex.push_back(r); } else { cmCTestLog(this->CTest, ERROR_MESSAGE, "Problem Compiling regular expression: " << cmCTestWarningErrorFileLine[entry].RegularExpressionString << std::endl); } } // Determine build command and build directory const std::string &makeCommand = this->CTest->GetCTestConfiguration("MakeCommand"); cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "MakeCommand:" << makeCommand << "\n"); if ( makeCommand.size() == 0 ) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find MakeCommand key in the DartConfiguration.tcl" << std::endl); return -1; } const std::string &buildDirectory = this->CTest->GetCTestConfiguration("BuildDirectory"); if ( buildDirectory.size() == 0 ) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find BuildDirectory key in the DartConfiguration.tcl" << std::endl); return -1; } std::string const& useLaunchers = this->CTest->GetCTestConfiguration("UseLaunchers"); this->UseCTestLaunch = cmSystemTools::IsOn(useLaunchers.c_str()); // Create a last build log cmGeneratedFileStream ofs; double elapsed_time_start = cmSystemTools::GetTime(); if ( !this->StartLogFile("Build", ofs) ) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create build log file" << std::endl); } // Create lists of regular expression strings for errors, error exceptions, // warnings and warning exceptions. std::vector::size_type cc; for ( cc = 0; cmCTestErrorMatches[cc]; cc ++ ) { this->CustomErrorMatches.push_back(cmCTestErrorMatches[cc]); } for ( cc = 0; cmCTestErrorExceptions[cc]; cc ++ ) { this->CustomErrorExceptions.push_back(cmCTestErrorExceptions[cc]); } for ( cc = 0; cmCTestWarningMatches[cc]; cc ++ ) { this->CustomWarningMatches.push_back(cmCTestWarningMatches[cc]); } for ( cc = 0; cmCTestWarningExceptions[cc]; cc ++ ) { this->CustomWarningExceptions.push_back(cmCTestWarningExceptions[cc]); } // Pre-compile regular expressions objects for all regular expressions std::vector::iterator it; #define cmCTestBuildHandlerPopulateRegexVector(strings, regexes) \ regexes.clear(); \ cmCTestLog(this->CTest, DEBUG, this << "Add " #regexes \ << std::endl); \ for ( it = strings.begin(); it != strings.end(); ++it ) \ { \ cmCTestLog(this->CTest, DEBUG, "Add " #strings ": " \ << it->c_str() << std::endl); \ regexes.push_back(it->c_str()); \ } cmCTestBuildHandlerPopulateRegexVector( this->CustomErrorMatches, this->ErrorMatchRegex); cmCTestBuildHandlerPopulateRegexVector( this->CustomErrorExceptions, this->ErrorExceptionRegex); cmCTestBuildHandlerPopulateRegexVector( this->CustomWarningMatches, this->WarningMatchRegex); cmCTestBuildHandlerPopulateRegexVector( this->CustomWarningExceptions, this->WarningExceptionRegex); // Determine source and binary tree substitutions to simplify the output. this->SimplifySourceDir = ""; this->SimplifyBuildDir = ""; if ( this->CTest->GetCTestConfiguration("SourceDirectory").size() > 20 ) { std::string srcdir = this->CTest->GetCTestConfiguration("SourceDirectory") + "/"; std::string srcdirrep; for ( cc = srcdir.size()-2; cc > 0; cc -- ) { if ( srcdir[cc] == '/' ) { srcdirrep = srcdir.c_str() + cc; srcdirrep = "/..." + srcdirrep; srcdir = srcdir.substr(0, cc+1); break; } } this->SimplifySourceDir = srcdir; } if ( this->CTest->GetCTestConfiguration("BuildDirectory").size() > 20 ) { std::string bindir = this->CTest->GetCTestConfiguration("BuildDirectory") + "/"; std::string bindirrep; for ( cc = bindir.size()-2; cc > 0; cc -- ) { if ( bindir[cc] == '/' ) { bindirrep = bindir.c_str() + cc; bindirrep = "/..." + bindirrep; bindir = bindir.substr(0, cc+1); break; } } this->SimplifyBuildDir = bindir; } // Ok, let's do the build // Remember start build time this->StartBuild = this->CTest->CurrentTime(); this->StartBuildTime = cmSystemTools::GetTime(); int retVal = 0; int res = cmsysProcess_State_Exited; if ( !this->CTest->GetShowOnly() ) { res = this->RunMakeCommand(makeCommand.c_str(), &retVal, buildDirectory.c_str(), 0, ofs); } else { cmCTestLog(this->CTest, DEBUG, "Build with command: " << makeCommand << std::endl); } // Remember end build time and calculate elapsed time this->EndBuild = this->CTest->CurrentTime(); this->EndBuildTime = cmSystemTools::GetTime(); double elapsed_build_time = cmSystemTools::GetTime() - elapsed_time_start; if (res != cmsysProcess_State_Exited || retVal ) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Error(s) when building project" << std::endl); } // Cleanups strings in the errors and warnings list. t_ErrorsAndWarningsVector::iterator evit; if ( !this->SimplifySourceDir.empty() ) { for ( evit = this->ErrorsAndWarnings.begin(); evit != this->ErrorsAndWarnings.end(); ++ evit ) { cmSystemTools::ReplaceString( evit->Text, this->SimplifySourceDir.c_str(), "/.../"); cmSystemTools::ReplaceString( evit->PreContext, this->SimplifySourceDir.c_str(), "/.../"); cmSystemTools::ReplaceString( evit->PostContext, this->SimplifySourceDir.c_str(), "/.../"); } } if ( !this->SimplifyBuildDir.empty() ) { for ( evit = this->ErrorsAndWarnings.begin(); evit != this->ErrorsAndWarnings.end(); ++ evit ) { cmSystemTools::ReplaceString( evit->Text, this->SimplifyBuildDir.c_str(), "/.../"); cmSystemTools::ReplaceString( evit->PreContext, this->SimplifyBuildDir.c_str(), "/.../"); cmSystemTools::ReplaceString( evit->PostContext, this->SimplifyBuildDir.c_str(), "/.../"); } } // Generate XML output cmGeneratedFileStream xofs; if(!this->StartResultingXML(cmCTest::PartBuild, "Build", xofs)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create build XML file" << std::endl); return -1; } this->GenerateXMLHeader(xofs); if(this->UseCTestLaunch) { this->GenerateXMLLaunched(xofs); } else { this->GenerateXMLLogScraped(xofs); } this->GenerateXMLFooter(xofs, elapsed_build_time); // Display message about number of errors and warnings cmCTestLog(this->CTest, HANDLER_OUTPUT, " " << this->TotalErrors << (this->TotalErrors >= this->MaxErrors ? " or more" : "") << " Compiler errors" << std::endl); cmCTestLog(this->CTest, HANDLER_OUTPUT, " " << this->TotalWarnings << (this->TotalWarnings >= this->MaxWarnings ? " or more" : "") << " Compiler warnings" << std::endl); return retVal; } //---------------------------------------------------------------------------- void cmCTestBuildHandler::GenerateXMLHeader(std::ostream& os) { this->CTest->StartXML(os, this->AppendXML); os << "\n" << "\t" << this->StartBuild << "\n" << "\t" << static_cast(this->StartBuildTime) << "\n" << "" << cmXMLSafe( this->CTest->GetCTestConfiguration("MakeCommand")) << "" << std::endl; } //---------------------------------------------------------------------------- class cmCTestBuildHandler::FragmentCompare { public: FragmentCompare(cmFileTimeComparison* ftc): FTC(ftc) {} bool operator()(std::string const& l, std::string const& r) { // Order files by modification time. Use lexicographic order // among files with the same time. int result; if(this->FTC->FileTimeCompare(l.c_str(), r.c_str(), &result) && result != 0) { return result < 0; } else { return l < r; } } private: cmFileTimeComparison* FTC; }; //---------------------------------------------------------------------------- void cmCTestBuildHandler::GenerateXMLLaunched(std::ostream& os) { if(this->CTestLaunchDir.empty()) { return; } // Sort XML fragments in chronological order. cmFileTimeComparison ftc; FragmentCompare fragmentCompare(&ftc); typedef std::set Fragments; Fragments fragments(fragmentCompare); // Identify fragments on disk. cmsys::Directory launchDir; launchDir.Load(this->CTestLaunchDir.c_str()); unsigned long n = launchDir.GetNumberOfFiles(); for(unsigned long i=0; i < n; ++i) { const char* fname = launchDir.GetFile(i); if(this->IsLaunchedErrorFile(fname)) { fragments.insert(this->CTestLaunchDir + "/" + fname); ++this->TotalErrors; } else if(this->IsLaunchedWarningFile(fname)) { fragments.insert(this->CTestLaunchDir + "/" + fname); ++this->TotalWarnings; } } // Copy the fragments into the final XML file. for(Fragments::const_iterator fi = fragments.begin(); fi != fragments.end(); ++fi) { this->GenerateXMLLaunchedFragment(os, fi->c_str()); } } //---------------------------------------------------------------------------- void cmCTestBuildHandler::GenerateXMLLogScraped(std::ostream& os) { std::vector& ew = this->ErrorsAndWarnings; std::vector::iterator it; // only report the first 50 warnings and first 50 errors unsigned short numErrorsAllowed = this->MaxErrors; unsigned short numWarningsAllowed = this->MaxWarnings; std::string srcdir = this->CTest->GetCTestConfiguration("SourceDirectory"); // make sure the source dir is in the correct case on windows // via a call to collapse full path. srcdir = cmSystemTools::CollapseFullPath(srcdir.c_str()); srcdir += "/"; for ( it = ew.begin(); it != ew.end() && (numErrorsAllowed || numWarningsAllowed); it++ ) { cmCTestBuildErrorWarning *cm = &(*it); if ((cm->Error && numErrorsAllowed) || (!cm->Error && numWarningsAllowed)) { if (cm->Error) { numErrorsAllowed--; } else { numWarningsAllowed--; } os << "\t<" << (cm->Error ? "Error" : "Warning") << ">\n" << "\t\t" << cm->LogLine << "\n" << "\t\t" << cmXMLSafe(cm->Text).Quotes(false) << "\n" << std::endl; std::vector::iterator rit; for ( rit = this->ErrorWarningFileLineRegex.begin(); rit != this->ErrorWarningFileLineRegex.end(); ++ rit ) { cmsys::RegularExpression* re = &rit->RegularExpression; if ( re->find(cm->Text.c_str() ) ) { cm->SourceFile = re->match(rit->FileIndex); // At this point we need to make this->SourceFile relative to // the source root of the project, so cvs links will work cmSystemTools::ConvertToUnixSlashes(cm->SourceFile); if(cm->SourceFile.find("/.../") != cm->SourceFile.npos) { cmSystemTools::ReplaceString(cm->SourceFile, "/.../", ""); std::string::size_type p = cm->SourceFile.find("/"); if(p != cm->SourceFile.npos) { cm->SourceFile = cm->SourceFile.substr( p+1, cm->SourceFile.size()-p); } } else { // make sure it is a full path with the correct case cm->SourceFile = cmSystemTools::CollapseFullPath( cm->SourceFile.c_str()); cmSystemTools::ReplaceString( cm->SourceFile, srcdir.c_str(), ""); } cm->LineNumber = atoi(re->match(rit->LineIndex).c_str()); break; } } if ( !cm->SourceFile.empty() && cm->LineNumber >= 0 ) { if ( cm->SourceFile.size() > 0 ) { os << "\t\t" << cm->SourceFile << "" << std::endl; } if ( cm->SourceFileTail.size() > 0 ) { os << "\t\t" << cm->SourceFileTail << "" << std::endl; } if ( cm->LineNumber >= 0 ) { os << "\t\t" << cm->LineNumber << "" << std::endl; } } os << "\t\t" << cmXMLSafe(cm->PreContext).Quotes(false) << "\n" << "\t\t" << cmXMLSafe(cm->PostContext).Quotes(false); // is this the last warning or error, if so notify if ((cm->Error && !numErrorsAllowed) || (!cm->Error && !numWarningsAllowed)) { os << "\nThe maximum number of reported warnings or errors has been " "reached!!!\n"; } os << "\n" << "\t\t0\n" << "Error ? "Error" : "Warning") << ">\n\n" << std::endl; } } } //---------------------------------------------------------------------------- void cmCTestBuildHandler::GenerateXMLFooter(std::ostream& os, double elapsed_build_time) { os << "\t\n\t\n" << "\t" << this->EndBuild << "\n" << "\t" << static_cast(this->EndBuildTime) << "\n" << "" << static_cast(elapsed_build_time/6)/10.0 << "" << "" << std::endl; this->CTest->EndXML(os); } //---------------------------------------------------------------------------- void cmCTestBuildHandler::GenerateXMLLaunchedFragment(std::ostream& os, const char* fname) { std::ifstream fin(fname, std::ios::in | std::ios::binary); std::string line; while(cmSystemTools::GetLineFromStream(fin, line)) { os << line << "\n"; } } //---------------------------------------------------------------------------- bool cmCTestBuildHandler::IsLaunchedErrorFile(const char* fname) { // error-{hash}.xml return (strncmp(fname, "error-", 6) == 0 && strcmp(fname+strlen(fname)-4, ".xml") == 0); } //---------------------------------------------------------------------------- bool cmCTestBuildHandler::IsLaunchedWarningFile(const char* fname) { // warning-{hash}.xml return (strncmp(fname, "warning-", 8) == 0 && strcmp(fname+strlen(fname)-4, ".xml") == 0); } //###################################################################### //###################################################################### //###################################################################### //###################################################################### //---------------------------------------------------------------------------- class cmCTestBuildHandler::LaunchHelper { public: LaunchHelper(cmCTestBuildHandler* handler); ~LaunchHelper(); private: cmCTestBuildHandler* Handler; cmCTest* CTest; void WriteLauncherConfig(); void WriteScrapeMatchers(const char* purpose, std::vector const& matchers); }; //---------------------------------------------------------------------------- cmCTestBuildHandler::LaunchHelper::LaunchHelper(cmCTestBuildHandler* handler): Handler(handler), CTest(handler->CTest) { std::string tag = this->CTest->GetCurrentTag(); if(tag.empty()) { // This is not for a dashboard submission, so there is no XML. // Skip enabling the launchers. this->Handler->UseCTestLaunch = false; } else { // Compute a directory in which to store launcher fragments. std::string& launchDir = this->Handler->CTestLaunchDir; launchDir = this->CTest->GetBinaryDir(); launchDir += "/Testing/"; launchDir += tag; launchDir += "/Build"; // Clean out any existing launcher fragments. cmSystemTools::RemoveADirectory(launchDir.c_str()); if(this->Handler->UseCTestLaunch) { // Enable launcher fragments. cmSystemTools::MakeDirectory(launchDir.c_str()); this->WriteLauncherConfig(); std::string launchEnv = "CTEST_LAUNCH_LOGS="; launchEnv += launchDir; cmSystemTools::PutEnv(launchEnv.c_str()); } } // If not using launchers, make sure they passthru. if(!this->Handler->UseCTestLaunch) { cmSystemTools::UnsetEnv("CTEST_LAUNCH_LOGS"); } } //---------------------------------------------------------------------------- cmCTestBuildHandler::LaunchHelper::~LaunchHelper() { if(this->Handler->UseCTestLaunch) { cmSystemTools::UnsetEnv("CTEST_LAUNCH_LOGS"); } } //---------------------------------------------------------------------------- void cmCTestBuildHandler::LaunchHelper::WriteLauncherConfig() { this->WriteScrapeMatchers("Warning", this->Handler->ReallyCustomWarningMatches); this->WriteScrapeMatchers("WarningSuppress", this->Handler->ReallyCustomWarningExceptions); // Give some testing configuration information to the launcher. std::string fname = this->Handler->CTestLaunchDir; fname += "/CTestLaunchConfig.cmake"; cmGeneratedFileStream fout(fname.c_str()); std::string srcdir = this->CTest->GetCTestConfiguration("SourceDirectory"); fout << "set(CTEST_SOURCE_DIRECTORY \"" << srcdir << "\")\n"; } //---------------------------------------------------------------------------- void cmCTestBuildHandler::LaunchHelper ::WriteScrapeMatchers(const char* purpose, std::vector const& matchers) { if(matchers.empty()) { return; } std::string fname = this->Handler->CTestLaunchDir; fname += "/Custom"; fname += purpose; fname += ".txt"; cmGeneratedFileStream fout(fname.c_str()); for(std::vector::const_iterator mi = matchers.begin(); mi != matchers.end(); ++mi) { fout << *mi << "\n"; } } //---------------------------------------------------------------------- int cmCTestBuildHandler::RunMakeCommand(const char* command, int* retVal, const char* dir, int timeout, std::ofstream& ofs) { // First generate the command and arguments std::vector args = cmSystemTools::ParseArguments(command); if(args.size() < 1) { return false; } std::vector argv; for(std::vector::const_iterator a = args.begin(); a != args.end(); ++a) { argv.push_back(a->c_str()); } argv.push_back(0); cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Run command:"); std::vector::iterator ait; for ( ait = argv.begin(); ait != argv.end() && *ait; ++ ait ) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " \"" << *ait << "\""); } cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl); // Optionally use make rule launchers to record errors and warnings. LaunchHelper launchHelper(this); static_cast(launchHelper); // Now create process object cmsysProcess* cp = cmsysProcess_New(); cmsysProcess_SetCommand(cp, &*argv.begin()); cmsysProcess_SetWorkingDirectory(cp, dir); cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1); cmsysProcess_SetTimeout(cp, timeout); cmsysProcess_Execute(cp); // Initialize tick's std::string::size_type tick = 0; const std::string::size_type tick_len = 1024; char* data; int length; cmCTestLog(this->CTest, HANDLER_OUTPUT, " Each symbol represents " << tick_len << " bytes of output." << std::endl << (this->UseCTestLaunch? "" : " '!' represents an error and '*' a warning.\n") << " " << std::flush); // Initialize building structures this->BuildProcessingQueue.clear(); this->OutputLineCounter = 0; this->ErrorsAndWarnings.clear(); this->TotalErrors = 0; this->TotalWarnings = 0; this->BuildOutputLogSize = 0; this->LastTickChar = '.'; this->WarningQuotaReached = false; this->ErrorQuotaReached = false; // For every chunk of data int res; while((res = cmsysProcess_WaitForData(cp, &data, &length, 0))) { // Replace '\0' with '\n', since '\0' does not really make sense. This is // for Visual Studio output for(int cc =0; cc < length; ++cc) { if(data[cc] == 0) { data[cc] = '\n'; } } // Process the chunk of data if ( res == cmsysProcess_Pipe_STDERR ) { this->ProcessBuffer(data, length, tick, tick_len, ofs, &this->BuildProcessingErrorQueue); } else { this->ProcessBuffer(data, length, tick, tick_len, ofs, &this->BuildProcessingQueue); } } this->ProcessBuffer(0, 0, tick, tick_len, ofs, &this->BuildProcessingQueue); this->ProcessBuffer(0, 0, tick, tick_len, ofs, &this->BuildProcessingErrorQueue); cmCTestLog(this->CTest, OUTPUT, " Size of output: " << int(this->BuildOutputLogSize / 1024.0) << "K" << std::endl); // Properly handle output of the build command cmsysProcess_WaitForExit(cp, 0); int result = cmsysProcess_GetState(cp); if(result == cmsysProcess_State_Exited) { if (retVal) { *retVal = cmsysProcess_GetExitValue(cp); cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Command exited with the value: " << *retVal << std::endl); // if a non zero return value if (*retVal) { // If there was an error running command, report that on the // dashboard. cmCTestBuildErrorWarning errorwarning; errorwarning.LogLine = 1; errorwarning.Text = "*** WARNING non-zero return value in ctest from: "; errorwarning.Text += argv[0]; errorwarning.PreContext = ""; errorwarning.PostContext = ""; errorwarning.Error = false; this->ErrorsAndWarnings.push_back(errorwarning); this->TotalWarnings ++; } } } else if(result == cmsysProcess_State_Exception) { if (retVal) { *retVal = cmsysProcess_GetExitException(cp); cmCTestLog(this->CTest, WARNING, "There was an exception: " << *retVal << std::endl); } } else if(result == cmsysProcess_State_Expired) { cmCTestLog(this->CTest, WARNING, "There was a timeout" << std::endl); } else if(result == cmsysProcess_State_Error) { // If there was an error running command, report that on the dashboard. cmCTestBuildErrorWarning errorwarning; errorwarning.LogLine = 1; errorwarning.Text = "*** ERROR executing: "; errorwarning.Text += cmsysProcess_GetErrorString(cp); errorwarning.PreContext = ""; errorwarning.PostContext = ""; errorwarning.Error = true; this->ErrorsAndWarnings.push_back(errorwarning); this->TotalErrors ++; cmCTestLog(this->CTest, ERROR_MESSAGE, "There was an error: " << cmsysProcess_GetErrorString(cp) << std::endl); } cmsysProcess_Delete(cp); return result; } //###################################################################### //###################################################################### //###################################################################### //###################################################################### //---------------------------------------------------------------------- void cmCTestBuildHandler::ProcessBuffer(const char* data, int length, size_t& tick, size_t tick_len, std::ofstream& ofs, t_BuildProcessingQueueType* queue) { const std::string::size_type tick_line_len = 50; const char* ptr; for ( ptr = data; ptr < data+length; ptr ++ ) { queue->push_back(*ptr); } this->BuildOutputLogSize += length; // until there are any lines left in the buffer while ( 1 ) { // Find the end of line t_BuildProcessingQueueType::iterator it; for ( it = queue->begin(); it != queue->end(); ++ it ) { if ( *it == '\n' ) { break; } } // Once certain number of errors or warnings reached, ignore future errors // or warnings. if ( this->TotalWarnings >= this->MaxWarnings ) { this->WarningQuotaReached = true; } if ( this->TotalErrors >= this->MaxErrors ) { this->ErrorQuotaReached = true; } // If the end of line was found if ( it != queue->end() ) { // Create a contiguous array for the line this->CurrentProcessingLine.clear(); t_BuildProcessingQueueType::iterator cit; for ( cit = queue->begin(); cit != it; ++cit ) { this->CurrentProcessingLine.push_back(*cit); } this->CurrentProcessingLine.push_back(0); const char* line = &*this->CurrentProcessingLine.begin(); // Process the line int lineType = this->ProcessSingleLine(line); // Erase the line from the queue queue->erase(queue->begin(), it+1); // Depending on the line type, produce error or warning, or nothing cmCTestBuildErrorWarning errorwarning; bool found = false; switch ( lineType ) { case b_WARNING_LINE: this->LastTickChar = '*'; errorwarning.Error = false; found = true; this->TotalWarnings ++; break; case b_ERROR_LINE: this->LastTickChar = '!'; errorwarning.Error = true; found = true; this->TotalErrors ++; break; } if ( found ) { // This is an error or warning, so generate report errorwarning.LogLine = static_cast(this->OutputLineCounter+1); errorwarning.Text = line; errorwarning.PreContext = ""; errorwarning.PostContext = ""; // Copy pre-context to report std::deque::iterator pcit; for ( pcit = this->PreContext.begin(); pcit != this->PreContext.end(); ++pcit ) { errorwarning.PreContext += *pcit + "\n"; } this->PreContext.clear(); // Store report this->ErrorsAndWarnings.push_back(errorwarning); this->LastErrorOrWarning = this->ErrorsAndWarnings.end()-1; this->PostContextCount = 0; } else { // This is not an error or warning. // So, figure out if this is a post-context line if ( this->ErrorsAndWarnings.size() && this->LastErrorOrWarning != this->ErrorsAndWarnings.end() && this->PostContextCount < this->MaxPostContext ) { this->PostContextCount ++; this->LastErrorOrWarning->PostContext += line; if ( this->PostContextCount < this->MaxPostContext ) { this->LastErrorOrWarning->PostContext += "\n"; } } else { // Otherwise store pre-context for the next error this->PreContext.push_back(line); if ( this->PreContext.size() > this->MaxPreContext ) { this->PreContext.erase(this->PreContext.begin(), this->PreContext.end()-this->MaxPreContext); } } } this->OutputLineCounter ++; } else { break; } } // Now that the buffer is processed, display missing ticks int tickDisplayed = false; while ( this->BuildOutputLogSize > (tick * tick_len) ) { tick ++; cmCTestLog(this->CTest, HANDLER_OUTPUT, this->LastTickChar); tickDisplayed = true; if ( tick % tick_line_len == 0 && tick > 0 ) { cmCTestLog(this->CTest, HANDLER_OUTPUT, " Size: " << int((this->BuildOutputLogSize / 1024.0) + 1) << "K" << std::endl << " "); } } if ( tickDisplayed ) { this->LastTickChar = '.'; } // And if this is verbose output, display the content of the chunk cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, cmCTestLogWrite(data, length)); // Always store the chunk to the file ofs << cmCTestLogWrite(data, length); } //---------------------------------------------------------------------- int cmCTestBuildHandler::ProcessSingleLine(const char* data) { if(this->UseCTestLaunch) { // No log scraping when using launchers. return b_REGULAR_LINE; } cmCTestLog(this->CTest, DEBUG, "Line: [" << data << "]" << std::endl); std::vector::iterator it; int warningLine = 0; int errorLine = 0; // Check for regular expressions if ( !this->ErrorQuotaReached ) { // Errors int wrxCnt = 0; for ( it = this->ErrorMatchRegex.begin(); it != this->ErrorMatchRegex.end(); ++ it ) { if ( it->find(data) ) { errorLine = 1; cmCTestLog(this->CTest, DEBUG, " Error Line: " << data << " (matches: " << this->CustomErrorMatches[wrxCnt] << ")" << std::endl); break; } wrxCnt ++; } // Error exceptions wrxCnt = 0; for ( it = this->ErrorExceptionRegex.begin(); it != this->ErrorExceptionRegex.end(); ++ it ) { if ( it->find(data) ) { errorLine = 0; cmCTestLog(this->CTest, DEBUG, " Not an error Line: " << data << " (matches: " << this->CustomErrorExceptions[wrxCnt] << ")" << std::endl); break; } wrxCnt ++; } } if ( !this->WarningQuotaReached ) { // Warnings int wrxCnt = 0; for ( it = this->WarningMatchRegex.begin(); it != this->WarningMatchRegex.end(); ++ it ) { if ( it->find(data) ) { warningLine = 1; cmCTestLog(this->CTest, DEBUG, " Warning Line: " << data << " (matches: " << this->CustomWarningMatches[wrxCnt] << ")" << std::endl); break; } wrxCnt ++; } wrxCnt = 0; // Warning exceptions for ( it = this->WarningExceptionRegex.begin(); it != this->WarningExceptionRegex.end(); ++ it ) { if ( it->find(data) ) { warningLine = 0; cmCTestLog(this->CTest, DEBUG, " Not a warning Line: " << data << " (matches: " << this->CustomWarningExceptions[wrxCnt] << ")" << std::endl); break; } wrxCnt ++; } } if ( errorLine ) { return b_ERROR_LINE; } if ( warningLine ) { return b_WARNING_LINE; } return b_REGULAR_LINE; }