Coverage Report

Created: 2026-03-12 06:35

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