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"
7 #include <initializer_list>
11 #include <cmext/algorithm>
13 #include "cmsys/FStream.hxx"
14 #include "cmsys/RegularExpression.hxx"
16 #include "cmDuration.h"
17 #include "cmProcessOutput.h"
18 #include "cmStringAlgorithms.h"
19 #include "cmSystemTools.h"
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,
30 if (newOpts.empty()) {
33 if (baseOpts.empty()) {
38 std::vector<std::string> extraOpts;
39 for (auto fit = newOpts.begin(), fitEnd = newOpts.end(); fit != fitEnd;
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
48 auto oit = newOpt.begin();
51 if (isQt5OrLater && (*oit == '-')) {
54 optName.assign(oit, newOpt.end());
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;
68 extraOpts.push_back(newOpt);
72 cm::append(baseOpts, extraOpts);
75 // - Class definitions
77 unsigned int const cmQtAutoGen::ParallelMax = 64;
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;
86 cm::string_view cmQtAutoGen::GeneratorName(GenT genType)
101 cm::string_view cmQtAutoGen::GeneratorNameUpper(GenT genType)
116 std::string cmQtAutoGen::Tools(bool moc, bool uic, bool rcc)
118 std::array<cm::string_view, 3> lst;
119 decltype(lst)::size_type num = 0;
121 lst.at(num++) = "AUTOMOC";
124 lst.at(num++) = "AUTOUIC";
127 lst.at(num++) = "AUTORCC";
131 return std::string(lst[0]);
133 return cmStrCat(lst[0], " and ", lst[1]);
135 return cmStrCat(lst[0], ", ", lst[1], " and ", lst[2]);
139 return std::string();
142 std::string cmQtAutoGen::Quoted(cm::string_view text)
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" } };
149 std::string res(text);
150 for (auto const& pair : replacements) {
151 cmSystemTools::ReplaceString(res, pair.first, pair.second);
153 return cmStrCat('"', res, '"');
156 std::string cmQtAutoGen::QuotedCommand(std::vector<std::string> const& command)
159 for (std::string const& item : command) {
163 std::string const cesc = cmQtAutoGen::Quoted(item);
164 if (item.empty() || (cesc.size() > (item.size() + 2)) ||
165 (cesc.find(' ') != std::string::npos)) {
174 std::string cmQtAutoGen::FileNameWithoutLastExtension(cm::string_view filename)
176 auto slashPos = filename.rfind('/');
177 if (slashPos != cm::string_view::npos) {
178 filename.remove_prefix(slashPos + 1);
180 auto dotPos = filename.rfind('.');
181 return std::string(filename.substr(0, dotPos));
184 std::string cmQtAutoGen::ParentDir(cm::string_view filename)
186 auto slashPos = filename.rfind('/');
187 if (slashPos == cm::string_view::npos) {
188 return std::string();
190 return std::string(filename.substr(0, slashPos));
193 std::string cmQtAutoGen::SubDirPrefix(cm::string_view filename)
195 auto slashPos = filename.rfind('/');
196 if (slashPos == cm::string_view::npos) {
197 return std::string();
199 return std::string(filename.substr(0, slashPos + 1));
202 std::string cmQtAutoGen::AppendFilenameSuffix(cm::string_view filename,
203 cm::string_view suffix)
205 auto dotPos = filename.rfind('.');
206 if (dotPos == cm::string_view::npos) {
207 return cmStrCat(filename, suffix);
209 return cmStrCat(filename.substr(0, dotPos), suffix,
210 filename.substr(dotPos, filename.size() - dotPos));
213 void cmQtAutoGen::UicMergeOptions(std::vector<std::string>& baseOpts,
214 std::vector<std::string> const& newOpts,
217 static std::initializer_list<cm::string_view> const valueOpts = {
218 "tr", "translate", "postfix", "generator",
219 "include", // Since Qt 5.3
222 MergeOptions(baseOpts, newOpts, valueOpts, isQt5OrLater);
225 void cmQtAutoGen::RccMergeOptions(std::vector<std::string>& baseOpts,
226 std::vector<std::string> const& newOpts,
229 static std::initializer_list<cm::string_view> const valueOpts = {
230 "name", "root", "compress", "threshold"
232 MergeOptions(baseOpts, newOpts, valueOpts, isQt5OrLater);
235 static void RccListParseContent(std::string const& content,
236 std::vector<std::string>& files)
238 cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
239 cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
241 const char* contentChars = content.c_str();
242 while (fileMatchRegex.find(contentChars)) {
243 std::string const qrcEntry = fileMatchRegex.match(1);
244 contentChars += qrcEntry.size();
246 fileReplaceRegex.find(qrcEntry);
247 std::string const tag = fileReplaceRegex.match(1);
248 files.push_back(qrcEntry.substr(tag.size()));
253 static bool RccListParseOutput(std::string const& rccStdOut,
254 std::string const& rccStdErr,
255 std::vector<std::string>& files,
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);
266 // Parse rcc std output
268 std::istringstream ostr(rccStdOut);
270 while (std::getline(ostr, oline)) {
272 if (!oline.empty()) {
273 files.push_back(oline);
277 // Parse rcc error output
279 std::istringstream estr(rccStdErr);
281 while (std::getline(estr, eline)) {
283 if (cmHasLiteralPrefix(eline, "RCC: Error in")) {
284 static std::string const searchString = "Cannot find file '";
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');
292 pos += searchString.length();
293 std::string::size_type sz = eline.size() - pos - 1;
294 files.push_back(eline.substr(pos, sz));
302 cmQtAutoGen::RccLister::RccLister() = default;
304 cmQtAutoGen::RccLister::RccLister(std::string rccExecutable,
305 std::vector<std::string> listOptions)
306 : RccExcutable_(std::move(rccExecutable))
307 , ListOptions_(std::move(listOptions))
311 bool cmQtAutoGen::RccLister::list(std::string const& qrcFile,
312 std::vector<std::string>& files,
313 std::string& error, bool verbose) const
317 if (!cmSystemTools::FileExists(qrcFile, true)) {
319 cmStrCat("The resource file ", Quoted(qrcFile), " does not exist.");
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);
329 if (!this->RccExcutable_.empty() &&
330 cmSystemTools::FileExists(this->RccExcutable_, true) &&
331 !this->ListOptions_.empty()) {
335 std::string rccStdOut;
336 std::string rccStdErr;
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));
345 cmSystemTools::Stdout(
346 cmStrCat("Running command:\n", QuotedCommand(cmd), '\n'));
349 result = cmSystemTools::RunSingleCommand(
350 cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(),
351 cmSystemTools::OUTPUT_NONE, cmDuration::zero(), cmProcessOutput::Auto);
353 if (!result || retVal) {
355 cmStrCat("The rcc list process failed for ", Quoted(qrcFile), '\n');
356 if (!rccStdOut.empty()) {
357 error += cmStrCat(rccStdOut, '\n');
359 if (!rccStdErr.empty()) {
360 error += cmStrCat(rccStdErr, '\n');
364 if (!RccListParseOutput(rccStdOut, rccStdErr, files, error)) {
368 // We can't use rcc for the file listing.
369 // Read the qrc file content into string and parse it.
371 std::string qrcContents;
373 cmsys::ifstream ifs(qrcFile.c_str());
375 std::ostringstream osst;
377 qrcContents = osst.str();
379 error = cmStrCat("The resource file ", Quoted(qrcFile),
380 " is not readable\n");
384 // Parse string content
385 RccListParseContent(qrcContents, files);
389 // Convert relative paths to absolute paths
390 for (std::string& entry : files) {
391 entry = cmSystemTools::CollapseFullPath(entry, fileDir);