resolve cyclic dependency with zstd
[platform/upstream/cmake.git] / Source / cmQtAutoGen.cxx
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 "cmQtAutoGen.h"
4
5 #include <algorithm>
6 #include <array>
7 #include <initializer_list>
8 #include <sstream>
9 #include <utility>
10
11 #include <cmext/algorithm>
12
13 #include "cmsys/FStream.hxx"
14 #include "cmsys/RegularExpression.hxx"
15
16 #include "cmDuration.h"
17 #include "cmProcessOutput.h"
18 #include "cmStringAlgorithms.h"
19 #include "cmSystemTools.h"
20
21 // - Static functions
22
23 /// @brief Merges newOpts into baseOpts
24 /// @arg valueOpts list of options that accept a value
25 static void MergeOptions(std::vector<std::string>& baseOpts,
26                          std::vector<std::string> const& newOpts,
27                          std::initializer_list<cm::string_view> valueOpts,
28                          bool isQt5OrLater)
29 {
30   if (newOpts.empty()) {
31     return;
32   }
33   if (baseOpts.empty()) {
34     baseOpts = newOpts;
35     return;
36   }
37
38   std::vector<std::string> extraOpts;
39   for (auto fit = newOpts.begin(), fitEnd = newOpts.end(); fit != fitEnd;
40        ++fit) {
41     std::string const& newOpt = *fit;
42     auto existIt = std::find(baseOpts.begin(), baseOpts.end(), newOpt);
43     if (existIt != baseOpts.end()) {
44       if (newOpt.size() >= 2) {
45         // Acquire the option name
46         std::string optName;
47         {
48           auto oit = newOpt.begin();
49           if (*oit == '-') {
50             ++oit;
51             if (isQt5OrLater && (*oit == '-')) {
52               ++oit;
53             }
54             optName.assign(oit, newOpt.end());
55           }
56         }
57         // Test if this is a value option and change the existing value
58         if (!optName.empty() && cm::contains(valueOpts, optName)) {
59           const auto existItNext(existIt + 1);
60           const auto fitNext(fit + 1);
61           if ((existItNext != baseOpts.end()) && (fitNext != fitEnd)) {
62             *existItNext = *fitNext;
63             ++fit;
64           }
65         }
66       }
67     } else {
68       extraOpts.push_back(newOpt);
69     }
70   }
71   // Append options
72   cm::append(baseOpts, extraOpts);
73 }
74
75 // - Class definitions
76
77 unsigned int const cmQtAutoGen::ParallelMax = 64;
78
79 #ifdef _WIN32
80 // Actually 32767 (see
81 // https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553) but we
82 // allow for a small margin
83 size_t const cmQtAutoGen::CommandLineLengthMax = 32000;
84 #endif
85
86 cm::string_view cmQtAutoGen::GeneratorName(GenT genType)
87 {
88   switch (genType) {
89     case GenT::GEN:
90       return "AutoGen";
91     case GenT::MOC:
92       return "AutoMoc";
93     case GenT::UIC:
94       return "AutoUic";
95     case GenT::RCC:
96       return "AutoRcc";
97   }
98   return "AutoGen";
99 }
100
101 cm::string_view cmQtAutoGen::GeneratorNameUpper(GenT genType)
102 {
103   switch (genType) {
104     case GenT::GEN:
105       return "AUTOGEN";
106     case GenT::MOC:
107       return "AUTOMOC";
108     case GenT::UIC:
109       return "AUTOUIC";
110     case GenT::RCC:
111       return "AUTORCC";
112   }
113   return "AUTOGEN";
114 }
115
116 std::string cmQtAutoGen::Tools(bool moc, bool uic, bool rcc)
117 {
118   std::array<cm::string_view, 3> lst;
119   decltype(lst)::size_type num = 0;
120   if (moc) {
121     lst.at(num++) = "AUTOMOC";
122   }
123   if (uic) {
124     lst.at(num++) = "AUTOUIC";
125   }
126   if (rcc) {
127     lst.at(num++) = "AUTORCC";
128   }
129   switch (num) {
130     case 1:
131       return std::string(lst[0]);
132     case 2:
133       return cmStrCat(lst[0], " and ", lst[1]);
134     case 3:
135       return cmStrCat(lst[0], ", ", lst[1], " and ", lst[2]);
136     default:
137       break;
138   }
139   return std::string();
140 }
141
142 std::string cmQtAutoGen::Quoted(cm::string_view text)
143 {
144   static std::initializer_list<std::pair<const char*, const char*>> const
145     replacements = { { "\\", "\\\\" }, { "\"", "\\\"" }, { "\a", "\\a" },
146                      { "\b", "\\b" },  { "\f", "\\f" },  { "\n", "\\n" },
147                      { "\r", "\\r" },  { "\t", "\\t" },  { "\v", "\\v" } };
148
149   std::string res(text);
150   for (auto const& pair : replacements) {
151     cmSystemTools::ReplaceString(res, pair.first, pair.second);
152   }
153   return cmStrCat('"', res, '"');
154 }
155
156 std::string cmQtAutoGen::QuotedCommand(std::vector<std::string> const& command)
157 {
158   std::string res;
159   for (std::string const& item : command) {
160     if (!res.empty()) {
161       res.push_back(' ');
162     }
163     std::string const cesc = cmQtAutoGen::Quoted(item);
164     if (item.empty() || (cesc.size() > (item.size() + 2)) ||
165         (cesc.find(' ') != std::string::npos)) {
166       res += cesc;
167     } else {
168       res += item;
169     }
170   }
171   return res;
172 }
173
174 std::string cmQtAutoGen::FileNameWithoutLastExtension(cm::string_view filename)
175 {
176   auto slashPos = filename.rfind('/');
177   if (slashPos != cm::string_view::npos) {
178     filename.remove_prefix(slashPos + 1);
179   }
180   auto dotPos = filename.rfind('.');
181   return std::string(filename.substr(0, dotPos));
182 }
183
184 std::string cmQtAutoGen::ParentDir(cm::string_view filename)
185 {
186   auto slashPos = filename.rfind('/');
187   if (slashPos == cm::string_view::npos) {
188     return std::string();
189   }
190   return std::string(filename.substr(0, slashPos));
191 }
192
193 std::string cmQtAutoGen::SubDirPrefix(cm::string_view filename)
194 {
195   auto slashPos = filename.rfind('/');
196   if (slashPos == cm::string_view::npos) {
197     return std::string();
198   }
199   return std::string(filename.substr(0, slashPos + 1));
200 }
201
202 std::string cmQtAutoGen::AppendFilenameSuffix(cm::string_view filename,
203                                               cm::string_view suffix)
204 {
205   auto dotPos = filename.rfind('.');
206   if (dotPos == cm::string_view::npos) {
207     return cmStrCat(filename, suffix);
208   }
209   return cmStrCat(filename.substr(0, dotPos), suffix,
210                   filename.substr(dotPos, filename.size() - dotPos));
211 }
212
213 void cmQtAutoGen::UicMergeOptions(std::vector<std::string>& baseOpts,
214                                   std::vector<std::string> const& newOpts,
215                                   bool isQt5OrLater)
216 {
217   static std::initializer_list<cm::string_view> const valueOpts = {
218     "tr",      "translate", "postfix", "generator",
219     "include", // Since Qt 5.3
220     "g"
221   };
222   MergeOptions(baseOpts, newOpts, valueOpts, isQt5OrLater);
223 }
224
225 void cmQtAutoGen::RccMergeOptions(std::vector<std::string>& baseOpts,
226                                   std::vector<std::string> const& newOpts,
227                                   bool isQt5OrLater)
228 {
229   static std::initializer_list<cm::string_view> const valueOpts = {
230     "name", "root", "compress", "threshold"
231   };
232   MergeOptions(baseOpts, newOpts, valueOpts, isQt5OrLater);
233 }
234
235 static void RccListParseContent(std::string const& content,
236                                 std::vector<std::string>& files)
237 {
238   cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
239   cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
240
241   const char* contentChars = content.c_str();
242   while (fileMatchRegex.find(contentChars)) {
243     std::string const qrcEntry = fileMatchRegex.match(1);
244     contentChars += qrcEntry.size();
245     {
246       fileReplaceRegex.find(qrcEntry);
247       std::string const tag = fileReplaceRegex.match(1);
248       files.push_back(qrcEntry.substr(tag.size()));
249     }
250   }
251 }
252
253 static bool RccListParseOutput(std::string const& rccStdOut,
254                                std::string const& rccStdErr,
255                                std::vector<std::string>& files,
256                                std::string& error)
257 {
258   // Lambda to strip CR characters
259   auto StripCR = [](std::string& line) {
260     std::string::size_type cr = line.find('\r');
261     if (cr != std::string::npos) {
262       line = line.substr(0, cr);
263     }
264   };
265
266   // Parse rcc std output
267   {
268     std::istringstream ostr(rccStdOut);
269     std::string oline;
270     while (std::getline(ostr, oline)) {
271       StripCR(oline);
272       if (!oline.empty()) {
273         files.push_back(oline);
274       }
275     }
276   }
277   // Parse rcc error output
278   {
279     std::istringstream estr(rccStdErr);
280     std::string eline;
281     while (std::getline(estr, eline)) {
282       StripCR(eline);
283       if (cmHasLiteralPrefix(eline, "RCC: Error in")) {
284         static std::string const searchString = "Cannot find file '";
285
286         std::string::size_type pos = eline.find(searchString);
287         if (pos == std::string::npos) {
288           error = cmStrCat("rcc lists unparsable output:\n",
289                            cmQtAutoGen::Quoted(eline), '\n');
290           return false;
291         }
292         pos += searchString.length();
293         std::string::size_type sz = eline.size() - pos - 1;
294         files.push_back(eline.substr(pos, sz));
295       }
296     }
297   }
298
299   return true;
300 }
301
302 cmQtAutoGen::RccLister::RccLister() = default;
303
304 cmQtAutoGen::RccLister::RccLister(std::string rccExecutable,
305                                   std::vector<std::string> listOptions)
306   : RccExcutable_(std::move(rccExecutable))
307   , ListOptions_(std::move(listOptions))
308 {
309 }
310
311 bool cmQtAutoGen::RccLister::list(std::string const& qrcFile,
312                                   std::vector<std::string>& files,
313                                   std::string& error, bool verbose) const
314 {
315   error.clear();
316
317   if (!cmSystemTools::FileExists(qrcFile, true)) {
318     error =
319       cmStrCat("The resource file ", Quoted(qrcFile), " does not exist.");
320     return false;
321   }
322
323   // Run rcc list command in the directory of the qrc file with the pathless
324   // qrc file name argument.  This way rcc prints relative paths.
325   // This avoids issues on Windows when the qrc file is in a path that
326   // contains non-ASCII characters.
327   std::string const fileDir = cmSystemTools::GetFilenamePath(qrcFile);
328
329   if (!this->RccExcutable_.empty() &&
330       cmSystemTools::FileExists(this->RccExcutable_, true) &&
331       !this->ListOptions_.empty()) {
332
333     bool result = false;
334     int retVal = 0;
335     std::string rccStdOut;
336     std::string rccStdErr;
337     {
338       std::vector<std::string> cmd;
339       cmd.emplace_back(this->RccExcutable_);
340       cm::append(cmd, this->ListOptions_);
341       cmd.emplace_back(cmSystemTools::GetFilenameName(qrcFile));
342
343       // Log command
344       if (verbose) {
345         cmSystemTools::Stdout(
346           cmStrCat("Running command:\n", QuotedCommand(cmd), '\n'));
347       }
348
349       result = cmSystemTools::RunSingleCommand(
350         cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(),
351         cmSystemTools::OUTPUT_NONE, cmDuration::zero(), cmProcessOutput::Auto);
352     }
353     if (!result || retVal) {
354       error =
355         cmStrCat("The rcc list process failed for ", Quoted(qrcFile), '\n');
356       if (!rccStdOut.empty()) {
357         error += cmStrCat(rccStdOut, '\n');
358       }
359       if (!rccStdErr.empty()) {
360         error += cmStrCat(rccStdErr, '\n');
361       }
362       return false;
363     }
364     if (!RccListParseOutput(rccStdOut, rccStdErr, files, error)) {
365       return false;
366     }
367   } else {
368     // We can't use rcc for the file listing.
369     // Read the qrc file content into string and parse it.
370     {
371       std::string qrcContents;
372       {
373         cmsys::ifstream ifs(qrcFile.c_str());
374         if (ifs) {
375           std::ostringstream osst;
376           osst << ifs.rdbuf();
377           qrcContents = osst.str();
378         } else {
379           error = cmStrCat("The resource file ", Quoted(qrcFile),
380                            " is not readable\n");
381           return false;
382         }
383       }
384       // Parse string content
385       RccListParseContent(qrcContents, files);
386     }
387   }
388
389   // Convert relative paths to absolute paths
390   for (std::string& entry : files) {
391     entry = cmSystemTools::CollapseFullPath(entry, fileDir);
392   }
393   return true;
394 }