/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 | } |