cmCTestUpdateCommand.cxx 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. #include "cmCTestUpdateCommand.h"
  4. #include <chrono>
  5. #include <sstream>
  6. #include <string>
  7. #include <cm/memory>
  8. #include <cmext/string_view>
  9. #include "cmArgumentParser.h"
  10. #include "cmCLocaleEnvironmentScope.h"
  11. #include "cmCTest.h"
  12. #include "cmCTestBZR.h"
  13. #include "cmCTestCVS.h"
  14. #include "cmCTestGIT.h"
  15. #include "cmCTestHG.h"
  16. #include "cmCTestP4.h"
  17. #include "cmCTestSVN.h"
  18. #include "cmCTestVC.h"
  19. #include "cmExecutionStatus.h"
  20. #include "cmGeneratedFileStream.h"
  21. #include "cmMakefile.h"
  22. #include "cmStringAlgorithms.h"
  23. #include "cmSystemTools.h"
  24. #include "cmVersion.h"
  25. #include "cmXMLWriter.h"
  26. namespace {
  27. enum
  28. {
  29. e_UNKNOWN = 0,
  30. e_CVS,
  31. e_SVN,
  32. e_BZR,
  33. e_GIT,
  34. e_HG,
  35. e_P4,
  36. e_LAST
  37. };
  38. char const* TypeToString(int type)
  39. {
  40. // clang-format off
  41. switch (type) {
  42. case e_CVS: return "CVS";
  43. case e_SVN: return "SVN";
  44. case e_BZR: return "BZR";
  45. case e_GIT: return "GIT";
  46. case e_HG: return "HG";
  47. case e_P4: return "P4";
  48. default: return "Unknown";
  49. }
  50. // clang-format on
  51. }
  52. char const* TypeToCommandKey(int type)
  53. {
  54. // clang-format off
  55. switch (type) {
  56. case e_CVS: return "CTEST_CVS_COMMAND";
  57. case e_SVN: return "CTEST_SVN_COMMAND";
  58. case e_BZR: return "CTEST_BZR_COMMAND";
  59. case e_GIT: return "CTEST_GIT_COMMAND";
  60. case e_HG: return "CTEST_HG_COMMAND";
  61. case e_P4: return "CTEST_P4_COMMAND";
  62. default: return nullptr;
  63. }
  64. // clang-format on
  65. }
  66. int DetermineType(std::string const& cmd)
  67. {
  68. std::string stype = cmSystemTools::LowerCase(cmd);
  69. if (stype.find("cvs") != std::string::npos) {
  70. return e_CVS;
  71. }
  72. if (stype.find("svn") != std::string::npos) {
  73. return e_SVN;
  74. }
  75. if (stype.find("bzr") != std::string::npos) {
  76. return e_BZR;
  77. }
  78. if (stype.find("git") != std::string::npos) {
  79. return e_GIT;
  80. }
  81. if (stype.find("hg") != std::string::npos) {
  82. return e_HG;
  83. }
  84. if (stype.find("p4") != std::string::npos) {
  85. return e_P4;
  86. }
  87. return e_UNKNOWN;
  88. }
  89. int DetectVCS(std::string const& dir)
  90. {
  91. if (cmSystemTools::FileExists(cmStrCat(dir, "/CVS"))) {
  92. return e_CVS;
  93. }
  94. if (cmSystemTools::FileExists(cmStrCat(dir, "/.svn"))) {
  95. return e_SVN;
  96. }
  97. if (cmSystemTools::FileExists(cmStrCat(dir, "/.bzr"))) {
  98. return e_BZR;
  99. }
  100. if (cmSystemTools::FileExists(cmStrCat(dir, "/.git"))) {
  101. return e_GIT;
  102. }
  103. if (cmSystemTools::FileExists(cmStrCat(dir, "/.hg"))) {
  104. return e_HG;
  105. }
  106. if (cmSystemTools::FileExists(cmStrCat(dir, "/.p4"))) {
  107. return e_P4;
  108. }
  109. if (cmSystemTools::FileExists(cmStrCat(dir, "/.p4config"))) {
  110. return e_P4;
  111. }
  112. return e_UNKNOWN;
  113. }
  114. std::unique_ptr<cmCTestVC> MakeVC(int type, cmCTest* ctest, cmMakefile* mf,
  115. std::ostream& os)
  116. {
  117. // clang-format off
  118. switch (type) {
  119. case e_CVS: return cm::make_unique<cmCTestCVS>(ctest, mf, os);
  120. case e_SVN: return cm::make_unique<cmCTestSVN>(ctest, mf, os);
  121. case e_BZR: return cm::make_unique<cmCTestBZR>(ctest, mf, os);
  122. case e_GIT: return cm::make_unique<cmCTestGIT>(ctest, mf, os);
  123. case e_HG: return cm::make_unique<cmCTestHG> (ctest, mf, os);
  124. case e_P4: return cm::make_unique<cmCTestP4> (ctest, mf, os);
  125. default: return cm::make_unique<cmCTestVC> (ctest, mf, os);
  126. }
  127. // clang-format on
  128. }
  129. } // namespace
  130. bool cmCTestUpdateCommand::ExecuteUpdate(UpdateArguments& args,
  131. cmExecutionStatus& status) const
  132. {
  133. cmMakefile& mf = status.GetMakefile();
  134. std::string const& source_dir = !args.Source.empty()
  135. ? args.Source
  136. : mf.GetSafeDefinition("CTEST_SOURCE_DIRECTORY");
  137. if (source_dir.empty()) {
  138. status.SetError("called with no source directory specified. "
  139. "Use SOURCE argument or set CTEST_SOURCE_DIRECTORY.");
  140. return false;
  141. }
  142. std::string const currentTag = this->CTest->GetCurrentTag();
  143. if (currentTag.empty()) {
  144. status.SetError("called with no current tag.");
  145. return false;
  146. }
  147. // Detect the VCS managing the source tree.
  148. int updateType = DetectVCS(source_dir);
  149. // Get update command
  150. std::string updateCommand = mf.GetSafeDefinition("CTEST_UPDATE_COMMAND");
  151. if (updateType == e_UNKNOWN && !updateCommand.empty()) {
  152. updateType = DetermineType(updateCommand);
  153. }
  154. if (updateType == e_UNKNOWN) {
  155. updateType = DetermineType(mf.GetSafeDefinition("CTEST_UPDATE_TYPE"));
  156. }
  157. // If no update command was specified, lookup one for this VCS tool.
  158. if (updateCommand.empty()) {
  159. char const* key = TypeToCommandKey(updateType);
  160. if (key) {
  161. updateCommand = mf.GetSafeDefinition(key);
  162. }
  163. if (updateCommand.empty()) {
  164. std::ostringstream e;
  165. e << "called with no update command specified. "
  166. "Please set CTEST_UPDATE_COMMAND";
  167. if (key) {
  168. e << " or " << key;
  169. }
  170. e << '.';
  171. status.SetError(e.str());
  172. return false;
  173. }
  174. }
  175. cmGeneratedFileStream ofs;
  176. if (!this->CTest->GetShowOnly()) {
  177. std::string logFile = cmStrCat("LastUpdate_", currentTag, ".log");
  178. if (!this->CTest->OpenOutputFile("Temporary", logFile, ofs)) {
  179. status.SetError(cmStrCat("cannot create log file: ", logFile));
  180. return false;
  181. }
  182. }
  183. cmGeneratedFileStream os;
  184. if (!this->CTest->OpenOutputFile(currentTag, "Update.xml", os, true)) {
  185. status.SetError("cannot create resulting XML file: Update.xml");
  186. return false;
  187. }
  188. this->CTest->AddSubmitFile(cmCTest::PartUpdate, "Update.xml");
  189. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  190. " Updating " << TypeToString(updateType)
  191. << " repository: " << source_dir << '\n',
  192. args.Quiet);
  193. // Make sure VCS tool messages are in English so we can parse them.
  194. cmCLocaleEnvironmentScope fixLocale;
  195. static_cast<void>(fixLocale);
  196. // Create an object to interact with the VCS tool.
  197. std::unique_ptr<cmCTestVC> vc = MakeVC(updateType, this->CTest, &mf, ofs);
  198. vc->SetCommandLineTool(updateCommand);
  199. vc->SetSourceDirectory(source_dir);
  200. // Cleanup the working tree.
  201. vc->Cleanup();
  202. std::string start_time = this->CTest->CurrentTime();
  203. auto start_time_time = std::chrono::system_clock::now();
  204. auto elapsed_time_start = std::chrono::steady_clock::now();
  205. bool updated = vc->Update();
  206. std::string buildname =
  207. cmCTest::SafeBuildIdField(mf.GetSafeDefinition("CTEST_BUILD_NAME"));
  208. cmXMLWriter xml(os);
  209. xml.StartDocument();
  210. xml.StartElement("Update");
  211. xml.Attribute("mode", "Client");
  212. xml.Attribute("Generator",
  213. std::string("ctest-") + cmVersion::GetCMakeVersion());
  214. xml.Element("Site", mf.GetSafeDefinition("CTEST_SITE"));
  215. xml.Element("BuildName", buildname);
  216. xml.Element("BuildStamp",
  217. this->CTest->GetCurrentTag() + "-" +
  218. this->CTest->GetTestGroupString());
  219. xml.Element("StartDateTime", start_time);
  220. xml.Element("StartTime", start_time_time);
  221. xml.Element("UpdateCommand", vc->GetUpdateCommandLine());
  222. xml.Element("UpdateType", TypeToString(updateType));
  223. std::string changeId = mf.GetSafeDefinition("CTEST_CHANGE_ID");
  224. if (!changeId.empty()) {
  225. xml.Element("ChangeId", changeId);
  226. }
  227. bool loadedMods = vc->WriteXML(xml);
  228. int localModifications = 0;
  229. int numUpdated = vc->GetPathCount(cmCTestVC::PathUpdated);
  230. if (numUpdated) {
  231. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  232. " Found " << numUpdated << " updated files\n",
  233. args.Quiet);
  234. }
  235. if (int numModified = vc->GetPathCount(cmCTestVC::PathModified)) {
  236. cmCTestOptionalLog(
  237. this->CTest, HANDLER_OUTPUT,
  238. " Found " << numModified << " locally modified files\n", args.Quiet);
  239. localModifications += numModified;
  240. }
  241. if (int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting)) {
  242. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  243. " Found " << numConflicting << " conflicting files\n",
  244. args.Quiet);
  245. localModifications += numConflicting;
  246. }
  247. cmCTestOptionalLog(this->CTest, DEBUG, "End" << std::endl, args.Quiet);
  248. std::string end_time = this->CTest->CurrentTime();
  249. xml.Element("EndDateTime", end_time);
  250. xml.Element("EndTime", std::chrono::system_clock::now());
  251. xml.Element("ElapsedMinutes",
  252. std::chrono::duration_cast<std::chrono::minutes>(
  253. std::chrono::steady_clock::now() - elapsed_time_start)
  254. .count());
  255. xml.StartElement("UpdateReturnStatus");
  256. if (localModifications) {
  257. xml.Content("Update error: "
  258. "There are modified or conflicting files in the repository");
  259. cmCTestLog(this->CTest, WARNING,
  260. " There are modified or conflicting files in the repository"
  261. << std::endl);
  262. }
  263. if (!updated) {
  264. xml.Content("Update command failed:\n");
  265. xml.Content(vc->GetUpdateCommandLine());
  266. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  267. " Update command failed: " << vc->GetUpdateCommandLine()
  268. << "\n");
  269. }
  270. xml.EndElement(); // UpdateReturnStatus
  271. xml.EndElement(); // Update
  272. xml.EndDocument();
  273. if (!args.ReturnValue.empty()) {
  274. mf.AddDefinition(args.ReturnValue,
  275. std::to_string(updated && loadedMods ? numUpdated : -1));
  276. }
  277. return true;
  278. }
  279. bool cmCTestUpdateCommand::InitialPass(std::vector<std::string> const& args,
  280. cmExecutionStatus& status) const
  281. {
  282. static auto const parser =
  283. cmArgumentParser<UpdateArguments>{ MakeBasicParser<UpdateArguments>() }
  284. .Bind("SOURCE"_s, &UpdateArguments::Source)
  285. .Bind("RETURN_VALUE"_s, &UpdateArguments::ReturnValue)
  286. .Bind("QUIET"_s, &UpdateArguments::Quiet);
  287. return this->Invoke(parser, args, status, [&](UpdateArguments& a) {
  288. return this->ExecuteUpdate(a, status);
  289. });
  290. }