diff options
author | Brad King <brad.king@kitware.com> | 2009-07-10 15:08:05 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2009-07-10 15:08:05 (GMT) |
commit | d4d467dbd5c4a1590333eeeb3082ad46dc9698df (patch) | |
tree | 9ade1437126f78d4d1a5d292848df45ce33cadda /Source/CTest | |
parent | c0e8c0f5aa004fb7f8c1804a5e29bd22862c9360 (diff) | |
download | CMake-d4d467dbd5c4a1590333eeeb3082ad46dc9698df.zip CMake-d4d467dbd5c4a1590333eeeb3082ad46dc9698df.tar.gz CMake-d4d467dbd5c4a1590333eeeb3082ad46dc9698df.tar.bz2 |
ENH: Teach CTest to handle Mercurial repositories
This creates cmCTestHG to drive CTest Update handling on hg-based work
trees. Currently we always update to the head of the remote tracking
branch (hg pull), so the nightly start time is ignored for Nightly
builds. A later change will address this.
See issue #7879. Patch from Emmanuel Christophe. I modified the patch
slightly for code style, to finish up some parsing details, and to fix
the test.
Diffstat (limited to 'Source/CTest')
-rw-r--r-- | Source/CTest/cmCTestHG.cxx | 343 | ||||
-rw-r--r-- | Source/CTest/cmCTestHG.h | 52 | ||||
-rw-r--r-- | Source/CTest/cmCTestUpdateCommand.cxx | 4 | ||||
-rw-r--r-- | Source/CTest/cmCTestUpdateHandler.cxx | 20 | ||||
-rw-r--r-- | Source/CTest/cmCTestUpdateHandler.h | 1 |
5 files changed, 419 insertions, 1 deletions
diff --git a/Source/CTest/cmCTestHG.cxx b/Source/CTest/cmCTestHG.cxx new file mode 100644 index 0000000..268fe77 --- /dev/null +++ b/Source/CTest/cmCTestHG.cxx @@ -0,0 +1,343 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc. 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 "cmCTestHG.h" + +#include "cmCTest.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" + +#include <cmsys/RegularExpression.hxx> + +//---------------------------------------------------------------------------- +cmCTestHG::cmCTestHG(cmCTest* ct, std::ostream& log): + cmCTestGlobalVC(ct, log) +{ + this->PriorRev = this->Unknown; +} + +//---------------------------------------------------------------------------- +cmCTestHG::~cmCTestHG() +{ +} + +//---------------------------------------------------------------------------- +class cmCTestHG::IdentifyParser: public cmCTestVC::LineParser +{ +public: + IdentifyParser(cmCTestHG* hg, const char* prefix, + std::string& rev): Rev(rev) + { + this->SetLog(&hg->Log, prefix); + this->RegexIdentify.compile("^([0-9a-f]+)"); + } +private: + std::string& Rev; + cmsys::RegularExpression RegexIdentify; + + bool ProcessLine() + { + if(this->RegexIdentify.find(this->Line)) + { + this->Rev = this->RegexIdentify.match(1); + return false; + } + return true; + } +}; + +//---------------------------------------------------------------------------- +class cmCTestHG::StatusParser: public cmCTestVC::LineParser +{ +public: + StatusParser(cmCTestHG* hg, const char* prefix): HG(hg) + { + this->SetLog(&hg->Log, prefix); + this->RegexStatus.compile("([MARC!?I]) (.*)"); + } + +private: + cmCTestHG* HG; + cmsys::RegularExpression RegexStatus; + + bool ProcessLine() + { + if(this->RegexStatus.find(this->Line)) + { + this->DoPath(this->RegexStatus.match(1)[0], + this->RegexStatus.match(2)); + } + return true; + } + + void DoPath(char status, std::string const& path) + { + if(path.empty()) return; + + // See "hg help status". Note that there is no 'conflict' status. + switch(status) + { + case 'M': case 'A': case '!': case 'R': + this->HG->DoModification(PathModified, path); + break; + case 'I': case '?': case 'C': case ' ': default: + break; + } + } +}; + +//---------------------------------------------------------------------------- +std::string cmCTestHG::GetWorkingRevision() +{ + // Run plumbing "hg identify" to get work tree revision. + const char* hg = this->CommandLineTool.c_str(); + const char* hg_identify[] = {hg, "identify","-i", 0}; + std::string rev; + IdentifyParser out(this, "rev-out> ", rev); + OutputLogger err(this->Log, "rev-err> "); + this->RunChild(hg_identify, &out, &err); + return rev; +} + +//---------------------------------------------------------------------------- +void cmCTestHG::NoteOldRevision() +{ + this->OldRevision = this->GetWorkingRevision(); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " + << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; +} + +//---------------------------------------------------------------------------- +void cmCTestHG::NoteNewRevision() +{ + this->NewRevision = this->GetWorkingRevision(); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " + << this->NewRevision << "\n"); +} + +//---------------------------------------------------------------------------- +bool cmCTestHG::UpdateImpl() +{ + // Use "hg pull" followed by "hg update" to update the working tree. + { + const char* hg = this->CommandLineTool.c_str(); + const char* hg_pull[] = {hg, "pull","-v", 0}; + OutputLogger out(this->Log, "pull-out> "); + OutputLogger err(this->Log, "pull-err> "); + this->RunChild(&hg_pull[0], &out, &err); + } + + // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY) + + std::vector<char const*> hg_update; + hg_update.push_back(this->CommandLineTool.c_str()); + hg_update.push_back("update"); + hg_update.push_back("-v"); + + // Add user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if(opts.empty()) + { + opts = this->CTest->GetCTestConfiguration("HGUpdateOptions"); + } + std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + for(std::vector<cmStdString>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) + { + hg_update.push_back(ai->c_str()); + } + + // Sentinel argument. + hg_update.push_back(0); + + OutputLogger out(this->Log, "update-out> "); + OutputLogger err(this->Log, "update-err> "); + return this->RunUpdateCommand(&hg_update[0], &out, &err); +} + +//---------------------------------------------------------------------------- +class cmCTestHG::LogParser: public cmCTestVC::OutputLogger, + private cmXMLParser +{ +public: + LogParser(cmCTestHG* hg, const char* prefix): + OutputLogger(hg->Log, prefix), HG(hg) { this->InitializeParser(); } + ~LogParser() { this->CleanupParser(); } +private: + cmCTestHG* HG; + + typedef cmCTestHG::Revision Revision; + typedef cmCTestHG::Change Change; + Revision Rev; + std::vector<Change> Changes; + Change CurChange; + std::vector<char> CData; + + virtual bool ProcessChunk(const char* data, int length) + { + this->OutputLogger::ProcessChunk(data, length); + this->ParseChunk(data, length); + return true; + } + + virtual void StartElement(const char* name, const char** atts) + { + this->CData.clear(); + if(strcmp(name, "logentry") == 0) + { + this->Rev = Revision(); + if(const char* rev = this->FindAttribute(atts, "revision")) + { + this->Rev.Rev = rev; + } + this->Changes.clear(); + } + } + + virtual void CharacterDataHandler(const char* data, int length) + { + this->CData.insert(this->CData.end(), data, data+length); + } + + virtual void EndElement(const char* name) + { + if(strcmp(name, "logentry") == 0) + { + this->HG->DoRevision(this->Rev, this->Changes); + } + else if(strcmp(name, "author") == 0 && !this->CData.empty()) + { + this->Rev.Author.assign(&this->CData[0], this->CData.size()); + } + else if ( strcmp(name, "email") == 0 && !this->CData.empty()) + { + // this->Rev.Email.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "date") == 0 && !this->CData.empty()) + { + this->Rev.Date.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "msg") == 0 && !this->CData.empty()) + { + this->Rev.Log.assign(&this->CData[0], this->CData.size()); + } + else if(strcmp(name, "files") == 0 && !this->CData.empty()) + { + std::vector<std::string> paths = this->SplitCData(); + for(unsigned int i = 0; i < paths.size(); ++i) + { + // Updated by default, will be modified using file_adds and + // file_dels. + this->CurChange = Change('U'); + this->CurChange.Path = paths[i]; + this->Changes.push_back(this->CurChange); + } + } + else if(strcmp(name, "file_adds") == 0 && !this->CData.empty()) + { + std::string added_paths(this->CData.begin(), this->CData.end()); + for(unsigned int i = 0; i < this->Changes.size(); ++i) + { + if(added_paths.find(this->Changes[i].Path) != std::string::npos) + { + this->Changes[i].Action = 'A'; + } + } + } + else if(strcmp(name, "file_dels") == 0 && !this->CData.empty()) + { + std::string added_paths(this->CData.begin(), this->CData.end()); + for(unsigned int i = 0; i < this->Changes.size(); ++i) + { + if(added_paths.find(this->Changes[i].Path) != std::string::npos) + { + this->Changes[i].Action = 'D'; + } + } + } + this->CData.clear(); + } + + std::vector<std::string> SplitCData() + { + std::vector<std::string> output; + std::string currPath; + for(unsigned int i=0; i < this->CData.size(); ++i) + { + if(this->CData[i] != ' ') + { + currPath.push_back(this->CData[i]); + } + else + { + output.push_back(currPath); + currPath.erase(); + } + } + output.push_back(currPath); + return output; + } + + virtual void ReportError(int, int, const char* msg) + { + this->HG->Log << "Error parsing hg log xml: " << msg << "\n"; + } +}; + +//---------------------------------------------------------------------------- +void cmCTestHG::LoadRevisions() +{ + // Use 'hg log' to get revisions in a xml format. + // + // TODO: This should use plumbing or python code to be more precise. + // The "list of strings" templates like {files} will not work when + // the project has spaces in the path. Also, they may not have + // proper XML escapes. + std::string range = this->OldRevision + ":" + this->NewRevision; + const char* hg = this->CommandLineTool.c_str(); + const char* hgXMLTemplate = + "<logentry\n" + " revision=\"{node|short}\">\n" + " <author>{author|person}</author>\n" + " <email>{author|email}</email>\n" + " <date>{date|isodate}</date>\n" + " <msg>{desc}</msg>\n" + " <files>{files}</files>\n" + " <file_adds>{file_adds}</file_adds>\n" + " <file_dels>{file_dels}</file_dels>\n" + "</logentry>\n"; + const char* hg_log[] = {hg, "log","--removed", "-r", range.c_str(), + "--template", hgXMLTemplate, 0}; + + LogParser out(this, "log-out> "); + out.Process("<?xml version=\"1.0\"?>\n" + "<log>\n"); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(hg_log, &out, &err); + out.Process("</log>\n"); +} + +//---------------------------------------------------------------------------- +void cmCTestHG::LoadModifications() +{ + // Use 'hg status' to get modified files. + const char* hg = this->CommandLineTool.c_str(); + const char* hg_status[] = {hg, "status", 0}; + StatusParser out(this, "status-out> "); + OutputLogger err(this->Log, "status-err> "); + this->RunChild(hg_status, &out, &err); +} diff --git a/Source/CTest/cmCTestHG.h b/Source/CTest/cmCTestHG.h new file mode 100644 index 0000000..aafa0e1 --- /dev/null +++ b/Source/CTest/cmCTestHG.h @@ -0,0 +1,52 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#ifndef cmCTestHG_h +#define cmCTestHG_h + +#include "cmCTestGlobalVC.h" + +/** \class cmCTestHG + * \brief Interaction with Mercurial command-line tool + * + */ +class cmCTestHG: public cmCTestGlobalVC +{ +public: + /** Construct with a CTest instance and update log stream. */ + cmCTestHG(cmCTest* ctest, std::ostream& log); + + virtual ~cmCTestHG(); + +private: + std::string GetWorkingRevision(); + virtual void NoteOldRevision(); + virtual void NoteNewRevision(); + virtual bool UpdateImpl(); + + void LoadRevisions(); + void LoadModifications(); + + // Parsing helper classes. + class IdentifyParser; + class StatusParser; + class LogParser; + friend class IdentifyParser; + friend class StatusParser; + friend class LogParser; +}; + +#endif diff --git a/Source/CTest/cmCTestUpdateCommand.cxx b/Source/CTest/cmCTestUpdateCommand.cxx index 73c1f40..3dc5cf6 100644 --- a/Source/CTest/cmCTestUpdateCommand.cxx +++ b/Source/CTest/cmCTestUpdateCommand.cxx @@ -56,6 +56,10 @@ cmCTestGenericHandler* cmCTestUpdateCommand::InitializeHandler() "GITCommand", "CTEST_GIT_COMMAND"); this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "HGCommand", "CTEST_HG_COMMAND"); + this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, + "HGUpdateOptions", "CTEST_HG_UPDATE_OPTIONS"); const char* initialCheckoutCommand = this->Makefile->GetDefinition("CTEST_CHECKOUT_COMMAND"); diff --git a/Source/CTest/cmCTestUpdateHandler.cxx b/Source/CTest/cmCTestUpdateHandler.cxx index d3930d7..749daa5 100644 --- a/Source/CTest/cmCTestUpdateHandler.cxx +++ b/Source/CTest/cmCTestUpdateHandler.cxx @@ -32,6 +32,7 @@ #include "cmCTestSVN.h" #include "cmCTestBZR.h" #include "cmCTestGIT.h" +#include "cmCTestHG.h" #include <cmsys/auto_ptr.hxx> @@ -54,7 +55,8 @@ static const char* cmCTestUpdateHandlerUpdateStrings[] = "CVS", "SVN", "BZR", - "GIT" + "GIT", + "HG" }; static const char* cmCTestUpdateHandlerUpdateToString(int type) @@ -145,6 +147,10 @@ int cmCTestUpdateHandler::DetermineType(const char* cmd, const char* type) { return cmCTestUpdateHandler::e_GIT; } + if ( stype.find("hg") != std::string::npos ) + { + return cmCTestUpdateHandler::e_HG; + } } else { @@ -167,6 +173,10 @@ int cmCTestUpdateHandler::DetermineType(const char* cmd, const char* type) { return cmCTestUpdateHandler::e_GIT; } + if ( stype.find("hg") != std::string::npos ) + { + return cmCTestUpdateHandler::e_HG; + } } return cmCTestUpdateHandler::e_UNKNOWN; } @@ -226,6 +236,7 @@ int cmCTestUpdateHandler::ProcessHandler() case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break; case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break; case e_GIT: vc.reset(new cmCTestGIT(this->CTest, ofs)); break; + case e_HG: vc.reset(new cmCTestHG(this->CTest, ofs)); break; default: vc.reset(new cmCTestVC(this->CTest, ofs)); break; } vc->SetCommandLineTool(this->UpdateCommand); @@ -371,6 +382,12 @@ int cmCTestUpdateHandler::DetectVCS(const char* dir) { return cmCTestUpdateHandler::e_GIT; } + sourceDirectory = dir; + sourceDirectory += "/.hg"; + if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) + { + return cmCTestUpdateHandler::e_HG; + } return cmCTestUpdateHandler::e_UNKNOWN; } @@ -400,6 +417,7 @@ bool cmCTestUpdateHandler::SelectVCS() case e_SVN: key = "SVNCommand"; break; case e_BZR: key = "BZRCommand"; break; case e_GIT: key = "GITCommand"; break; + case e_HG: key = "HGCommand"; break; default: break; } if (key) diff --git a/Source/CTest/cmCTestUpdateHandler.h b/Source/CTest/cmCTestUpdateHandler.h index 139ceed..f1e76f8 100644 --- a/Source/CTest/cmCTestUpdateHandler.h +++ b/Source/CTest/cmCTestUpdateHandler.h @@ -48,6 +48,7 @@ public: e_SVN, e_BZR, e_GIT, + e_HG, e_LAST }; |