cmCTestP4.cxx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. /*============================================================================
  2. CMake - Cross Platform Makefile Generator
  3. Copyright 2000-2013 Kitware, Inc.
  4. Distributed under the OSI-approved BSD License (the "License");
  5. see accompanying file Copyright.txt for details.
  6. This software is distributed WITHOUT ANY WARRANTY; without even the
  7. implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. See the License for more information.
  9. ============================================================================*/
  10. #include "cmCTestP4.h"
  11. #include "cmCTest.h"
  12. #include "cmSystemTools.h"
  13. #include <cmsys/Process.h>
  14. #include <cmsys/RegularExpression.hxx>
  15. #include <sys/types.h>
  16. #include <ctype.h>
  17. #include <time.h>
  18. //----------------------------------------------------------------------------
  19. cmCTestP4::cmCTestP4(cmCTest* ct, std::ostream& log):
  20. cmCTestGlobalVC(ct, log)
  21. {
  22. this->PriorRev = this->Unknown;
  23. }
  24. //----------------------------------------------------------------------------
  25. cmCTestP4::~cmCTestP4()
  26. {
  27. }
  28. //----------------------------------------------------------------------------
  29. class cmCTestP4::IdentifyParser: public cmCTestVC::LineParser
  30. {
  31. public:
  32. IdentifyParser(cmCTestP4* p4, const char* prefix,
  33. std::string& rev): Rev(rev)
  34. {
  35. this->SetLog(&p4->Log, prefix);
  36. this->RegexIdentify.compile("^Change ([0-9]+) on");
  37. }
  38. private:
  39. std::string& Rev;
  40. cmsys::RegularExpression RegexIdentify;
  41. bool ProcessLine()
  42. {
  43. if(this->RegexIdentify.find(this->Line))
  44. {
  45. this->Rev = this->RegexIdentify.match(1);
  46. return false;
  47. }
  48. return true;
  49. }
  50. };
  51. //----------------------------------------------------------------------------
  52. class cmCTestP4::ChangesParser: public cmCTestVC::LineParser
  53. {
  54. public:
  55. ChangesParser(cmCTestP4* p4, const char* prefix) : P4(p4)
  56. {
  57. this->SetLog(&P4->Log, prefix);
  58. this->RegexIdentify.compile("^Change ([0-9]+) on");
  59. }
  60. private:
  61. cmsys::RegularExpression RegexIdentify;
  62. cmCTestP4* P4;
  63. bool ProcessLine()
  64. {
  65. if(this->RegexIdentify.find(this->Line))
  66. {
  67. P4->ChangeLists.push_back(this->RegexIdentify.match(1));
  68. }
  69. return true;
  70. }
  71. };
  72. //----------------------------------------------------------------------------
  73. class cmCTestP4::UserParser: public cmCTestVC::LineParser
  74. {
  75. public:
  76. UserParser(cmCTestP4* p4, const char* prefix) : P4(p4)
  77. {
  78. this->SetLog(&P4->Log, prefix);
  79. this->RegexUser.compile("^(.+) <(.*)> \\((.*)\\) accessed (.*)$");
  80. }
  81. private:
  82. cmsys::RegularExpression RegexUser;
  83. cmCTestP4* P4;
  84. bool ProcessLine()
  85. {
  86. if(this->RegexUser.find(this->Line))
  87. {
  88. User NewUser;
  89. NewUser.UserName = this->RegexUser.match(1);
  90. NewUser.EMail = this->RegexUser.match(2);
  91. NewUser.Name = this->RegexUser.match(3);
  92. NewUser.AccessTime = this->RegexUser.match(4);
  93. P4->Users[this->RegexUser.match(1)] = NewUser;
  94. return false;
  95. }
  96. return true;
  97. }
  98. };
  99. //----------------------------------------------------------------------------
  100. /* Diff format:
  101. ==== //depot/file#rev - /absolute/path/to/file ====
  102. (diff data)
  103. ==== //depot/file2#rev - /absolute/path/to/file2 ====
  104. (diff data)
  105. ==== //depot/file3#rev - /absolute/path/to/file3 ====
  106. ==== //depot/file4#rev - /absolute/path/to/file4 ====
  107. (diff data)
  108. */
  109. class cmCTestP4::DiffParser: public cmCTestVC::LineParser
  110. {
  111. public:
  112. DiffParser(cmCTestP4* p4, const char* prefix)
  113. : P4(p4), AlreadyNotified(false)
  114. {
  115. this->SetLog(&P4->Log, prefix);
  116. this->RegexDiff.compile("^==== (.*)#[0-9]+ - (.*)");
  117. }
  118. private:
  119. cmCTestP4* P4;
  120. bool AlreadyNotified;
  121. std::string CurrentPath;
  122. cmsys::RegularExpression RegexDiff;
  123. bool ProcessLine()
  124. {
  125. if(!this->Line.empty() && this->Line[0] == '='
  126. && this->RegexDiff.find(this->Line))
  127. {
  128. CurrentPath = this->RegexDiff.match(1);
  129. AlreadyNotified = false;
  130. }
  131. else
  132. {
  133. if(!AlreadyNotified)
  134. {
  135. P4->DoModification(PathModified, CurrentPath);
  136. AlreadyNotified = true;
  137. }
  138. }
  139. return true;
  140. }
  141. };
  142. //----------------------------------------------------------------------------
  143. cmCTestP4::User cmCTestP4::GetUserData(const std::string& username)
  144. {
  145. std::map<std::string, cmCTestP4::User>::const_iterator it =
  146. Users.find(username);
  147. if(it == Users.end())
  148. {
  149. std::vector<char const*> p4_users;
  150. SetP4Options(p4_users);
  151. p4_users.push_back("users");
  152. p4_users.push_back("-m");
  153. p4_users.push_back("1");
  154. p4_users.push_back(username.c_str());
  155. p4_users.push_back(0);
  156. UserParser out(this, "users-out> ");
  157. OutputLogger err(this->Log, "users-err> ");
  158. RunChild(&p4_users[0], &out, &err);
  159. // The user should now be added to the map. Search again.
  160. it = Users.find(username);
  161. if(it == Users.end())
  162. {
  163. return cmCTestP4::User();
  164. }
  165. }
  166. return it->second;
  167. }
  168. //----------------------------------------------------------------------------
  169. /* Commit format:
  170. Change 1111111 by user@client on 2013/09/26 11:50:36
  171. text
  172. text
  173. Affected files ...
  174. ... //path/to/file#rev edit
  175. ... //path/to/file#rev add
  176. ... //path/to/file#rev delete
  177. ... //path/to/file#rev integrate
  178. */
  179. class cmCTestP4::DescribeParser: public cmCTestVC::LineParser
  180. {
  181. public:
  182. DescribeParser(cmCTestP4* p4, const char* prefix):
  183. LineParser('\n', false), P4(p4), Section(SectionHeader)
  184. {
  185. this->SetLog(&P4->Log, prefix);
  186. this->RegexHeader.compile("^Change ([0-9]+) by (.+)@(.+) on (.*)$");
  187. this->RegexDiff.compile("^\\.\\.\\. (.*)#[0-9]+ ([^ ]+)$");
  188. }
  189. private:
  190. cmsys::RegularExpression RegexHeader;
  191. cmsys::RegularExpression RegexDiff;
  192. cmCTestP4* P4;
  193. typedef cmCTestP4::Revision Revision;
  194. typedef cmCTestP4::Change Change;
  195. std::vector<Change> Changes;
  196. enum SectionType { SectionHeader, SectionBody, SectionDiffHeader,
  197. SectionDiff, SectionCount };
  198. SectionType Section;
  199. Revision Rev;
  200. virtual bool ProcessLine()
  201. {
  202. if(this->Line.empty())
  203. {
  204. this->NextSection();
  205. }
  206. else
  207. {
  208. switch(this->Section)
  209. {
  210. case SectionHeader: this->DoHeaderLine(); break;
  211. case SectionBody: this->DoBodyLine(); break;
  212. case SectionDiffHeader: break; // nothing to do
  213. case SectionDiff: this->DoDiffLine(); break;
  214. case SectionCount: break; // never happens
  215. }
  216. }
  217. return true;
  218. }
  219. void NextSection()
  220. {
  221. if(this->Section == SectionDiff)
  222. {
  223. this->P4->DoRevision(this->Rev, this->Changes);
  224. this->Rev = Revision();
  225. }
  226. this->Section = SectionType((this->Section+1) % SectionCount);
  227. }
  228. void DoHeaderLine()
  229. {
  230. if(this->RegexHeader.find(this->Line))
  231. {
  232. this->Rev.Rev = this->RegexHeader.match(1);
  233. this->Rev.Date = this->RegexHeader.match(4);
  234. cmCTestP4::User user = P4->GetUserData(this->RegexHeader.match(2));
  235. this->Rev.Author = user.Name;
  236. this->Rev.EMail = user.EMail;
  237. this->Rev.Committer = this->Rev.Author;
  238. this->Rev.CommitterEMail = this->Rev.EMail;
  239. this->Rev.CommitDate = this->Rev.Date;
  240. }
  241. }
  242. void DoBodyLine()
  243. {
  244. if(this->Line[0] == '\t')
  245. {
  246. this->Rev.Log += this->Line.substr(1);
  247. }
  248. this->Rev.Log += "\n";
  249. }
  250. void DoDiffLine()
  251. {
  252. if(this->RegexDiff.find(this->Line))
  253. {
  254. Change change;
  255. std::string Path = this->RegexDiff.match(1);
  256. if(Path.length() > 2 && Path[0] == '/' && Path[1] == '/')
  257. {
  258. size_t found = Path.find('/', 2);
  259. if(found != std::string::npos)
  260. {
  261. Path = Path.substr(found + 1);
  262. }
  263. }
  264. change.Path = Path;
  265. std::string action = this->RegexDiff.match(2);
  266. if(action == "add")
  267. {
  268. change.Action = 'A';
  269. }
  270. else if(action == "delete")
  271. {
  272. change.Action = 'D';
  273. }
  274. else if(action == "edit" || action == "integrate")
  275. {
  276. change.Action = 'M';
  277. }
  278. Changes.push_back(change);
  279. }
  280. }
  281. };
  282. //----------------------------------------------------------------------------
  283. void cmCTestP4::SetP4Options(std::vector<char const*> &CommandOptions)
  284. {
  285. if(P4Options.empty())
  286. {
  287. const char* p4 = this->CommandLineTool.c_str();
  288. P4Options.push_back(p4);
  289. //The CTEST_P4_CLIENT variable sets the P4 client used when issuing
  290. //Perforce commands, if it's different from the default one.
  291. std::string client = this->CTest->GetCTestConfiguration("P4Client");
  292. if(!client.empty())
  293. {
  294. P4Options.push_back("-c");
  295. P4Options.push_back(client);
  296. }
  297. //Set the message language to be English, in case the P4 admin
  298. //has localized them
  299. P4Options.push_back("-L");
  300. P4Options.push_back("en");
  301. //The CTEST_P4_OPTIONS variable adds additional Perforce command line
  302. //options before the main command
  303. std::string opts = this->CTest->GetCTestConfiguration("P4Options");
  304. std::vector<std::string> args =
  305. cmSystemTools::ParseArguments(opts.c_str());
  306. P4Options.insert(P4Options.end(), args.begin(), args.end());
  307. }
  308. CommandOptions.clear();
  309. for(std::vector<std::string>::iterator i = P4Options.begin();
  310. i != P4Options.end(); ++i)
  311. {
  312. CommandOptions.push_back(i->c_str());
  313. }
  314. }
  315. //----------------------------------------------------------------------------
  316. std::string cmCTestP4::GetWorkingRevision()
  317. {
  318. std::vector<char const*> p4_identify;
  319. SetP4Options(p4_identify);
  320. p4_identify.push_back("changes");
  321. p4_identify.push_back("-m");
  322. p4_identify.push_back("1");
  323. p4_identify.push_back("-t");
  324. std::string source = this->SourceDirectory + "/...#have";
  325. p4_identify.push_back(source.c_str());
  326. p4_identify.push_back(0);
  327. std::string rev;
  328. IdentifyParser out(this, "p4_changes-out> ", rev);
  329. OutputLogger err(this->Log, "p4_changes-err> ");
  330. bool result = RunChild(&p4_identify[0], &out, &err);
  331. // If there was a problem contacting the server return "<unknown>"
  332. if(!result)
  333. {
  334. return "<unknown>";
  335. }
  336. if(rev.empty())
  337. {
  338. return "0";
  339. }
  340. else
  341. {
  342. return rev;
  343. }
  344. }
  345. //----------------------------------------------------------------------------
  346. void cmCTestP4::NoteOldRevision()
  347. {
  348. this->OldRevision = this->GetWorkingRevision();
  349. cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
  350. << this->OldRevision << "\n");
  351. this->PriorRev.Rev = this->OldRevision;
  352. }
  353. //----------------------------------------------------------------------------
  354. void cmCTestP4::NoteNewRevision()
  355. {
  356. this->NewRevision = this->GetWorkingRevision();
  357. cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
  358. << this->NewRevision << "\n");
  359. }
  360. //----------------------------------------------------------------------------
  361. void cmCTestP4::LoadRevisions()
  362. {
  363. std::vector<char const*> p4_changes;
  364. SetP4Options(p4_changes);
  365. // Use 'p4 changes ...@old,new' to get a list of changelists
  366. std::string range = this->SourceDirectory + "/...";
  367. // If any revision is unknown it means we couldn't contact the server.
  368. // Do not process updates
  369. if(this->OldRevision == "<unknown>" || this->NewRevision == "<unknown>")
  370. {
  371. cmCTestLog(this->CTest, HANDLER_OUTPUT, " At least one of the revisions "
  372. << "is unknown. No repository changes will be reported.\n");
  373. return;
  374. }
  375. range.append("@").append(this->OldRevision)
  376. .append(",").append(this->NewRevision);
  377. p4_changes.push_back("changes");
  378. p4_changes.push_back(range.c_str());
  379. p4_changes.push_back(0);
  380. ChangesParser out(this, "p4_changes-out> ");
  381. OutputLogger err(this->Log, "p4_changes-err> ");
  382. ChangeLists.clear();
  383. this->RunChild(&p4_changes[0], &out, &err);
  384. if(ChangeLists.empty())
  385. return;
  386. //p4 describe -s ...@1111111,2222222
  387. std::vector<char const*> p4_describe;
  388. for(std::vector<std::string>::reverse_iterator i = ChangeLists.rbegin();
  389. i != ChangeLists.rend(); ++i)
  390. {
  391. SetP4Options(p4_describe);
  392. p4_describe.push_back("describe");
  393. p4_describe.push_back("-s");
  394. p4_describe.push_back(i->c_str());
  395. p4_describe.push_back(0);
  396. DescribeParser outDescribe(this, "p4_describe-out> ");
  397. OutputLogger errDescribe(this->Log, "p4_describe-err> ");
  398. this->RunChild(&p4_describe[0], &outDescribe, &errDescribe);
  399. }
  400. }
  401. //----------------------------------------------------------------------------
  402. void cmCTestP4::LoadModifications()
  403. {
  404. std::vector<char const*> p4_diff;
  405. SetP4Options(p4_diff);
  406. p4_diff.push_back("diff");
  407. //Ideally we would use -Od but not all clients support it
  408. p4_diff.push_back("-dn");
  409. std::string source = this->SourceDirectory + "/...";
  410. p4_diff.push_back(source.c_str());
  411. p4_diff.push_back(0);
  412. DiffParser out(this, "p4_diff-out> ");
  413. OutputLogger err(this->Log, "p4_diff-err> ");
  414. this->RunChild(&p4_diff[0], &out, &err);
  415. }
  416. //----------------------------------------------------------------------------
  417. bool cmCTestP4::UpdateCustom(const std::string& custom)
  418. {
  419. std::vector<std::string> p4_custom_command;
  420. cmSystemTools::ExpandListArgument(custom, p4_custom_command, true);
  421. std::vector<char const*> p4_custom;
  422. for(std::vector<std::string>::const_iterator
  423. i = p4_custom_command.begin(); i != p4_custom_command.end(); ++i)
  424. {
  425. p4_custom.push_back(i->c_str());
  426. }
  427. p4_custom.push_back(0);
  428. OutputLogger custom_out(this->Log, "p4_customsync-out> ");
  429. OutputLogger custom_err(this->Log, "p4_customsync-err> ");
  430. return this->RunUpdateCommand(&p4_custom[0], &custom_out, &custom_err);
  431. }
  432. //----------------------------------------------------------------------------
  433. bool cmCTestP4::UpdateImpl()
  434. {
  435. std::string custom = this->CTest->GetCTestConfiguration("P4UpdateCustom");
  436. if(!custom.empty())
  437. {
  438. return this->UpdateCustom(custom);
  439. }
  440. // If we couldn't get a revision number before updating, abort.
  441. if(this->OldRevision == "<unknown>")
  442. {
  443. this->UpdateCommandLine = "Unknown current revision";
  444. cmCTestLog(this->CTest, ERROR_MESSAGE, " Unknown current revision\n");
  445. return false;
  446. }
  447. std::vector<char const*> p4_sync;
  448. SetP4Options(p4_sync);
  449. p4_sync.push_back("sync");
  450. // Get user-specified update options.
  451. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
  452. if(opts.empty())
  453. {
  454. opts = this->CTest->GetCTestConfiguration("P4UpdateOptions");
  455. }
  456. std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str());
  457. for(std::vector<std::string>::const_iterator ai = args.begin();
  458. ai != args.end(); ++ai)
  459. {
  460. p4_sync.push_back(ai->c_str());
  461. }
  462. std::string source = this->SourceDirectory + "/...";
  463. // Specify the start time for nightly testing.
  464. if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
  465. {
  466. std::string date = this->GetNightlyTime();
  467. //CTest reports the date as YYYY-MM-DD, Perforce needs it as YYYY/MM/DD
  468. std::replace(date.begin(), date.end(), '-', '/');
  469. //Revision specification: /...@"YYYY/MM/DD HH:MM:SS"
  470. source.append("@\"").append(date).append("\"");
  471. }
  472. p4_sync.push_back(source.c_str());
  473. p4_sync.push_back(0);
  474. OutputLogger out(this->Log, "p4_sync-out> ");
  475. OutputLogger err(this->Log, "p4_sync-err> ");
  476. return this->RunUpdateCommand(&p4_sync[0], &out, &err);
  477. }