|
|
@@ -2,94 +2,329 @@
|
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
|
#include "cmCTestUpdateCommand.h"
|
|
|
|
|
|
-#include <utility>
|
|
|
+#include <chrono>
|
|
|
+#include <sstream>
|
|
|
+#include <string>
|
|
|
+#include <vector>
|
|
|
|
|
|
#include <cm/memory>
|
|
|
+#include <cmext/string_view>
|
|
|
|
|
|
+#include "cmArgumentParser.h"
|
|
|
+#include "cmCLocaleEnvironmentScope.h"
|
|
|
#include "cmCTest.h"
|
|
|
-#include "cmCTestGenericHandler.h"
|
|
|
-#include "cmCTestUpdateHandler.h"
|
|
|
+#include "cmCTestBZR.h"
|
|
|
+#include "cmCTestCVS.h"
|
|
|
+#include "cmCTestGIT.h"
|
|
|
+#include "cmCTestHG.h"
|
|
|
+#include "cmCTestP4.h"
|
|
|
+#include "cmCTestSVN.h"
|
|
|
+#include "cmCTestVC.h"
|
|
|
#include "cmExecutionStatus.h"
|
|
|
+#include "cmGeneratedFileStream.h"
|
|
|
#include "cmMakefile.h"
|
|
|
+#include "cmStringAlgorithms.h"
|
|
|
#include "cmSystemTools.h"
|
|
|
+#include "cmVersion.h"
|
|
|
+#include "cmXMLWriter.h"
|
|
|
|
|
|
-std::unique_ptr<cmCTestGenericHandler> cmCTestUpdateCommand::InitializeHandler(
|
|
|
- HandlerArguments& args, cmExecutionStatus& status) const
|
|
|
+namespace {
|
|
|
+
|
|
|
+enum
|
|
|
+{
|
|
|
+ e_UNKNOWN = 0,
|
|
|
+ e_CVS,
|
|
|
+ e_SVN,
|
|
|
+ e_BZR,
|
|
|
+ e_GIT,
|
|
|
+ e_HG,
|
|
|
+ e_P4,
|
|
|
+ e_LAST
|
|
|
+};
|
|
|
+
|
|
|
+char const* TypeToString(int type)
|
|
|
+{
|
|
|
+ // clang-format off
|
|
|
+ switch (type) {
|
|
|
+ case e_CVS: return "CVS";
|
|
|
+ case e_SVN: return "SVN";
|
|
|
+ case e_BZR: return "BZR";
|
|
|
+ case e_GIT: return "GIT";
|
|
|
+ case e_HG: return "HG";
|
|
|
+ case e_P4: return "P4";
|
|
|
+ default: return "Unknown";
|
|
|
+ }
|
|
|
+ // clang-format on
|
|
|
+}
|
|
|
+
|
|
|
+char const* TypeToCommandKey(int type)
|
|
|
+{
|
|
|
+ // clang-format off
|
|
|
+ switch (type) {
|
|
|
+ case e_CVS: return "CTEST_CVS_COMMAND";
|
|
|
+ case e_SVN: return "CTEST_SVN_COMMAND";
|
|
|
+ case e_BZR: return "CTEST_BZR_COMMAND";
|
|
|
+ case e_GIT: return "CTEST_GIT_COMMAND";
|
|
|
+ case e_HG: return "CTEST_HG_COMMAND";
|
|
|
+ case e_P4: return "CTEST_P4_COMMAND";
|
|
|
+ default: return nullptr;
|
|
|
+ }
|
|
|
+ // clang-format on
|
|
|
+}
|
|
|
+
|
|
|
+int DetermineType(std::string const& cmd)
|
|
|
+{
|
|
|
+ std::string stype = cmSystemTools::LowerCase(cmd);
|
|
|
+ if (stype.find("cvs") != std::string::npos) {
|
|
|
+ return e_CVS;
|
|
|
+ }
|
|
|
+ if (stype.find("svn") != std::string::npos) {
|
|
|
+ return e_SVN;
|
|
|
+ }
|
|
|
+ if (stype.find("bzr") != std::string::npos) {
|
|
|
+ return e_BZR;
|
|
|
+ }
|
|
|
+ if (stype.find("git") != std::string::npos) {
|
|
|
+ return e_GIT;
|
|
|
+ }
|
|
|
+ if (stype.find("hg") != std::string::npos) {
|
|
|
+ return e_HG;
|
|
|
+ }
|
|
|
+ if (stype.find("p4") != std::string::npos) {
|
|
|
+ return e_P4;
|
|
|
+ }
|
|
|
+ return e_UNKNOWN;
|
|
|
+}
|
|
|
+
|
|
|
+int DetectVCS(std::string const& dir)
|
|
|
+{
|
|
|
+ if (cmSystemTools::FileExists(cmStrCat(dir, "/CVS"))) {
|
|
|
+ return e_CVS;
|
|
|
+ }
|
|
|
+ if (cmSystemTools::FileExists(cmStrCat(dir, "/.svn"))) {
|
|
|
+ return e_SVN;
|
|
|
+ }
|
|
|
+ if (cmSystemTools::FileExists(cmStrCat(dir, "/.bzr"))) {
|
|
|
+ return e_BZR;
|
|
|
+ }
|
|
|
+ if (cmSystemTools::FileExists(cmStrCat(dir, "/.git"))) {
|
|
|
+ return e_GIT;
|
|
|
+ }
|
|
|
+ if (cmSystemTools::FileExists(cmStrCat(dir, "/.hg"))) {
|
|
|
+ return e_HG;
|
|
|
+ }
|
|
|
+ if (cmSystemTools::FileExists(cmStrCat(dir, "/.p4"))) {
|
|
|
+ return e_P4;
|
|
|
+ }
|
|
|
+ if (cmSystemTools::FileExists(cmStrCat(dir, "/.p4config"))) {
|
|
|
+ return e_P4;
|
|
|
+ }
|
|
|
+ return e_UNKNOWN;
|
|
|
+}
|
|
|
+
|
|
|
+std::unique_ptr<cmCTestVC> MakeVC(int type, cmCTest* ctest, cmMakefile* mf,
|
|
|
+ std::ostream& os)
|
|
|
+{
|
|
|
+ // clang-format off
|
|
|
+ switch (type) {
|
|
|
+ case e_CVS: return cm::make_unique<cmCTestCVS>(ctest, mf, os);
|
|
|
+ case e_SVN: return cm::make_unique<cmCTestSVN>(ctest, mf, os);
|
|
|
+ case e_BZR: return cm::make_unique<cmCTestBZR>(ctest, mf, os);
|
|
|
+ case e_GIT: return cm::make_unique<cmCTestGIT>(ctest, mf, os);
|
|
|
+ case e_HG: return cm::make_unique<cmCTestHG> (ctest, mf, os);
|
|
|
+ case e_P4: return cm::make_unique<cmCTestP4> (ctest, mf, os);
|
|
|
+ default: return cm::make_unique<cmCTestVC> (ctest, mf, os);
|
|
|
+ }
|
|
|
+ // clang-format on
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace
|
|
|
+
|
|
|
+bool cmCTestUpdateCommand::ExecuteUpdate(UpdateArguments& args,
|
|
|
+ cmExecutionStatus& status) const
|
|
|
{
|
|
|
cmMakefile& mf = status.GetMakefile();
|
|
|
- if (!args.Source.empty()) {
|
|
|
- this->CTest->SetCTestConfiguration(
|
|
|
- "SourceDirectory", cmSystemTools::CollapseFullPath(args.Source),
|
|
|
- args.Quiet);
|
|
|
- } else {
|
|
|
- this->CTest->SetCTestConfiguration(
|
|
|
- "SourceDirectory",
|
|
|
- cmSystemTools::CollapseFullPath(
|
|
|
- mf.GetSafeDefinition("CTEST_SOURCE_DIRECTORY")),
|
|
|
- args.Quiet);
|
|
|
- }
|
|
|
- std::string source_dir =
|
|
|
- this->CTest->GetCTestConfiguration("SourceDirectory");
|
|
|
-
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "UpdateCommand", "CTEST_UPDATE_COMMAND", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "UpdateOptions", "CTEST_UPDATE_OPTIONS", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "CVSCommand", "CTEST_CVS_COMMAND", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "CVSUpdateOptions", "CTEST_CVS_UPDATE_OPTIONS", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "SVNCommand", "CTEST_SVN_COMMAND", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "SVNOptions", "CTEST_SVN_OPTIONS", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "BZRCommand", "CTEST_BZR_COMMAND", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "GITCommand", "CTEST_GIT_COMMAND", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "GITInitSubmodules", "CTEST_GIT_INIT_SUBMODULES", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "GITUpdateCustom", "CTEST_GIT_UPDATE_CUSTOM", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "UpdateVersionOnly", "CTEST_UPDATE_VERSION_ONLY", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "UpdateVersionOverride", "CTEST_UPDATE_VERSION_OVERRIDE", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "HGCommand", "CTEST_HG_COMMAND", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "HGUpdateOptions", "CTEST_HG_UPDATE_OPTIONS", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "P4Command", "CTEST_P4_COMMAND", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "P4UpdateOptions", "CTEST_P4_UPDATE_OPTIONS", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "P4Client", "CTEST_P4_CLIENT", args.Quiet);
|
|
|
- this->CTest->SetCTestConfigurationFromCMakeVariable(
|
|
|
- &mf, "P4Options", "CTEST_P4_OPTIONS", args.Quiet);
|
|
|
-
|
|
|
- auto handler = cm::make_unique<cmCTestUpdateHandler>(this->CTest);
|
|
|
+
|
|
|
+ std::string const& source_dir = !args.Source.empty()
|
|
|
+ ? args.Source
|
|
|
+ : mf.GetSafeDefinition("CTEST_SOURCE_DIRECTORY");
|
|
|
if (source_dir.empty()) {
|
|
|
- status.SetError("source directory not specified. Please use SOURCE tag");
|
|
|
- return nullptr;
|
|
|
+ status.SetError("called with no source directory specified. "
|
|
|
+ "Use SOURCE argument or set CTEST_SOURCE_DIRECTORY.");
|
|
|
+ return false;
|
|
|
}
|
|
|
- handler->SourceDirectory = source_dir;
|
|
|
- handler->SetQuiet(args.Quiet);
|
|
|
- return std::unique_ptr<cmCTestGenericHandler>(std::move(handler));
|
|
|
+
|
|
|
+ std::string const currentTag = this->CTest->GetCurrentTag();
|
|
|
+ if (currentTag.empty()) {
|
|
|
+ status.SetError("called with no current tag.");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Detect the VCS managing the source tree.
|
|
|
+ int updateType = DetectVCS(source_dir);
|
|
|
+
|
|
|
+ // Get update command
|
|
|
+ std::string updateCommand = mf.GetSafeDefinition("CTEST_UPDATE_COMMAND");
|
|
|
+
|
|
|
+ if (updateType == e_UNKNOWN && !updateCommand.empty()) {
|
|
|
+ updateType = DetermineType(updateCommand);
|
|
|
+ }
|
|
|
+ if (updateType == e_UNKNOWN) {
|
|
|
+ updateType = DetermineType(mf.GetSafeDefinition("CTEST_UPDATE_TYPE"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // If no update command was specified, lookup one for this VCS tool.
|
|
|
+ if (updateCommand.empty()) {
|
|
|
+ const char* key = TypeToCommandKey(updateType);
|
|
|
+ if (key) {
|
|
|
+ updateCommand = mf.GetSafeDefinition(key);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (updateCommand.empty()) {
|
|
|
+ std::ostringstream e;
|
|
|
+ e << "called with no update command specified. "
|
|
|
+ "Please set CTEST_UPDATE_COMMAND";
|
|
|
+ if (key) {
|
|
|
+ e << " or " << key;
|
|
|
+ }
|
|
|
+ e << '.';
|
|
|
+ status.SetError(e.str());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ cmGeneratedFileStream ofs;
|
|
|
+ if (!this->CTest->GetShowOnly()) {
|
|
|
+ std::string logFile = cmStrCat("LastUpdate_", currentTag, ".log");
|
|
|
+ if (!this->CTest->OpenOutputFile("Temporary", logFile, ofs)) {
|
|
|
+ status.SetError(cmStrCat("cannot create log file: ", logFile));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ cmGeneratedFileStream os;
|
|
|
+ if (!this->CTest->OpenOutputFile(currentTag, "Update.xml", os, true)) {
|
|
|
+ status.SetError("cannot create resulting XML file: Update.xml");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ this->CTest->AddSubmitFile(cmCTest::PartUpdate, "Update.xml");
|
|
|
+
|
|
|
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
|
|
|
+ " Updating " << TypeToString(updateType)
|
|
|
+ << " repository: " << source_dir << '\n',
|
|
|
+ args.Quiet);
|
|
|
+
|
|
|
+ // Make sure VCS tool messages are in English so we can parse them.
|
|
|
+ cmCLocaleEnvironmentScope fixLocale;
|
|
|
+ static_cast<void>(fixLocale);
|
|
|
+
|
|
|
+ // Create an object to interact with the VCS tool.
|
|
|
+ std::unique_ptr<cmCTestVC> vc = MakeVC(updateType, this->CTest, &mf, ofs);
|
|
|
+
|
|
|
+ vc->SetCommandLineTool(updateCommand);
|
|
|
+ vc->SetSourceDirectory(source_dir);
|
|
|
+
|
|
|
+ // Cleanup the working tree.
|
|
|
+ vc->Cleanup();
|
|
|
+
|
|
|
+ std::string start_time = this->CTest->CurrentTime();
|
|
|
+ auto start_time_time = std::chrono::system_clock::now();
|
|
|
+ auto elapsed_time_start = std::chrono::steady_clock::now();
|
|
|
+
|
|
|
+ bool updated = vc->Update();
|
|
|
+ std::string buildname =
|
|
|
+ cmCTest::SafeBuildIdField(mf.GetSafeDefinition("CTEST_BUILD_NAME"));
|
|
|
+
|
|
|
+ cmXMLWriter xml(os);
|
|
|
+ xml.StartDocument();
|
|
|
+ xml.StartElement("Update");
|
|
|
+ xml.Attribute("mode", "Client");
|
|
|
+ xml.Attribute("Generator",
|
|
|
+ std::string("ctest-") + cmVersion::GetCMakeVersion());
|
|
|
+ xml.Element("Site", mf.GetSafeDefinition("CTEST_SITE"));
|
|
|
+ xml.Element("BuildName", buildname);
|
|
|
+ xml.Element("BuildStamp",
|
|
|
+ this->CTest->GetCurrentTag() + "-" +
|
|
|
+ this->CTest->GetTestGroupString());
|
|
|
+ xml.Element("StartDateTime", start_time);
|
|
|
+ xml.Element("StartTime", start_time_time);
|
|
|
+ xml.Element("UpdateCommand", vc->GetUpdateCommandLine());
|
|
|
+ xml.Element("UpdateType", TypeToString(updateType));
|
|
|
+
|
|
|
+ std::string changeId = mf.GetSafeDefinition("CTEST_CHANGE_ID");
|
|
|
+ if (!changeId.empty()) {
|
|
|
+ xml.Element("ChangeId", changeId);
|
|
|
+ }
|
|
|
+
|
|
|
+ bool loadedMods = vc->WriteXML(xml);
|
|
|
+
|
|
|
+ int localModifications = 0;
|
|
|
+ int numUpdated = vc->GetPathCount(cmCTestVC::PathUpdated);
|
|
|
+ if (numUpdated) {
|
|
|
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
|
|
|
+ " Found " << numUpdated << " updated files\n",
|
|
|
+ args.Quiet);
|
|
|
+ }
|
|
|
+ if (int numModified = vc->GetPathCount(cmCTestVC::PathModified)) {
|
|
|
+ cmCTestOptionalLog(
|
|
|
+ this->CTest, HANDLER_OUTPUT,
|
|
|
+ " Found " << numModified << " locally modified files\n", args.Quiet);
|
|
|
+ localModifications += numModified;
|
|
|
+ }
|
|
|
+ if (int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting)) {
|
|
|
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
|
|
|
+ " Found " << numConflicting << " conflicting files\n",
|
|
|
+ args.Quiet);
|
|
|
+ localModifications += numConflicting;
|
|
|
+ }
|
|
|
+
|
|
|
+ cmCTestOptionalLog(this->CTest, DEBUG, "End" << std::endl, args.Quiet);
|
|
|
+ std::string end_time = this->CTest->CurrentTime();
|
|
|
+ xml.Element("EndDateTime", end_time);
|
|
|
+ xml.Element("EndTime", std::chrono::system_clock::now());
|
|
|
+ xml.Element("ElapsedMinutes",
|
|
|
+ std::chrono::duration_cast<std::chrono::minutes>(
|
|
|
+ std::chrono::steady_clock::now() - elapsed_time_start)
|
|
|
+ .count());
|
|
|
+
|
|
|
+ xml.StartElement("UpdateReturnStatus");
|
|
|
+ if (localModifications) {
|
|
|
+ xml.Content("Update error: "
|
|
|
+ "There are modified or conflicting files in the repository");
|
|
|
+ cmCTestLog(this->CTest, WARNING,
|
|
|
+ " There are modified or conflicting files in the repository"
|
|
|
+ << std::endl);
|
|
|
+ }
|
|
|
+ if (!updated) {
|
|
|
+ xml.Content("Update command failed:\n");
|
|
|
+ xml.Content(vc->GetUpdateCommandLine());
|
|
|
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
|
|
|
+ " Update command failed: " << vc->GetUpdateCommandLine()
|
|
|
+ << "\n");
|
|
|
+ }
|
|
|
+ xml.EndElement(); // UpdateReturnStatus
|
|
|
+ xml.EndElement(); // Update
|
|
|
+ xml.EndDocument();
|
|
|
+
|
|
|
+ if (!args.ReturnValue.empty()) {
|
|
|
+ mf.AddDefinition(args.ReturnValue,
|
|
|
+ std::to_string(updated && loadedMods ? numUpdated : -1));
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
bool cmCTestUpdateCommand::InitialPass(std::vector<std::string> const& args,
|
|
|
cmExecutionStatus& status) const
|
|
|
{
|
|
|
- static auto const parser = MakeHandlerParser<HandlerArguments>();
|
|
|
+ static auto const parser =
|
|
|
+ cmArgumentParser<UpdateArguments>{ MakeBasicParser<UpdateArguments>() }
|
|
|
+ .Bind("SOURCE"_s, &UpdateArguments::Source)
|
|
|
+ .Bind("RETURN_VALUE"_s, &UpdateArguments::ReturnValue)
|
|
|
+ .Bind("QUIET"_s, &UpdateArguments::Quiet);
|
|
|
|
|
|
- return this->Invoke(parser, args, status, [&](HandlerArguments& a) {
|
|
|
- return this->ExecuteHandlerCommand(a, status);
|
|
|
+ return this->Invoke(parser, args, status, [&](UpdateArguments& a) {
|
|
|
+ return this->ExecuteUpdate(a, status);
|
|
|
});
|
|
|
}
|