1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmCTestVC.h"
4 
5 #include <cstdio>
6 #include <ctime>
7 #include <sstream>
8 #include <vector>
9 
10 #include "cmsys/Process.h"
11 
12 #include "cmCTest.h"
13 #include "cmSystemTools.h"
14 #include "cmValue.h"
15 #include "cmXMLWriter.h"
16 
cmCTestVC(cmCTest * ct,std::ostream & log)17 cmCTestVC::cmCTestVC(cmCTest* ct, std::ostream& log)
18   : CTest(ct)
19   , Log(log)
20 {
21   this->PathCount[PathUpdated] = 0;
22   this->PathCount[PathModified] = 0;
23   this->PathCount[PathConflicting] = 0;
24   this->Unknown.Date = "Unknown";
25   this->Unknown.Author = "Unknown";
26   this->Unknown.Rev = "Unknown";
27 }
28 
29 cmCTestVC::~cmCTestVC() = default;
30 
SetCommandLineTool(std::string const & tool)31 void cmCTestVC::SetCommandLineTool(std::string const& tool)
32 {
33   this->CommandLineTool = tool;
34 }
35 
SetSourceDirectory(std::string const & dir)36 void cmCTestVC::SetSourceDirectory(std::string const& dir)
37 {
38   this->SourceDirectory = dir;
39 }
40 
InitialCheckout(const std::string & command)41 bool cmCTestVC::InitialCheckout(const std::string& command)
42 {
43   cmCTestLog(this->CTest, HANDLER_OUTPUT,
44              "   First perform the initial checkout: " << command << "\n");
45 
46   // Make the parent directory in which to perform the checkout.
47   std::string parent = cmSystemTools::GetFilenamePath(this->SourceDirectory);
48   cmCTestLog(this->CTest, HANDLER_OUTPUT,
49              "   Perform checkout in directory: " << parent << "\n");
50   if (!cmSystemTools::MakeDirectory(parent)) {
51     cmCTestLog(this->CTest, ERROR_MESSAGE,
52                "Cannot create directory: " << parent << std::endl);
53     return false;
54   }
55 
56   // Construct the initial checkout command line.
57   std::vector<std::string> args = cmSystemTools::ParseArguments(command);
58   std::vector<char const*> vc_co;
59   vc_co.reserve(args.size() + 1);
60   for (std::string const& arg : args) {
61     vc_co.push_back(arg.c_str());
62   }
63   vc_co.push_back(nullptr);
64 
65   // Run the initial checkout command and log its output.
66   this->Log << "--- Begin Initial Checkout ---\n";
67   OutputLogger out(this->Log, "co-out> ");
68   OutputLogger err(this->Log, "co-err> ");
69   bool result = this->RunChild(&vc_co[0], &out, &err, parent.c_str());
70   this->Log << "--- End Initial Checkout ---\n";
71   if (!result) {
72     cmCTestLog(this->CTest, ERROR_MESSAGE,
73                "Initial checkout failed!" << std::endl);
74   }
75   return result;
76 }
77 
RunChild(char const * const * cmd,OutputParser * out,OutputParser * err,const char * workDir,Encoding encoding)78 bool cmCTestVC::RunChild(char const* const* cmd, OutputParser* out,
79                          OutputParser* err, const char* workDir,
80                          Encoding encoding)
81 {
82   this->Log << cmCTestVC::ComputeCommandLine(cmd) << "\n";
83 
84   cmsysProcess* cp = cmsysProcess_New();
85   cmsysProcess_SetCommand(cp, cmd);
86   workDir = workDir ? workDir : this->SourceDirectory.c_str();
87   cmsysProcess_SetWorkingDirectory(cp, workDir);
88   cmCTestVC::RunProcess(cp, out, err, encoding);
89   int result = cmsysProcess_GetExitValue(cp);
90   cmsysProcess_Delete(cp);
91   return result == 0;
92 }
93 
ComputeCommandLine(char const * const * cmd)94 std::string cmCTestVC::ComputeCommandLine(char const* const* cmd)
95 {
96   std::ostringstream line;
97   const char* sep = "";
98   for (const char* const* arg = cmd; *arg; ++arg) {
99     line << sep << "\"" << *arg << "\"";
100     sep = " ";
101   }
102   return line.str();
103 }
104 
RunUpdateCommand(char const * const * cmd,OutputParser * out,OutputParser * err,Encoding encoding)105 bool cmCTestVC::RunUpdateCommand(char const* const* cmd, OutputParser* out,
106                                  OutputParser* err, Encoding encoding)
107 {
108   // Report the command line.
109   this->UpdateCommandLine = this->ComputeCommandLine(cmd);
110   if (this->CTest->GetShowOnly()) {
111     this->Log << this->UpdateCommandLine << "\n";
112     return true;
113   }
114 
115   // Run the command.
116   return this->RunChild(cmd, out, err, nullptr, encoding);
117 }
118 
GetNightlyTime()119 std::string cmCTestVC::GetNightlyTime()
120 {
121   // Get the nightly start time corresponding to the current dau.
122   struct tm* t = this->CTest->GetNightlyTime(
123     this->CTest->GetCTestConfiguration("NightlyStartTime"),
124     this->CTest->GetTomorrowTag());
125   char current_time[1024];
126   sprintf(current_time, "%04d-%02d-%02d %02d:%02d:%02d", t->tm_year + 1900,
127           t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
128   return std::string(current_time);
129 }
130 
Cleanup()131 void cmCTestVC::Cleanup()
132 {
133   this->Log << "--- Begin Cleanup ---\n";
134   this->CleanupImpl();
135   this->Log << "--- End Cleanup ---\n";
136 }
137 
CleanupImpl()138 void cmCTestVC::CleanupImpl()
139 {
140   // We do no cleanup by default.
141 }
142 
Update()143 bool cmCTestVC::Update()
144 {
145   bool result = true;
146 
147   // Use the explicitly specified version.
148   std::string versionOverride =
149     this->CTest->GetCTestConfiguration("UpdateVersionOverride");
150   if (!versionOverride.empty()) {
151     this->SetNewRevision(versionOverride);
152     return true;
153   }
154 
155   // if update version only is on then do not actually update,
156   // just note the current version and finish
157   if (!cmIsOn(this->CTest->GetCTestConfiguration("UpdateVersionOnly"))) {
158     result = this->NoteOldRevision() && result;
159     this->Log << "--- Begin Update ---\n";
160     result = this->UpdateImpl() && result;
161     this->Log << "--- End Update ---\n";
162   }
163   result = this->NoteNewRevision() && result;
164   return result;
165 }
166 
NoteOldRevision()167 bool cmCTestVC::NoteOldRevision()
168 {
169   // We do nothing by default.
170   return true;
171 }
172 
NoteNewRevision()173 bool cmCTestVC::NoteNewRevision()
174 {
175   // We do nothing by default.
176   return true;
177 }
178 
SetNewRevision(std::string const &)179 void cmCTestVC::SetNewRevision(std::string const& /*unused*/)
180 {
181   // We do nothing by default.
182 }
183 
UpdateImpl()184 bool cmCTestVC::UpdateImpl()
185 {
186   cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
187              "* Unknown VCS tool, not updating!" << std::endl);
188   return true;
189 }
190 
WriteXML(cmXMLWriter & xml)191 bool cmCTestVC::WriteXML(cmXMLWriter& xml)
192 {
193   this->Log << "--- Begin Revisions ---\n";
194   bool result = this->WriteXMLUpdates(xml);
195   this->Log << "--- End Revisions ---\n";
196   return result;
197 }
198 
WriteXMLUpdates(cmXMLWriter &)199 bool cmCTestVC::WriteXMLUpdates(cmXMLWriter& /*unused*/)
200 {
201   cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
202              "* CTest cannot extract updates for this VCS tool.\n");
203   return true;
204 }
205 
WriteXMLEntry(cmXMLWriter & xml,std::string const & path,std::string const & name,std::string const & full,File const & f)206 void cmCTestVC::WriteXMLEntry(cmXMLWriter& xml, std::string const& path,
207                               std::string const& name, std::string const& full,
208                               File const& f)
209 {
210   static const char* desc[3] = { "Updated", "Modified", "Conflicting" };
211   Revision const& rev = f.Rev ? *f.Rev : this->Unknown;
212   std::string prior = f.PriorRev ? f.PriorRev->Rev : std::string("Unknown");
213   xml.StartElement(desc[f.Status]);
214   xml.Element("File", name);
215   xml.Element("Directory", path);
216   xml.Element("FullName", full);
217   xml.Element("CheckinDate", rev.Date);
218   xml.Element("Author", rev.Author);
219   xml.Element("Email", rev.EMail);
220   xml.Element("Committer", rev.Committer);
221   xml.Element("CommitterEmail", rev.CommitterEMail);
222   xml.Element("CommitDate", rev.CommitDate);
223   xml.Element("Log", rev.Log);
224   xml.Element("Revision", rev.Rev);
225   xml.Element("PriorRevision", prior);
226   xml.EndElement();
227   ++this->PathCount[f.Status];
228 }
229