Resync
[CMakeLuaTailorHgBridge.git] / CMakeLua / Source / CTest / cmCTestLaunch.cxx
blob49653189991536397c0f6d09c204955962ff8412
1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmCTestLaunch.cxx,v $
5 Language: C++
6 Date: $Date: 2009-02-12 18:00:22 $
7 Version: $Revision: 1.3 $
9 Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
10 See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
12 This software is distributed WITHOUT ANY WARRANTY; without even
13 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 PURPOSE. See the above copyright notices for more information.
16 =========================================================================*/
17 #include "cmCTestLaunch.h"
19 #include "cmGeneratedFileStream.h"
20 #include "cmSystemTools.h"
21 #include "cmXMLSafe.h"
22 #include "cmake.h"
24 #include <cmsys/MD5.h>
25 #include <cmsys/Process.h>
26 #include <cmsys/RegularExpression.hxx>
28 //----------------------------------------------------------------------------
29 cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv)
31 this->Passthru = true;
32 this->Process = 0;
33 this->ExitCode = 1;
34 this->CWD = cmSystemTools::GetCurrentWorkingDirectory();
36 if(!this->ParseArguments(argc, argv))
38 return;
41 this->ComputeFileNames();
43 this->ScrapeRulesLoaded = false;
44 this->HaveOut = false;
45 this->HaveErr = false;
46 this->Process = cmsysProcess_New();
49 //----------------------------------------------------------------------------
50 cmCTestLaunch::~cmCTestLaunch()
52 cmsysProcess_Delete(this->Process);
53 if(!this->Passthru)
55 cmSystemTools::RemoveFile(this->LogOut.c_str());
56 cmSystemTools::RemoveFile(this->LogErr.c_str());
60 //----------------------------------------------------------------------------
61 bool cmCTestLaunch::ParseArguments(int argc, const char* const* argv)
63 // Launcher options occur first and are separated from the real
64 // command line by a '--' option.
65 enum Doing { DoingNone,
66 DoingOutput,
67 DoingSource,
68 DoingLanguage,
69 DoingTargetName,
70 DoingTargetType,
71 DoingBuildDir,
72 DoingCount };
73 Doing doing = DoingNone;
74 int arg0 = 0;
75 for(int i=1; !arg0 && i < argc; ++i)
77 const char* arg = argv[i];
78 if(strcmp(arg, "--") == 0)
80 arg0 = i+1;
82 else if(strcmp(arg, "--output") == 0)
84 doing = DoingOutput;
86 else if(strcmp(arg, "--source") == 0)
88 doing = DoingSource;
90 else if(strcmp(arg, "--language") == 0)
92 doing = DoingLanguage;
94 else if(strcmp(arg, "--target-name") == 0)
96 doing = DoingTargetName;
98 else if(strcmp(arg, "--target-type") == 0)
100 doing = DoingTargetType;
102 else if(strcmp(arg, "--build-dir") == 0)
104 doing = DoingBuildDir;
106 else if(doing == DoingOutput)
108 this->OptionOutput = arg;
109 doing = DoingNone;
111 else if(doing == DoingSource)
113 this->OptionSource = arg;
114 doing = DoingNone;
116 else if(doing == DoingLanguage)
118 this->OptionLanguage = arg;
119 if(this->OptionLanguage == "CXX")
121 this->OptionLanguage = "C++";
123 doing = DoingNone;
125 else if(doing == DoingTargetName)
127 this->OptionTargetName = arg;
128 doing = DoingNone;
130 else if(doing == DoingTargetType)
132 this->OptionTargetType = arg;
133 doing = DoingNone;
135 else if(doing == DoingBuildDir)
137 this->OptionBuildDir = arg;
138 doing = DoingNone;
142 // Extract the real command line.
143 if(arg0)
145 this->RealArgC = argc - arg0;
146 this->RealArgV = argv + arg0;
147 for(int i=0; i < this->RealArgC; ++i)
149 this->HandleRealArg(this->RealArgV[i]);
151 return true;
153 else
155 this->RealArgC = 0;
156 this->RealArgV = 0;
157 std::cerr << "No launch/command separator ('--') found!\n";
158 return false;
162 //----------------------------------------------------------------------------
163 void cmCTestLaunch::HandleRealArg(const char* arg)
165 #ifdef _WIN32
166 // Expand response file arguments.
167 if(arg[0] == '@' && cmSystemTools::FileExists(arg+1))
169 std::ifstream fin(arg+1);
170 std::string line;
171 while(cmSystemTools::GetLineFromStream(fin, line))
173 cmSystemTools::ParseWindowsCommandLine(line.c_str(), this->RealArgs);
175 return;
177 #endif
178 this->RealArgs.push_back(arg);
181 //----------------------------------------------------------------------------
182 void cmCTestLaunch::ComputeFileNames()
184 // We just passthru the behavior of the real command unless the
185 // CTEST_LAUNCH_LOGS environment variable is set.
186 const char* d = getenv("CTEST_LAUNCH_LOGS");
187 if(!(d && *d))
189 return;
191 this->Passthru = false;
193 // The environment variable specifies the directory into which we
194 // generate build logs.
195 this->LogDir = d;
196 cmSystemTools::ConvertToUnixSlashes(this->LogDir);
197 this->LogDir += "/";
199 // We hash the input command working dir and command line to obtain
200 // a repeatable and (probably) unique name for log files.
201 char hash[32];
202 cmsysMD5* md5 = cmsysMD5_New();
203 cmsysMD5_Initialize(md5);
204 cmsysMD5_Append(md5, (unsigned char const*)(this->CWD.c_str()), -1);
205 for(std::vector<std::string>::const_iterator ai = this->RealArgs.begin();
206 ai != this->RealArgs.end(); ++ai)
208 cmsysMD5_Append(md5, (unsigned char const*)ai->c_str(), -1);
210 cmsysMD5_FinalizeHex(md5, hash);
211 cmsysMD5_Delete(md5);
212 this->LogHash.assign(hash, 32);
214 // We store stdout and stderr in temporary log files.
215 this->LogOut = this->LogDir;
216 this->LogOut += "launch-";
217 this->LogOut += this->LogHash;
218 this->LogOut += "-out.txt";
219 this->LogErr = this->LogDir;
220 this->LogErr += "launch-";
221 this->LogErr += this->LogHash;
222 this->LogErr += "-err.txt";
225 //----------------------------------------------------------------------------
226 void cmCTestLaunch::RunChild()
228 // Prepare to run the real command.
229 cmsysProcess* cp = this->Process;
230 cmsysProcess_SetCommand(cp, this->RealArgV);
232 std::ofstream fout;
233 std::ofstream ferr;
234 if(this->Passthru)
236 // In passthru mode we just share the output pipes.
237 cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1);
238 cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1);
240 else
242 // In full mode we record the child output pipes to log files.
243 fout.open(this->LogOut.c_str(),
244 std::ios::out | std::ios::binary);
245 ferr.open(this->LogErr.c_str(),
246 std::ios::out | std::ios::binary);
249 // Run the real command.
250 cmsysProcess_Execute(cp);
252 // Record child stdout and stderr if necessary.
253 if(!this->Passthru)
255 char* data = 0;
256 int length = 0;
257 while(int p = cmsysProcess_WaitForData(cp, &data, &length, 0))
259 if(p == cmsysProcess_Pipe_STDOUT)
261 fout.write(data, length);
262 std::cout.write(data, length);
263 this->HaveOut = true;
265 else if(p == cmsysProcess_Pipe_STDERR)
267 ferr.write(data, length);
268 std::cerr.write(data, length);
269 this->HaveErr = true;
274 // Wait for the real command to finish.
275 cmsysProcess_WaitForExit(cp, 0);
276 this->ExitCode = cmsysProcess_GetExitValue(cp);
279 //----------------------------------------------------------------------------
280 int cmCTestLaunch::Run()
282 if(!this->Process)
284 std::cerr << "Could not allocate cmsysProcess instance!\n";
285 return -1;
288 this->RunChild();
290 if(this->CheckResults())
292 return this->ExitCode;
295 this->LoadConfig();
296 this->WriteXML();
298 return this->ExitCode;
301 //----------------------------------------------------------------------------
302 void cmCTestLaunch::LoadLabels()
304 if(this->OptionBuildDir.empty() || this->OptionTargetName.empty())
306 return;
309 // Labels are listed in per-target files.
310 std::string fname = this->OptionBuildDir;
311 fname += cmake::GetCMakeFilesDirectory();
312 fname += "/";
313 fname += this->OptionTargetName;
314 fname += ".dir/Labels.txt";
316 // We are interested in per-target labels for this source file.
317 std::string source = this->OptionSource;
318 cmSystemTools::ConvertToUnixSlashes(source);
320 // Load the labels file.
321 std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
322 if(!fin) { return; }
323 bool inTarget = true;
324 bool inSource = false;
325 std::string line;
326 while(cmSystemTools::GetLineFromStream(fin, line))
328 if(line.empty() || line[0] == '#')
330 // Ignore blank and comment lines.
331 continue;
333 else if(line[0] == ' ')
335 // Label lines appear indented by one space.
336 if(inTarget || inSource)
338 this->Labels.insert(line.c_str()+1);
341 else if(!this->OptionSource.empty() && !inSource)
343 // Non-indented lines specify a source file name. The first one
344 // is the end of the target-wide labels. Use labels following a
345 // matching source.
346 inTarget = false;
347 inSource = this->SourceMatches(line, source);
349 else
351 return;
356 //----------------------------------------------------------------------------
357 bool cmCTestLaunch::SourceMatches(std::string const& lhs,
358 std::string const& rhs)
360 // TODO: Case sensitivity, UseRelativePaths, etc. Note that both
361 // paths in the comparison get generated by CMake. This is done for
362 // every source in the target, so it should be efficient (cannot use
363 // cmSystemTools::IsSameFile).
364 return lhs == rhs;
367 //----------------------------------------------------------------------------
368 bool cmCTestLaunch::IsError() const
370 return this->ExitCode != 0;
373 //----------------------------------------------------------------------------
374 void cmCTestLaunch::WriteXML()
376 // Name the xml file.
377 std::string logXML = this->LogDir;
378 logXML += this->IsError()? "error-" : "warning-";
379 logXML += this->LogHash;
380 logXML += ".xml";
382 // Use cmGeneratedFileStream to atomically create the report file.
383 cmGeneratedFileStream fxml(logXML.c_str());
384 fxml << "\t<Failure type=\""
385 << (this->IsError()? "Error" : "Warning") << "\">\n";
386 this->WriteXMLAction(fxml);
387 this->WriteXMLCommand(fxml);
388 this->WriteXMLResult(fxml);
389 this->WriteXMLLabels(fxml);
390 fxml << "\t</Failure>\n";
393 //----------------------------------------------------------------------------
394 void cmCTestLaunch::WriteXMLAction(std::ostream& fxml)
396 fxml << "\t\t<!-- Meta-information about the build action -->\n";
397 fxml << "\t\t<Action>\n";
399 // TargetName
400 if(!this->OptionTargetName.empty())
402 fxml << "\t\t\t<TargetName>"
403 << cmXMLSafe(this->OptionTargetName)
404 << "</TargetName>\n";
407 // Language
408 if(!this->OptionLanguage.empty())
410 fxml << "\t\t\t<Language>"
411 << cmXMLSafe(this->OptionLanguage)
412 << "</Language>\n";
415 // SourceFile
416 if(!this->OptionSource.empty())
418 std::string source = this->OptionSource;
419 cmSystemTools::ConvertToUnixSlashes(source);
421 // If file is in source tree use its relative location.
422 if(cmSystemTools::FileIsFullPath(this->SourceDir.c_str()) &&
423 cmSystemTools::FileIsFullPath(source.c_str()) &&
424 cmSystemTools::IsSubDirectory(source.c_str(),
425 this->SourceDir.c_str()))
427 source = cmSystemTools::RelativePath(this->SourceDir.c_str(),
428 source.c_str());
431 fxml << "\t\t\t<SourceFile>"
432 << cmXMLSafe(source)
433 << "</SourceFile>\n";
436 // OutputFile
437 if(!this->OptionOutput.empty())
439 fxml << "\t\t\t<OutputFile>"
440 << cmXMLSafe(this->OptionOutput)
441 << "</OutputFile>\n";
444 // OutputType
445 const char* outputType = 0;
446 if(!this->OptionTargetType.empty())
448 if(this->OptionTargetType == "EXECUTABLE")
450 outputType = "executable";
452 else if(this->OptionTargetType == "SHARED_LIBRARY")
454 outputType = "shared library";
456 else if(this->OptionTargetType == "MODULE_LIBRARY")
458 outputType = "module library";
460 else if(this->OptionTargetType == "STATIC_LIBRARY")
462 outputType = "static library";
465 else if(!this->OptionSource.empty())
467 outputType = "object file";
469 if(outputType)
471 fxml << "\t\t\t<OutputType>"
472 << cmXMLSafe(outputType)
473 << "</OutputType>\n";
476 fxml << "\t\t</Action>\n";
479 //----------------------------------------------------------------------------
480 void cmCTestLaunch::WriteXMLCommand(std::ostream& fxml)
482 fxml << "\n";
483 fxml << "\t\t<!-- Details of command -->\n";
484 fxml << "\t\t<Command>\n";
485 if(!this->CWD.empty())
487 fxml << "\t\t\t<WorkingDirectory>"
488 << cmXMLSafe(this->CWD)
489 << "</WorkingDirectory>\n";
491 for(std::vector<std::string>::const_iterator ai = this->RealArgs.begin();
492 ai != this->RealArgs.end(); ++ai)
494 fxml << "\t\t\t<Argument>"
495 << cmXMLSafe(ai->c_str())
496 << "</Argument>\n";
498 fxml << "\t\t</Command>\n";
501 //----------------------------------------------------------------------------
502 void cmCTestLaunch::WriteXMLResult(std::ostream& fxml)
504 fxml << "\n";
505 fxml << "\t\t<!-- Result of command -->\n";
506 fxml << "\t\t<Result>\n";
508 // StdOut
509 fxml << "\t\t\t<StdOut>";
510 this->DumpFileToXML(fxml, this->LogOut);
511 fxml << "</StdOut>\n";
513 // StdErr
514 fxml << "\t\t\t<StdErr>";
515 this->DumpFileToXML(fxml, this->LogErr);
516 fxml << "</StdErr>\n";
518 // ExitCondition
519 fxml << "\t\t\t<ExitCondition>";
520 cmsysProcess* cp = this->Process;
521 switch (cmsysProcess_GetState(cp))
523 case cmsysProcess_State_Starting:
524 fxml << "No process has been executed"; break;
525 case cmsysProcess_State_Executing:
526 fxml << "The process is still executing"; break;
527 case cmsysProcess_State_Disowned:
528 fxml << "Disowned"; break;
529 case cmsysProcess_State_Killed:
530 fxml << "Killed by parent"; break;
532 case cmsysProcess_State_Expired:
533 fxml << "Killed when timeout expired"; break;
534 case cmsysProcess_State_Exited:
535 fxml << this->ExitCode; break;
536 case cmsysProcess_State_Exception:
537 fxml << "Terminated abnormally: "
538 << cmXMLSafe(cmsysProcess_GetExceptionString(cp)); break;
539 case cmsysProcess_State_Error:
540 fxml << "Error administrating child process: "
541 << cmXMLSafe(cmsysProcess_GetErrorString(cp)); break;
543 fxml << "</ExitCondition>\n";
545 fxml << "\t\t</Result>\n";
548 //----------------------------------------------------------------------------
549 void cmCTestLaunch::WriteXMLLabels(std::ostream& fxml)
551 this->LoadLabels();
552 if(!this->Labels.empty())
554 fxml << "\n";
555 fxml << "\t\t<!-- Interested parties -->\n";
556 fxml << "\t\t<Labels>\n";
557 for(std::set<cmStdString>::const_iterator li = this->Labels.begin();
558 li != this->Labels.end(); ++li)
560 fxml << "\t\t\t<Label>" << cmXMLSafe(*li) << "</Label>\n";
562 fxml << "\t\t</Labels>\n";
566 //----------------------------------------------------------------------------
567 void cmCTestLaunch::DumpFileToXML(std::ostream& fxml,
568 std::string const& fname)
570 std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
572 std::string line;
573 const char* sep = "";
574 while(cmSystemTools::GetLineFromStream(fin, line))
576 fxml << sep << cmXMLSafe(line).Quotes(false);
577 sep = "\n";
581 //----------------------------------------------------------------------------
582 bool cmCTestLaunch::CheckResults()
584 // Skip XML in passthru mode.
585 if(this->Passthru)
587 return true;
590 // We always report failure for error conditions.
591 if(this->IsError())
593 return false;
596 // Scrape the output logs to look for warnings.
597 if((this->HaveErr && this->ScrapeLog(this->LogErr)) ||
598 (this->HaveOut && this->ScrapeLog(this->LogOut)))
600 return false;
602 return true;
605 //----------------------------------------------------------------------------
606 void cmCTestLaunch::LoadScrapeRules()
608 if(this->ScrapeRulesLoaded)
610 return;
612 this->ScrapeRulesLoaded = true;
614 // Common compiler warning formats. These are much simpler than the
615 // full log-scraping expressions because we do not need to extract
616 // file and line information.
617 this->RegexWarning.push_back("(^|[ :])[Ww][Aa][Rr][Nn][Ii][Nn][Gg]");
618 this->RegexWarning.push_back("(^|[ :])[Rr][Ee][Mm][Aa][Rr][Kk]");
619 this->RegexWarning.push_back("(^|[ :])[Nn][Oo][Tt][Ee]");
621 // Load custom match rules given to us by CTest.
622 this->LoadScrapeRules("Warning", this->RegexWarning);
623 this->LoadScrapeRules("WarningSuppress", this->RegexWarningSuppress);
626 //----------------------------------------------------------------------------
627 void
628 cmCTestLaunch
629 ::LoadScrapeRules(const char* purpose,
630 std::vector<cmsys::RegularExpression>& regexps)
632 std::string fname = this->LogDir;
633 fname += "Custom";
634 fname += purpose;
635 fname += ".txt";
636 std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
637 std::string line;
638 cmsys::RegularExpression rex;
639 while(cmSystemTools::GetLineFromStream(fin, line))
641 if(rex.compile(line.c_str()))
643 regexps.push_back(rex);
648 //----------------------------------------------------------------------------
649 bool cmCTestLaunch::ScrapeLog(std::string const& fname)
651 this->LoadScrapeRules();
653 // Look for log file lines matching warning expressions but not
654 // suppression expressions.
655 std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary);
656 std::string line;
657 while(cmSystemTools::GetLineFromStream(fin, line))
659 if(this->Match(line.c_str(), this->RegexWarning) &&
660 !this->Match(line.c_str(), this->RegexWarningSuppress))
662 return true;
665 return false;
668 //----------------------------------------------------------------------------
669 bool cmCTestLaunch::Match(std::string const& line,
670 std::vector<cmsys::RegularExpression>& regexps)
672 for(std::vector<cmsys::RegularExpression>::iterator ri = regexps.begin();
673 ri != regexps.end(); ++ri)
675 if(ri->find(line.c_str()))
677 return true;
680 return false;
683 //----------------------------------------------------------------------------
684 int cmCTestLaunch::Main(int argc, const char* const argv[])
686 if(argc == 2)
688 std::cerr << "ctest --launch: this mode is for internal CTest use only"
689 << std::endl;
690 return 1;
692 cmCTestLaunch self(argc, argv);
693 return self.Run();
696 //----------------------------------------------------------------------------
697 #include "cmGlobalGenerator.h"
698 #include "cmLocalGenerator.h"
699 #include "cmMakefile.h"
700 #include "cmake.h"
701 #include <cmsys/auto_ptr.hxx>
702 void cmCTestLaunch::LoadConfig()
704 cmake cm;
705 cmGlobalGenerator gg;
706 gg.SetCMakeInstance(&cm);
707 cmsys::auto_ptr<cmLocalGenerator> lg(gg.CreateLocalGenerator());
708 cmMakefile* mf = lg->GetMakefile();
709 std::string fname = this->LogDir;
710 fname += "CTestLaunchConfig.cmake";
711 if(cmSystemTools::FileExists(fname.c_str()) &&
712 mf->ReadListFile(0, fname.c_str()))
714 this->SourceDir = mf->GetSafeDefinition("CTEST_SOURCE_DIRECTORY");
715 cmSystemTools::ConvertToUnixSlashes(this->SourceDir);