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;
79 cm::string_view
cmQtAutoGen::GeneratorName(GenT genType
)
94 cm::string_view
cmQtAutoGen::GeneratorNameUpper(GenT genType
)
109 std::string
cmQtAutoGen::Tools(bool moc
, bool uic
, bool rcc
)
111 std::array
<cm::string_view
, 3> lst
;
112 decltype(lst
)::size_type num
= 0;
114 lst
.at(num
++) = "AUTOMOC";
117 lst
.at(num
++) = "AUTOUIC";
120 lst
.at(num
++) = "AUTORCC";
124 return std::string(lst
[0]);
126 return cmStrCat(lst
[0], " and ", lst
[1]);
128 return cmStrCat(lst
[0], ", ", lst
[1], " and ", lst
[2]);
132 return std::string();
135 std::string
cmQtAutoGen::Quoted(cm::string_view text
)
137 static std::initializer_list
<std::pair
<const char*, const char*>> const
138 replacements
= { { "\\", "\\\\" }, { "\"", "\\\"" }, { "\a", "\\a" },
139 { "\b", "\\b" }, { "\f", "\\f" }, { "\n", "\\n" },
140 { "\r", "\\r" }, { "\t", "\\t" }, { "\v", "\\v" } };
142 std::string
res(text
);
143 for (auto const& pair
: replacements
) {
144 cmSystemTools::ReplaceString(res
, pair
.first
, pair
.second
);
146 return cmStrCat('"', res
, '"');
149 std::string
cmQtAutoGen::QuotedCommand(std::vector
<std::string
> const& command
)
152 for (std::string
const& item
: command
) {
156 std::string
const cesc
= cmQtAutoGen::Quoted(item
);
157 if (item
.empty() || (cesc
.size() > (item
.size() + 2)) ||
158 (cesc
.find(' ') != std::string::npos
)) {
167 std::string
cmQtAutoGen::FileNameWithoutLastExtension(cm::string_view filename
)
169 auto slashPos
= filename
.rfind('/');
170 if (slashPos
!= cm::string_view::npos
) {
171 filename
.remove_prefix(slashPos
+ 1);
173 auto dotPos
= filename
.rfind('.');
174 return std::string(filename
.substr(0, dotPos
));
177 std::string
cmQtAutoGen::ParentDir(cm::string_view filename
)
179 auto slashPos
= filename
.rfind('/');
180 if (slashPos
== cm::string_view::npos
) {
181 return std::string();
183 return std::string(filename
.substr(0, slashPos
));
186 std::string
cmQtAutoGen::SubDirPrefix(cm::string_view filename
)
188 auto slashPos
= filename
.rfind('/');
189 if (slashPos
== cm::string_view::npos
) {
190 return std::string();
192 return std::string(filename
.substr(0, slashPos
+ 1));
195 std::string
cmQtAutoGen::AppendFilenameSuffix(cm::string_view filename
,
196 cm::string_view suffix
)
198 auto dotPos
= filename
.rfind('.');
199 if (dotPos
== cm::string_view::npos
) {
200 return cmStrCat(filename
, suffix
);
202 return cmStrCat(filename
.substr(0, dotPos
), suffix
,
203 filename
.substr(dotPos
, filename
.size() - dotPos
));
206 void cmQtAutoGen::UicMergeOptions(std::vector
<std::string
>& baseOpts
,
207 std::vector
<std::string
> const& newOpts
,
210 static std::initializer_list
<cm::string_view
> const valueOpts
= {
211 "tr", "translate", "postfix", "generator",
212 "include", // Since Qt 5.3
215 MergeOptions(baseOpts
, newOpts
, valueOpts
, isQt5OrLater
);
218 void cmQtAutoGen::RccMergeOptions(std::vector
<std::string
>& baseOpts
,
219 std::vector
<std::string
> const& newOpts
,
222 static std::initializer_list
<cm::string_view
> const valueOpts
= {
223 "name", "root", "compress", "threshold"
225 MergeOptions(baseOpts
, newOpts
, valueOpts
, isQt5OrLater
);
228 static void RccListParseContent(std::string
const& content
,
229 std::vector
<std::string
>& files
)
231 cmsys::RegularExpression
fileMatchRegex("(<file[^<]+)");
232 cmsys::RegularExpression
fileReplaceRegex("(^<file[^>]*>)");
234 const char* contentChars
= content
.c_str();
235 while (fileMatchRegex
.find(contentChars
)) {
236 std::string
const qrcEntry
= fileMatchRegex
.match(1);
237 contentChars
+= qrcEntry
.size();
239 fileReplaceRegex
.find(qrcEntry
);
240 std::string
const tag
= fileReplaceRegex
.match(1);
241 files
.push_back(qrcEntry
.substr(tag
.size()));
246 static bool RccListParseOutput(std::string
const& rccStdOut
,
247 std::string
const& rccStdErr
,
248 std::vector
<std::string
>& files
,
251 // Lambda to strip CR characters
252 auto StripCR
= [](std::string
& line
) {
253 std::string::size_type cr
= line
.find('\r');
254 if (cr
!= std::string::npos
) {
255 line
= line
.substr(0, cr
);
259 // Parse rcc std output
261 std::istringstream
ostr(rccStdOut
);
263 while (std::getline(ostr
, oline
)) {
265 if (!oline
.empty()) {
266 files
.push_back(oline
);
270 // Parse rcc error output
272 std::istringstream
estr(rccStdErr
);
274 while (std::getline(estr
, eline
)) {
276 if (cmHasLiteralPrefix(eline
, "RCC: Error in")) {
277 static std::string
const searchString
= "Cannot find file '";
279 std::string::size_type pos
= eline
.find(searchString
);
280 if (pos
== std::string::npos
) {
281 error
= cmStrCat("rcc lists unparsable output:\n",
282 cmQtAutoGen::Quoted(eline
), '\n');
285 pos
+= searchString
.length();
286 std::string::size_type sz
= eline
.size() - pos
- 1;
287 files
.push_back(eline
.substr(pos
, sz
));
295 cmQtAutoGen::RccLister::RccLister() = default;
297 cmQtAutoGen::RccLister::RccLister(std::string rccExecutable
,
298 std::vector
<std::string
> listOptions
)
299 : RccExcutable_(std::move(rccExecutable
))
300 , ListOptions_(std::move(listOptions
))
304 bool cmQtAutoGen::RccLister::list(std::string
const& qrcFile
,
305 std::vector
<std::string
>& files
,
306 std::string
& error
, bool verbose
) const
310 if (!cmSystemTools::FileExists(qrcFile
, true)) {
312 cmStrCat("The resource file ", Quoted(qrcFile
), " does not exist.");
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 std::string
const fileDir
= cmSystemTools::GetFilenamePath(qrcFile
);
322 if (!this->RccExcutable_
.empty() &&
323 cmSystemTools::FileExists(this->RccExcutable_
, true) &&
324 !this->ListOptions_
.empty()) {
328 std::string rccStdOut
;
329 std::string rccStdErr
;
331 std::vector
<std::string
> cmd
;
332 cmd
.emplace_back(this->RccExcutable_
);
333 cm::append(cmd
, this->ListOptions_
);
334 cmd
.emplace_back(cmSystemTools::GetFilenameName(qrcFile
));
338 cmSystemTools::Stdout(
339 cmStrCat("Running command:\n", QuotedCommand(cmd
), '\n'));
342 result
= cmSystemTools::RunSingleCommand(
343 cmd
, &rccStdOut
, &rccStdErr
, &retVal
, fileDir
.c_str(),
344 cmSystemTools::OUTPUT_NONE
, cmDuration::zero(), cmProcessOutput::Auto
);
346 if (!result
|| retVal
) {
348 cmStrCat("The rcc list process failed for ", Quoted(qrcFile
), '\n');
349 if (!rccStdOut
.empty()) {
350 error
+= cmStrCat(rccStdOut
, '\n');
352 if (!rccStdErr
.empty()) {
353 error
+= cmStrCat(rccStdErr
, '\n');
357 if (!RccListParseOutput(rccStdOut
, rccStdErr
, files
, error
)) {
361 // We can't use rcc for the file listing.
362 // Read the qrc file content into string and parse it.
364 std::string qrcContents
;
366 cmsys::ifstream
ifs(qrcFile
.c_str());
368 std::ostringstream osst
;
370 qrcContents
= osst
.str();
372 error
= cmStrCat("The resource file ", Quoted(qrcFile
),
373 " is not readable\n");
377 // Parse string content
378 RccListParseContent(qrcContents
, files
);
382 // Convert relative paths to absolute paths
383 for (std::string
& entry
: files
) {
384 entry
= cmSystemTools::CollapseFullPath(entry
, fileDir
);