1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmCTestLaunch.cxx,v $
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"
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;
34 this->CWD
= cmSystemTools::GetCurrentWorkingDirectory();
36 if(!this->ParseArguments(argc
, argv
))
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
);
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
,
73 Doing doing
= DoingNone
;
75 for(int i
=1; !arg0
&& i
< argc
; ++i
)
77 const char* arg
= argv
[i
];
78 if(strcmp(arg
, "--") == 0)
82 else if(strcmp(arg
, "--output") == 0)
86 else if(strcmp(arg
, "--source") == 0)
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
;
111 else if(doing
== DoingSource
)
113 this->OptionSource
= arg
;
116 else if(doing
== DoingLanguage
)
118 this->OptionLanguage
= arg
;
119 if(this->OptionLanguage
== "CXX")
121 this->OptionLanguage
= "C++";
125 else if(doing
== DoingTargetName
)
127 this->OptionTargetName
= arg
;
130 else if(doing
== DoingTargetType
)
132 this->OptionTargetType
= arg
;
135 else if(doing
== DoingBuildDir
)
137 this->OptionBuildDir
= arg
;
142 // Extract the real command line.
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
]);
157 std::cerr
<< "No launch/command separator ('--') found!\n";
162 //----------------------------------------------------------------------------
163 void cmCTestLaunch::HandleRealArg(const char* arg
)
166 // Expand response file arguments.
167 if(arg
[0] == '@' && cmSystemTools::FileExists(arg
+1))
169 std::ifstream
fin(arg
+1);
171 while(cmSystemTools::GetLineFromStream(fin
, line
))
173 cmSystemTools::ParseWindowsCommandLine(line
.c_str(), this->RealArgs
);
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");
191 this->Passthru
= false;
193 // The environment variable specifies the directory into which we
194 // generate build logs.
196 cmSystemTools::ConvertToUnixSlashes(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.
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
);
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);
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.
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()
284 std::cerr
<< "Could not allocate cmsysProcess instance!\n";
290 if(this->CheckResults())
292 return this->ExitCode
;
298 return this->ExitCode
;
301 //----------------------------------------------------------------------------
302 void cmCTestLaunch::LoadLabels()
304 if(this->OptionBuildDir
.empty() || this->OptionTargetName
.empty())
309 // Labels are listed in per-target files.
310 std::string fname
= this->OptionBuildDir
;
311 fname
+= cmake::GetCMakeFilesDirectory();
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
);
323 bool inTarget
= true;
324 bool inSource
= false;
326 while(cmSystemTools::GetLineFromStream(fin
, line
))
328 if(line
.empty() || line
[0] == '#')
330 // Ignore blank and comment lines.
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
347 inSource
= this->SourceMatches(line
, source
);
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).
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
;
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";
400 if(!this->OptionTargetName
.empty())
402 fxml
<< "\t\t\t<TargetName>"
403 << cmXMLSafe(this->OptionTargetName
)
404 << "</TargetName>\n";
408 if(!this->OptionLanguage
.empty())
410 fxml
<< "\t\t\t<Language>"
411 << cmXMLSafe(this->OptionLanguage
)
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(),
431 fxml
<< "\t\t\t<SourceFile>"
433 << "</SourceFile>\n";
437 if(!this->OptionOutput
.empty())
439 fxml
<< "\t\t\t<OutputFile>"
440 << cmXMLSafe(this->OptionOutput
)
441 << "</OutputFile>\n";
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";
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
)
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())
498 fxml
<< "\t\t</Command>\n";
501 //----------------------------------------------------------------------------
502 void cmCTestLaunch::WriteXMLResult(std::ostream
& fxml
)
505 fxml
<< "\t\t<!-- Result of command -->\n";
506 fxml
<< "\t\t<Result>\n";
509 fxml
<< "\t\t\t<StdOut>";
510 this->DumpFileToXML(fxml
, this->LogOut
);
511 fxml
<< "</StdOut>\n";
514 fxml
<< "\t\t\t<StdErr>";
515 this->DumpFileToXML(fxml
, this->LogErr
);
516 fxml
<< "</StdErr>\n";
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
)
552 if(!this->Labels
.empty())
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
);
573 const char* sep
= "";
574 while(cmSystemTools::GetLineFromStream(fin
, line
))
576 fxml
<< sep
<< cmXMLSafe(line
).Quotes(false);
581 //----------------------------------------------------------------------------
582 bool cmCTestLaunch::CheckResults()
584 // Skip XML in passthru mode.
590 // We always report failure for error conditions.
596 // Scrape the output logs to look for warnings.
597 if((this->HaveErr
&& this->ScrapeLog(this->LogErr
)) ||
598 (this->HaveOut
&& this->ScrapeLog(this->LogOut
)))
605 //----------------------------------------------------------------------------
606 void cmCTestLaunch::LoadScrapeRules()
608 if(this->ScrapeRulesLoaded
)
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 //----------------------------------------------------------------------------
629 ::LoadScrapeRules(const char* purpose
,
630 std::vector
<cmsys::RegularExpression
>& regexps
)
632 std::string fname
= this->LogDir
;
636 std::ifstream
fin(fname
.c_str(), std::ios::in
| std::ios::binary
);
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
);
657 while(cmSystemTools::GetLineFromStream(fin
, line
))
659 if(this->Match(line
.c_str(), this->RegexWarning
) &&
660 !this->Match(line
.c_str(), this->RegexWarningSuppress
))
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()))
683 //----------------------------------------------------------------------------
684 int cmCTestLaunch::Main(int argc
, const char* const argv
[])
688 std::cerr
<< "ctest --launch: this mode is for internal CTest use only"
692 cmCTestLaunch
self(argc
, argv
);
696 //----------------------------------------------------------------------------
697 #include "cmGlobalGenerator.h"
698 #include "cmLocalGenerator.h"
699 #include "cmMakefile.h"
701 #include <cmsys/auto_ptr.hxx>
702 void cmCTestLaunch::LoadConfig()
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
);