Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4/*
5 * The tool generates deployment artifacts for the Qt builds such as:
6 * - CaMeL case header files named by public C++ symbols located in public module header files
7 * - Header file that contains the module version information, and named as <module>Vesion
8 * - LD version script if applicable
9 * - Aliases or copies of the header files sorted by the generic Qt-types: public/private/qpa
10 * and stored in the corresponding directories.
11 * Also the tool executes conformity checks on each header file if applicable, to make sure they
12 * follow rules that are relevant for their header type.
13 * The tool can be run in two modes: with either '-all' or '-headers' options specified. Depending
14 * on the selected mode, the tool either scans the filesystem to find header files or use the
15 * pre-defined list of header files.
16 */
17
18#include <algorithm>
19#include <iostream>
20#include <fstream>
21#include <string>
22#include <string_view>
23#include <cstring>
24#include <sstream>
25#include <filesystem>
26#include <unordered_map>
27#include <vector>
28#include <regex>
29#include <map>
30#include <set>
31#include <stdexcept>
32#include <array>
33
39
40// Enum contains the list of checks that can be executed on header files.
43 NamespaceChecks = 1, /* Checks if header file is wrapped with QT_<BEGIN|END>_NAMESPACE macros */
44 PrivateHeaderChecks = 2, /* Checks if the public header includes a private header */
45 IncludeChecks = 4, /* Checks if the real header file but not an alias is included */
46 WeMeantItChecks = 8, /* Checks if private header files contains 'We meant it' disclaimer */
48 /* Checks that lead to the fatal error of the sync process: */
51};
52
54
55static const std::regex GlobalHeaderRegex("^q(.*)global\\.h$");
56
57constexpr std::string_view ErrorMessagePreamble = "ERROR: ";
58constexpr std::string_view WarningMessagePreamble = "WARNING: ";
59
60// This comparator is used to sort include records in master header.
61// It's used to put q.*global.h file to the top of the list and sort all other files alphabetically.
62bool MasterHeaderIncludeComparator(const std::string &a, const std::string &b)
63{
64 std::smatch amatch;
65 std::smatch bmatch;
66
67 if (std::regex_match(a, amatch, GlobalHeaderRegex)) {
68 if (std::regex_match(b, bmatch, GlobalHeaderRegex)) {
69 return amatch[1].str().empty()
70 || (!bmatch[1].str().empty() && amatch[1].str() < bmatch[1].str());
71 }
72 return true;
73 } else if (std::regex_match(b, bmatch, GlobalHeaderRegex)) {
74 return false;
75 }
76
77 return a < b;
78};
79
80namespace utils {
81std::string asciiToLower(std::string s)
82{
83 std::transform(s.begin(), s.end(), s.begin(),
84 [](unsigned char c) { return (c >= 'A' && c <= 'Z') ? c | 0x20 : c; });
85 return s;
86}
87
88std::string asciiToUpper(std::string s)
89{
90 std::transform(s.begin(), s.end(), s.begin(),
91 [](unsigned char c) { return (c >= 'a' && c <= 'z') ? c & 0xdf : c; });
92 return s;
93}
94
95bool parseVersion(const std::string &version, int &major, int &minor)
96{
97 const size_t separatorPos = version.find('.');
98 if (separatorPos == std::string::npos || separatorPos == (version.size() - 1)
99 || separatorPos == 0)
100 return false;
101
102 try {
103 size_t pos = 0;
104 major = std::stoi(version.substr(0, separatorPos), &pos);
105 if (pos != separatorPos)
106 return false;
107
108 const size_t nextPart = separatorPos + 1;
109 pos = 0;
110 minor = std::stoi(version.substr(nextPart), &pos);
111 if (pos != (version.size() - nextPart))
112 return false;
113 } catch (const std::invalid_argument &) {
114 return false;
115 } catch (const std::out_of_range &) {
116 return false;
117 }
118
119 return true;
120}
121
123{
124 struct : public std::streambuf
125 {
126 int overflow(int c) override { return c; }
127 } buff;
128
129public:
131} DummyOutput;
132
134{
135 std::cerr << "Internal error. Please create bugreport at https://bugreports.qt.io "
136 "using 'Build tools: Other component.'"
137 << std::endl;
138}
139
140void printFilesystemError(const std::filesystem::filesystem_error &fserr, std::string_view errorMsg)
141{
142 std::cerr << errorMsg << ": " << fserr.path1() << ".\n"
143 << fserr.what() << "(" << fserr.code().value() << ")" << std::endl;
144}
145
146std::filesystem::path normilizedPath(const std::string &path)
147{
148 try {
149 auto result = std::filesystem::path(std::filesystem::weakly_canonical(path).generic_string());
150 return result;
151 } catch (const std::filesystem::filesystem_error &fserr) {
152 printFilesystemError(fserr, "Unable to normalize path");
153 throw;
154 }
155}
156
157bool createDirectories(const std::string &path, std::string_view errorMsg, bool *exists = nullptr)
158{
159 bool result = true;
160 try {
161 if (!std::filesystem::exists(path)) {
162 if (exists)
163 *exists = false;
164 std::filesystem::create_directories(path);
165 } else {
166 if (exists)
167 *exists = true;
168 }
169 } catch (const std::filesystem::filesystem_error &fserr) {
170 result = false;
171 std::cerr << errorMsg << ": " << path << ".\n"
172 << fserr.code().message() << "(" << fserr.code().value() << "):" << fserr.what()
173 << std::endl;
174 }
175 return result;
176}
177
178} // namespace utils
179
180using FileStamp = std::filesystem::file_time_type;
181
183{
184 template<typename T>
185 struct CommandLineOption
186 {
187 CommandLineOption(T *_value, bool _isOptional = false)
188 : value(_value), isOptional(_isOptional)
189 {
190 }
191
192 T *value;
193 bool isOptional;
194 };
195
196public:
197 CommandLineOptions(int argc, char *argv[]) : m_isValid(parseArguments(argc, argv)) { }
198
199 bool isValid() const { return m_isValid; }
200
201 const std::string &moduleName() const { return m_moduleName; }
202
203 const std::string &sourceDir() const { return m_sourceDir; }
204
205 const std::string &binaryDir() const { return m_binaryDir; }
206
207 const std::string &includeDir() const { return m_includeDir; }
208
209 const std::string &installIncludeDir() const { return m_installIncludeDir; }
210
211 const std::string &privateIncludeDir() const { return m_privateIncludeDir; }
212
213 const std::string &qpaIncludeDir() const { return m_qpaIncludeDir; }
214
215 const std::string &rhiIncludeDir() const { return m_rhiIncludeDir; }
216
217 const std::string &ssgIncludeDir() const { return m_ssgIncludeDir; }
218
219 const std::string &spiIncludeDir() const { return m_spiIncludeDir; }
220
221 const std::string &stagingDir() const { return m_stagingDir; }
222
223 const std::string &versionScriptFile() const { return m_versionScriptFile; }
224
225 const std::set<std::string> &knownModules() const { return m_knownModules; }
226
227 const std::regex &qpaHeadersRegex() const { return m_qpaHeadersRegex; }
228
229 const std::regex &rhiHeadersRegex() const { return m_rhiHeadersRegex; }
230
231 const std::regex &ssgHeadersRegex() const { return m_ssgHeadersRegex; }
232
233 const std::regex &spiHeadersRegex() const { return m_spiHeadersRegex; }
234
235 const std::regex &privateHeadersRegex() const { return m_privateHeadersRegex; }
236
237 const std::regex &publicNamespaceRegex() const { return m_publicNamespaceRegex; }
238
239 const std::set<std::string> &headers() const { return m_headers; }
240
241 const std::set<std::string> &generatedHeaders() const { return m_generatedHeaders; }
242
243 bool scanAllMode() const { return m_scanAllMode; }
244
245 bool isInternal() const { return m_isInternal; }
246
247 bool isNonQtModule() const { return m_isNonQtModule; }
248
249 bool printHelpOnly() const { return m_printHelpOnly; }
250
251 bool debug() const { return m_debug; }
252
253 bool copy() const { return m_copy; }
254
255 bool minimal() const { return m_minimal; }
256
257 bool showOnly() const { return m_showOnly; }
258
259 bool warningsAreErrors() const { return m_warningsAreErrors; }
260
261 void printHelp() const
262 {
263 std::cout << "Usage: syncqt -sourceDir <dir> -binaryDir <dir> -module <module name>"
264 " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir> -rhiIncludeDir <dir> -ssgIncludeDir <dir>"
265 " -spiIncludeDir <dir>"
266 " -stagingDir <dir> <-headers <header list>|-all> [-debug]"
267 " [-versionScript <path>] [-qpaHeadersFilter <regex>] [-rhiHeadersFilter <regex>]"
268 " [-spiHeadersFilter <regex>]"
269 " [-knownModules <module1> <module2>... <moduleN>]"
270 " [-nonQt] [-internal] [-copy]\n"
271 ""
272 "Mandatory arguments:\n"
273 " -module Module name.\n"
274 " -headers List of header files.\n"
275 " -all In 'all' mode syncqt scans source\n"
276 " directory for public qt headers and\n"
277 " artifacts not considering CMake source\n"
278 " tree. The main use cases are the \n"
279 " generating of documentation and creating\n"
280 " API review changes.\n"
281 " -sourceDir Module source directory.\n"
282 " -binaryDir Module build directory.\n"
283 " -includeDir Module include directory where the\n"
284 " generated header files will be located.\n"
285 " -privateIncludeDir Module include directory for the\n"
286 " generated private header files.\n"
287 " -qpaIncludeDir Module include directory for the \n"
288 " generated QPA header files.\n"
289 " -rhiIncludeDir Module include directory for the \n"
290 " generated RHI header files.\n"
291 " -ssgIncludeDir Module include directory for the \n"
292 " generated SSG header files.\n"
293 " -spiIncludeDir Module include directory for the \n"
294 " generated SPI (semi-private or\n"
295 " service-provider interface) header files.\n"
296 " -stagingDir Temporary staging directory to collect\n"
297 " artifacts that need to be installed.\n"
298 " -knownModules list of known modules. syncqt uses the\n"
299 " list to check the #include macros\n"
300 " consistency.\n"
301 "Optional arguments:\n"
302 " -internal Indicates that the module is internal.\n"
303 " -nonQt Indicates that the module is not a Qt\n"
304 " module.\n"
305 " -privateHeadersFilter Regex that filters private header files\n"
306 " from the list of 'headers'.\n"
307 " -qpaHeadersFilter Regex that filters qpa header files from.\n"
308 " the list of 'headers'.\n"
309 " -rhiHeadersFilter Regex that filters rhi header files from.\n"
310 " the list of 'headers'.\n"
311 " -ssgHeadersFilter Regex that filters ssg files from.\n"
312 " the list of 'headers'.\n"
313 " -spiHeadersFilter Regex that filters SPI (semi-private or\n"
314 " service-provider interface) header files\n"
315 " from the list of 'headers'.\n"
316 " -publicNamespaceFilter Symbols that are in the specified\n"
317 " namespace.\n"
318 " are treated as public symbols.\n"
319 " -versionScript Generate linker version script by\n"
320 " provided path.\n"
321 " -debug Enable debug output.\n"
322 " -copy Copy header files instead of creating\n"
323 " aliases.\n"
324 " -minimal Do not create CaMeL case headers for the\n"
325 " public C++ symbols.\n"
326 " -showonly Show actions, but not perform them.\n"
327 " -warningsAreErrors Treat all warnings as errors.\n"
328 " -help Print this help.\n";
329 }
330
331private:
332 template<typename T>
333 [[nodiscard]] bool checkRequiredArguments(const std::unordered_map<std::string, T> &arguments)
334 {
335 bool ret = true;
336 for (const auto &argument : arguments) {
337 if (!argument.second.isOptional
338 && (!argument.second.value || argument.second.value->size()) == 0) {
339 std::cerr << "Missing argument: " << argument.first << std::endl;
340 ret = false;
341 }
342 }
343 return ret;
344 }
345
346 [[nodiscard]] bool parseArguments(int argc, char *argv[])
347 {
348 std::string qpaHeadersFilter;
349 std::string rhiHeadersFilter;
350 std::string ssgHeadersFilter;
351 std::string spiHeadersFilter;
352 std::string privateHeadersFilter;
353 std::string publicNamespaceFilter;
354 const std::unordered_map<std::string, CommandLineOption<std::string>> stringArgumentMap = {
355 { "-module", { &m_moduleName } },
356 { "-sourceDir", { &m_sourceDir } },
357 { "-binaryDir", { &m_binaryDir } },
358 { "-installIncludeDir", { &m_installIncludeDir, true } },
359 { "-privateHeadersFilter", { &privateHeadersFilter, true } },
360 { "-qpaHeadersFilter", { &qpaHeadersFilter, true } },
361 { "-rhiHeadersFilter", { &rhiHeadersFilter, true } },
362 { "-ssgHeadersFilter", { &ssgHeadersFilter, true } },
363 { "-spiHeadersFilter", { &spiHeadersFilter, true } },
364 { "-includeDir", { &m_includeDir } },
365 { "-privateIncludeDir", { &m_privateIncludeDir } },
366 { "-qpaIncludeDir", { &m_qpaIncludeDir } },
367 { "-rhiIncludeDir", { &m_rhiIncludeDir } },
368 { "-ssgIncludeDir", { &m_ssgIncludeDir } },
369 { "-spiIncludeDir", { &m_spiIncludeDir } },
370 { "-stagingDir", { &m_stagingDir, true } },
371 { "-versionScript", { &m_versionScriptFile, true } },
372 { "-publicNamespaceFilter", { &publicNamespaceFilter, true } },
373 };
374
375 const std::unordered_map<std::string, CommandLineOption<std::set<std::string>>>
376 listArgumentMap = {
377 { "-headers", { &m_headers, true } },
378 { "-generatedHeaders", { &m_generatedHeaders, true } },
379 { "-knownModules", { &m_knownModules, true } },
380 };
381
382 const std::unordered_map<std::string, CommandLineOption<bool>> boolArgumentMap = {
383 { "-nonQt", { &m_isNonQtModule, true } }, { "-debug", { &m_debug, true } },
384 { "-help", { &m_printHelpOnly, true } },
385 { "-internal", { &m_isInternal, true } }, { "-all", { &m_scanAllMode, true } },
386 { "-copy", { &m_copy, true } }, { "-minimal", { &m_minimal, true } },
387 { "-showonly", { &m_showOnly, true } }, { "-showOnly", { &m_showOnly, true } },
388 { "-warningsAreErrors", { &m_warningsAreErrors, true } }
389 };
390
391 std::string *currentValue = nullptr;
392 std::set<std::string> *currentListValue = nullptr;
393
394 auto parseArgument = [&](const std::string &arg) -> bool {
395 if (arg[0] == '-') {
396 currentValue = nullptr;
397 currentListValue = nullptr;
398 {
399 auto it = stringArgumentMap.find(arg);
400 if (it != stringArgumentMap.end()) {
401 if (it->second.value == nullptr) {
403 return false;
404 }
405 currentValue = it->second.value;
406 return true;
407 }
408 }
409
410 {
411 auto it = boolArgumentMap.find(arg);
412 if (it != boolArgumentMap.end()) {
413 if (it->second.value == nullptr) {
415 return false;
416 }
417 *(it->second.value) = true;
418 return true;
419 }
420 }
421
422 {
423 auto it = listArgumentMap.find(arg);
424 if (it != listArgumentMap.end()) {
425 if (it->second.value == nullptr) {
427 return false;
428 }
429 currentListValue = it->second.value;
430 currentListValue->insert(""); // Indicate that argument is provided
431 return true;
432 }
433 }
434
435 std::cerr << "Unknown argument: " << arg << std::endl;
436 return false;
437 }
438
439 if (currentValue != nullptr) {
440 *currentValue = arg;
441 currentValue = nullptr;
442 } else if (currentListValue != nullptr) {
443 currentListValue->insert(arg);
444 } else {
445 std::cerr << "Unknown argument: " << arg << std::endl;
446 return false;
447 }
448 return true;
449 };
450
451 for (int i = 1; i < argc; ++i) {
452 std::string arg(argv[i]);
453 if (arg.empty())
454 continue;
455
456 if (arg[0] == '@') {
457 std::ifstream ifs(arg.substr(1), std::ifstream::in);
458 if (!ifs.is_open()) {
459 std::cerr << "Unable to open rsp file: " << arg[0] << std::endl;
460 return false;
461 }
462 std::string argFromFile;
463 while (std::getline(ifs, argFromFile)) {
464 if (argFromFile.empty())
465 continue;
466 if (!parseArgument(argFromFile))
467 return false;
468 }
469 ifs.close();
470 continue;
471 }
472
473 if (!parseArgument(arg))
474 return false;
475 }
476
477 if (m_printHelpOnly)
478 return true;
479
480 if (!qpaHeadersFilter.empty())
481 m_qpaHeadersRegex = std::regex(qpaHeadersFilter);
482
483 if (!rhiHeadersFilter.empty())
484 m_rhiHeadersRegex = std::regex(rhiHeadersFilter);
485
486 if (!ssgHeadersFilter.empty())
487 m_ssgHeadersRegex = std::regex(ssgHeadersFilter);
488
489 if (!spiHeadersFilter.empty())
490 m_spiHeadersRegex = std::regex(spiHeadersFilter);
491
492 if (!privateHeadersFilter.empty())
493 m_privateHeadersRegex = std::regex(privateHeadersFilter);
494
495 if (!publicNamespaceFilter.empty())
496 m_publicNamespaceRegex = std::regex(publicNamespaceFilter);
497
498 if (m_headers.empty() && !m_scanAllMode) {
499 std::cerr << "You need to specify either -headers or -all option." << std::endl;
500 return false;
501 }
502
503 if (!m_headers.empty() && m_scanAllMode) {
504 std::cerr << "Both -headers and -all are specified. Need to choose only one"
505 "operational mode." << std::endl;
506 return false;
507 }
508
509 for (const auto &argument : listArgumentMap)
510 argument.second.value->erase("");
511
512 bool ret = true;
513 ret &= checkRequiredArguments(stringArgumentMap);
514 ret &= checkRequiredArguments(listArgumentMap);
515
516 normilizePaths();
517
518 return ret;
519 }
520
521 // Convert all paths from command line to a generic one.
522 void normilizePaths()
523 {
524 const std::array paths = {
525 &m_sourceDir, &m_binaryDir, &m_includeDir,
526 &m_installIncludeDir, &m_privateIncludeDir, &m_qpaIncludeDir,
527 &m_rhiIncludeDir, &m_stagingDir, &m_versionScriptFile,
528 &m_spiIncludeDir,
529 };
530 for (auto path : paths) {
531 if (!path->empty())
532 *path = utils::normilizedPath(*path).generic_string();
533 }
534 }
535
536 std::string m_moduleName;
537 std::string m_sourceDir;
538 std::string m_binaryDir;
539 std::string m_includeDir;
540 std::string m_installIncludeDir;
541 std::string m_privateIncludeDir;
542 std::string m_qpaIncludeDir;
543 std::string m_rhiIncludeDir;
544 std::string m_ssgIncludeDir;
545 std::string m_spiIncludeDir;
546 std::string m_stagingDir;
547 std::string m_versionScriptFile;
548 std::set<std::string> m_knownModules;
549 std::set<std::string> m_headers;
550 std::set<std::string> m_generatedHeaders;
551 bool m_scanAllMode = false;
552 bool m_copy = false;
553 bool m_isNonQtModule = false;
554 bool m_isInternal = false;
555 bool m_printHelpOnly = false;
556 bool m_debug = false;
557 bool m_minimal = false;
558 bool m_showOnly = false;
559 bool m_warningsAreErrors = false;
560 std::regex m_qpaHeadersRegex;
561 std::regex m_rhiHeadersRegex;
562 std::regex m_ssgHeadersRegex;
563 std::regex m_spiHeadersRegex;
564 std::regex m_privateHeadersRegex;
565 std::regex m_publicNamespaceRegex;
566
567 bool m_isValid;
568};
569
571{
572 class SymbolDescriptor
573 {
574 public:
575 // Where the symbol comes from
576 enum SourceType {
577 Pragma = 0, // pragma qt_class is mentioned a header file
578 Declaration, // The symbol declaration inside a header file
579 MaxSourceType
580 };
581
582 void update(const std::string &file, SourceType type)
583 {
584 if (type < m_type) {
585 m_file = file;
586 m_type = type;
587 }
588 }
589
590 // The file that contains a symbol.
591 const std::string &file() const { return m_file; }
592
593 private:
594 SourceType m_type = MaxSourceType;
595 std::string m_file;
596 };
598
599 struct ParsingResult
600 {
601 std::vector<std::string> versionScriptContent;
602 std::string requireConfig;
603 bool masterInclude = true;
604 };
605
606 CommandLineOptions *m_commandLineArgs = nullptr;
607
608 std::map<std::string /* header file name */, std::string /* header feature guard name */,
609 decltype(MasterHeaderIncludeComparator) *>
610 m_masterHeaderContents;
611
612 std::unordered_map<std::string /* the deprecated header name*/,
613 std::string /* the replacement */>
614 m_deprecatedHeaders;
615 std::vector<std::string> m_versionScriptContents;
616 std::set<std::string> m_producedHeaders;
617 std::vector<std::string> m_headerCheckExceptions;
618 SymbolContainer m_symbols;
619 std::ostream &scannerDebug() const
620 {
621 if (m_commandLineArgs->debug())
622 return std::cout;
623 return utils::DummyOutput;
624 }
625
626 enum { Active, Stopped, IgnoreNext, Ignore } m_versionScriptGeneratorState = Active;
627
628 std::filesystem::path m_outputRootName;
629 std::filesystem::path m_currentFile;
630 std::string m_currentFilename;
631 std::string m_currentFileString;
632 size_t m_currentFileLineNumber = 0;
633 bool m_currentFileInSourceDir = false;
634
635 enum FileType {
636 PublicHeader = 0,
637 PrivateHeader = 1,
638 QpaHeader = 2,
639 ExportHeader = 4,
640 RhiHeader = 8,
641 SsgHeader = 16,
642 SpiHeader = 32
643 };
644 unsigned int m_currentFileType = PublicHeader;
645
646 int m_criticalChecks = CriticalChecks;
647 std::string_view m_warningMessagePreamble;
648
649public:
658
659 // The function converts the relative path to a header files to the absolute. It also makes the
660 // path canonical(removes '..' and '.' parts of the path). The source directory passed in
661 // '-sourceDir' command line argument is used as base path for relative paths to create the
662 // absolute path.
663 [[nodiscard]] std::filesystem::path makeHeaderAbsolute(const std::string &filename) const;
664
666 {
667 if (m_commandLineArgs->warningsAreErrors()) {
668 m_criticalChecks = AllChecks;
669 m_warningMessagePreamble = ErrorMessagePreamble;
670 }
671
672 m_versionScriptGeneratorState =
673 m_commandLineArgs->versionScriptFile().empty() ? Stopped : Active;
674 auto error = NoError;
675
676 // In the scan all mode we ingore the list of header files that is specified in the
677 // '-headers' argument, and collect header files from the source directory tree.
678 if (m_commandLineArgs->scanAllMode()) {
679 for (auto const &entry :
680 std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) {
681
682 const bool isRegularFile = entry.is_regular_file();
683 const bool isHeaderFlag = isHeader(entry);
684 const bool isDocFileHeuristicFlag =
685 isDocFileHeuristic(entry.path().generic_string());
686 const bool shouldProcessHeader =
687 isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag;
688 const std::string filePath = entry.path().generic_string();
689
690 if (shouldProcessHeader) {
691 scannerDebug() << "Processing header: " << filePath << std::endl;
692 if (!processHeader(makeHeaderAbsolute(filePath)))
693 error = SyncFailed;
694 } else {
695 scannerDebug()
696 << "Skipping processing header: " << filePath
697 << " isRegularFile: " << isRegularFile
698 << " isHeaderFlag: " << isHeaderFlag
699 << " isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
700 << std::endl;
701 }
702 }
703 } else {
704 // Since the list of header file is quite big syncqt supports response files to avoid
705 // the issues with long command lines.
706 std::set<std::string> rspHeaders;
707 const auto &headers = m_commandLineArgs->headers();
708 for (auto it = headers.begin(); it != headers.end(); ++it) {
709 const auto &header = *it;
710 scannerDebug() << "Processing header: " << header << std::endl;
712 error = SyncFailed;
713 }
714 }
715 for (const auto &header : rspHeaders) {
716 scannerDebug() << "Processing header: " << header << std::endl;
717 if (!processHeader(makeHeaderAbsolute(header)))
718 error = SyncFailed;
719 }
720 }
721
722 // No further processing in minimal mode.
723 if (m_commandLineArgs->minimal())
724 return error;
725
726 // Generate aliases for all unique symbols collected during the header files parsing.
727 for (auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
728 const std::string &filename = it->second.file();
729 if (!filename.empty()) {
731 m_commandLineArgs->includeDir() + '/' + it->first, filename)) {
732 m_producedHeaders.insert(it->first);
733 } else {
734 error = SyncFailed;
735 }
736 }
737 }
738
739 // Generate the header file containing version information.
740 if (!m_commandLineArgs->isNonQtModule()) {
741 std::string moduleNameLower = utils::asciiToLower(m_commandLineArgs->moduleName());
742 std::string versionHeaderFilename(moduleNameLower + "version.h");
743 std::string versionHeaderCamel(m_commandLineArgs->moduleName() + "Version");
744 std::string versionFile = m_commandLineArgs->includeDir() + '/' + versionHeaderFilename;
745
746 std::error_code ec;
747 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
748 if (ec)
749 originalStamp = FileStamp::clock::now();
750
751 if (generateVersionHeader(versionFile)) {
752 if (!generateAliasedHeaderFileIfTimestampChanged(
753 m_commandLineArgs->includeDir() + '/' + versionHeaderCamel,
754 versionFile, originalStamp)) {
755 error = SyncFailed;
756 }
757 m_masterHeaderContents[versionHeaderFilename] = {};
758 m_producedHeaders.insert(versionHeaderFilename);
759 m_producedHeaders.insert(versionHeaderCamel);
760 } else {
761 error = SyncFailed;
762 }
763 }
764
765 if (!m_commandLineArgs->scanAllMode()) {
766 if (!m_commandLineArgs->isNonQtModule()) {
768 error = SyncFailed;
769
771 error = SyncFailed;
772 }
773
774 if (!m_commandLineArgs->versionScriptFile().empty()) {
776 error = SyncFailed;
777 }
778 }
779
780 if (!m_commandLineArgs->isNonQtModule()) {
782 error = SyncFailed;
783 }
784
785 if (!m_commandLineArgs->scanAllMode() && !m_commandLineArgs->stagingDir().empty()) {
786 // Copy the generated files to a spearate staging directory to make the installation
787 // process eaiser.
789 error = SyncFailed;
790 }
791 return error;
792 }
793
794 // The function copies files, that were generated while the sync procedure to a staging
795 // directory. This is necessary to simplify the installation of the generated files.
796 [[nodiscard]] bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory,
797 bool skipCleanup = false)
798 {
799 bool result = true;
800 bool outDirExists = false;
801 if (!utils::createDirectories(outputDirectory, "Unable to create staging directory",
802 &outDirExists))
803 return false;
804
805 if (outDirExists && !skipCleanup) {
806 try {
807 for (const auto &entry :
808 std::filesystem::recursive_directory_iterator(outputDirectory)) {
809 if (m_producedHeaders.find(entry.path().filename().generic_string())
810 == m_producedHeaders.end()) {
811 // Check if header file came from another module as result of the
812 // cross-module deprecation before removing it.
813 std::string firstLine;
814 {
815 std::ifstream input(entry.path(), std::ifstream::in);
816 if (input.is_open()) {
817 std::getline(input, firstLine);
818 input.close();
819 }
820 }
821 if (firstLine.find("#ifndef DEPRECATED_HEADER_"
822 + m_commandLineArgs->moduleName())
823 == 0
824 || firstLine.find("#ifndef DEPRECATED_HEADER_") != 0)
825 std::filesystem::remove(entry.path());
826 }
827 }
828 } catch (const std::filesystem::filesystem_error &fserr) {
829 utils::printFilesystemError(fserr, "Unable to clean the staging directory");
830 return false;
831 }
832 }
833
834 for (const auto &header : m_producedHeaders) {
835 std::filesystem::path src(m_commandLineArgs->includeDir() + '/' + header);
836 std::filesystem::path dst(outputDirectory + '/' + header);
837 if (!m_commandLineArgs->showOnly())
838 result &= updateOrCopy(src, dst);
839 }
840 return result;
841 }
842
843 void resetCurrentFileInfoData(const std::filesystem::path &headerFile)
844 {
845 // This regex filters the generated '*exports.h' and '*exports_p.h' header files.
846 static const std::regex ExportsHeaderRegex("^q(.*)exports(_p)?\\.h$");
847
848 m_currentFile = headerFile;
849 m_currentFileLineNumber = 0;
850 m_currentFilename = m_currentFile.filename().generic_string();
851 m_currentFileType = PublicHeader;
852 m_currentFileString = m_currentFile.generic_string();
853 m_currentFileInSourceDir = m_currentFileString.find(m_commandLineArgs->sourceDir()) == 0;
854
855 if (isHeaderPrivate(m_currentFilename))
856 m_currentFileType = PrivateHeader;
857
858 if (isHeaderQpa(m_currentFilename))
859 m_currentFileType = QpaHeader | PrivateHeader;
860
861 if (isHeaderRhi(m_currentFilename))
862 m_currentFileType = RhiHeader | PrivateHeader;
863
864 if (isHeaderSsg(m_currentFilename))
865 m_currentFileType = SsgHeader | PrivateHeader;
866
867 if (isHeaderSpi(m_currentFilename))
868 m_currentFileType = SpiHeader | PrivateHeader;
869
870 if (std::regex_match(m_currentFilename, ExportsHeaderRegex))
871 m_currentFileType |= ExportHeader;
872 }
873
874 [[nodiscard]] bool processHeader(const std::filesystem::path &headerFile)
875 {
876 // This regex filters any paths that contain the '3rdparty' directory.
877 static const std::regex ThirdPartyFolderRegex("(^|.+/)3rdparty/.+");
878
879 // This regex filters '-config.h' and '-config_p.h' header files.
880 static const std::regex ConfigHeaderRegex("^(q|.+-)config(_p)?\\.h");
881
883 // We assume that header files ouside of the module source or build directories do not
884 // belong to the module. Skip any processing.
885 if (!m_currentFileInSourceDir
886 && m_currentFileString.find(m_commandLineArgs->binaryDir()) != 0) {
887 scannerDebug() << "Header file: " << headerFile
888 << " is outside the sync directories. Skipping." << std::endl;
889 m_headerCheckExceptions.push_back(m_currentFileString);
890 return true;
891 }
892
893 // Check if a directory is passed as argument. That shouldn't happen, print error and exit.
894 if (m_currentFilename.empty()) {
895 std::cerr << "Header file name of " << m_currentFileString << "is empty" << std::endl;
896 return false;
897 }
898
899 std::error_code ec;
900 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
901 if (ec)
902 originalStamp = FileStamp::clock::now();
903 ec.clear();
904
905 bool isPrivate = m_currentFileType & PrivateHeader;
906 bool isQpa = m_currentFileType & QpaHeader;
907 bool isRhi = m_currentFileType & RhiHeader;
908 bool isSsg = m_currentFileType & SsgHeader;
909 bool isSpi = m_currentFileType & SpiHeader;
910 bool isExport = m_currentFileType & ExportHeader;
911 scannerDebug()
912 << "processHeader:start: " << headerFile
913 << " m_currentFilename: " << m_currentFilename
914 << " isPrivate: " << isPrivate
915 << " isQpa: " << isQpa
916 << " isRhi: " << isRhi
917 << " isSsg: " << isSsg
918 << " isSpi: " << isSpi
919 << " isExport: " << isExport
920 << std::endl;
921
922 // Chose the directory where to generate the header aliases or to copy header file if
923 // the '-copy' argument is passed.
924 std::string outputDir = m_commandLineArgs->includeDir();
925 if (isQpa)
926 outputDir = m_commandLineArgs->qpaIncludeDir();
927 else if (isRhi)
928 outputDir = m_commandLineArgs->rhiIncludeDir();
929 else if (isSsg)
930 outputDir = m_commandLineArgs->ssgIncludeDir();
931 else if (isSpi)
932 outputDir = m_commandLineArgs->spiIncludeDir();
933 else if (isPrivate)
934 outputDir = m_commandLineArgs->privateIncludeDir();
935
936 if (!utils::createDirectories(outputDir, "Unable to create output directory"))
937 return false;
938
939 bool headerFileExists = std::filesystem::exists(headerFile);
940
941 std::string aliasedFilepath = headerFile.generic_string();
942
943 std::string aliasPath = outputDir + '/' + m_currentFilename;
944
945 // If the '-copy' argument is passed, we copy the original file to a corresponding output
946 // directory otherwise we only create a header file alias that contains relative path to
947 // the original header file in the module source or build tree.
948 if (m_commandLineArgs->copy() && headerFileExists) {
949 if (!updateOrCopy(headerFile, aliasPath))
950 return false;
951 } else {
952 if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
953 originalStamp))
954 return false;
955 }
956
957 // No further processing in minimal mode.
958 if (m_commandLineArgs->minimal())
959 return true;
960
961 // Stop processing if header files doesn't exist. This happens at configure time, since
962 // either header files are generated later than syncqt is running or header files only
963 // generated at build time. These files will be processed at build time, if CMake files
964 // contain the correct dependencies between the missing header files and the module
965 // 'sync_headers' targets.
966 if (!headerFileExists) {
967 scannerDebug() << "Header file: " << headerFile
968 << " doesn't exist, but is added to syncqt scanning. Skipping.";
969 return true;
970 }
971
972 bool isGenerated = isHeaderGenerated(m_currentFileString);
973
974 // Make sure that we detect the '3rdparty' directory inside the source directory only,
975 // since full path to the Qt sources might contain '/3rdparty/' too.
976 bool is3rdParty = std::regex_match(
977 std::filesystem::relative(headerFile, m_commandLineArgs->sourceDir())
978 .generic_string(),
979 ThirdPartyFolderRegex);
980
981 // No processing of generated Qt config header files.
982 if (!std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
983 unsigned int skipChecks = m_commandLineArgs->scanAllMode() ? AllChecks : NoChecks;
984
985 // Collect checks that should skipped for the header file.
986 if (m_commandLineArgs->isNonQtModule() || is3rdParty || isQpa || isRhi || isSsg
987 || isSpi || !m_currentFileInSourceDir || isGenerated) {
988 skipChecks = AllChecks;
989 } else {
990 if (std::regex_match(m_currentFilename, GlobalHeaderRegex) || isExport)
991 skipChecks |= NamespaceChecks;
992
993 if (isHeaderPCH(m_currentFilename))
994 skipChecks |= WeMeantItChecks;
995
996 if (isPrivate) {
997 skipChecks |= NamespaceChecks;
998 skipChecks |= PrivateHeaderChecks;
999 skipChecks |= IncludeChecks;
1000 } else {
1001 skipChecks |= WeMeantItChecks;
1002 }
1003 }
1004
1005 ParsingResult parsingResult;
1006 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
1007 && !isQpa && !isRhi && !isSsg && !isSpi && !isPrivate && !isGenerated;
1008 if (!parseHeader(headerFile, parsingResult, skipChecks)) {
1009 scannerDebug() << "parseHeader failed: " << headerFile << std::endl;
1010 return false;
1011 }
1012
1013 // Record the private header file inside the version script content.
1014 if (isPrivate && !m_commandLineArgs->versionScriptFile().empty()
1015 && !parsingResult.versionScriptContent.empty()) {
1016 m_versionScriptContents.insert(m_versionScriptContents.end(),
1017 parsingResult.versionScriptContent.begin(),
1018 parsingResult.versionScriptContent.end());
1019 }
1020
1021 // Add the '#if QT_CONFIG(<feature>)' check for header files that supposed to be
1022 // included into the module master header only if corresponding feature is enabled.
1023 bool willBeInModuleMasterHeader = false;
1024 if (!isQpa && !isRhi && !isSsg && !isSpi && !isPrivate) {
1025 if (m_currentFilename.find('_') == std::string::npos
1026 && parsingResult.masterInclude) {
1027 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
1028 willBeInModuleMasterHeader = true;
1029 }
1030 }
1031
1032 scannerDebug()
1033 << "processHeader:end: " << headerFile
1034 << " is3rdParty: " << is3rdParty
1035 << " isGenerated: " << isGenerated
1036 << " m_currentFileInSourceDir: " << m_currentFileInSourceDir
1037 << " willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
1038 << std::endl;
1039 } else if (m_currentFilename == "qconfig.h") {
1040 // Hardcode generating of QtConfig alias
1041 updateSymbolDescriptor("QtConfig", "qconfig.h", SyncScanner::SymbolDescriptor::Pragma);
1042 }
1043
1044 return true;
1045 }
1046
1047 void parseVersionScriptContent(const std::string buffer, ParsingResult &result)
1048 {
1049 // This regex looks for the symbols that needs to be placed into linker version script.
1050 static const std::regex VersionScriptSymbolRegex(
1051 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1052
1053 // This regex looks for the namespaces that needs to be placed into linker version script.
1054 static const std::regex VersionScriptNamespaceRegex(
1055 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1056
1057 // This regex filters the tailing colon from the symbol name.
1058 static const std::regex TrailingColonRegex("([\\w]+):$");
1059
1060 switch (m_versionScriptGeneratorState) {
1061 case Ignore:
1062 scannerDebug() << "line ignored: " << buffer << std::endl;
1063 m_versionScriptGeneratorState = Active;
1064 return;
1065 case Stopped:
1066 return;
1067 case IgnoreNext:
1068 m_versionScriptGeneratorState = Ignore;
1069 break;
1070 case Active:
1071 break;
1072 }
1073
1074 if (buffer.empty())
1075 return;
1076
1077 std::smatch match;
1078 std::string symbol;
1079 if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
1080 symbol = match[1].str();
1081 else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex))
1082 symbol = match[1].str();
1083
1084 if (std::regex_match(symbol, match, TrailingColonRegex))
1085 symbol = match[1].str();
1086
1087 // checkLineForSymbols(buffer, symbol);
1088 if (!symbol.empty() && symbol[symbol.size() - 1] != ';') {
1089 std::string relPath = m_currentFileInSourceDir
1090 ? std::filesystem::relative(m_currentFile, m_commandLineArgs->sourceDir())
1091 .string()
1092 : std::filesystem::relative(m_currentFile, m_commandLineArgs->binaryDir())
1093 .string();
1094
1095 std::string versionStringRecord = " *";
1096 size_t startPos = 0;
1097 size_t endPos = 0;
1098 while (endPos != std::string::npos) {
1099 endPos = symbol.find("::", startPos);
1100 size_t length = endPos != std::string::npos ? (endPos - startPos)
1101 : (symbol.size() - startPos);
1102 if (length > 0) {
1103 std::string symbolPart = symbol.substr(startPos, length);
1104 versionStringRecord += std::to_string(symbolPart.size());
1105 versionStringRecord += symbolPart;
1106 }
1107 startPos = endPos + 2;
1108 }
1109 versionStringRecord += "*;";
1110 if (versionStringRecord.size() < LinkerScriptCommentAlignment)
1111 versionStringRecord +=
1112 std::string(LinkerScriptCommentAlignment - versionStringRecord.size(), ' ');
1113 versionStringRecord += " # ";
1114 versionStringRecord += relPath;
1115 versionStringRecord += ":";
1116 versionStringRecord += std::to_string(m_currentFileLineNumber);
1117 versionStringRecord += "\n";
1118 result.versionScriptContent.push_back(versionStringRecord);
1119 }
1120 }
1121
1122 // The function parses 'headerFile' and collect artifacts that are used at generating step.
1123 // 'timeStamp' is saved in internal structures to compare it when generating files.
1124 // 'result' the function output value that stores the result of parsing.
1125 // 'skipChecks' checks that are not applicable for the header file.
1126 [[nodiscard]] bool parseHeader(const std::filesystem::path &headerFile,
1127 ParsingResult &result,
1128 unsigned int skipChecks)
1129 {
1130 if (m_commandLineArgs->showOnly())
1131 std::cout << headerFile << " [" << m_commandLineArgs->moduleName() << "]" << std::endl;
1132 // This regex checks if line contains a macro.
1133 static const std::regex MacroRegex("^\\s*#.*");
1134
1135 // The regex's bellow check line for known pragmas:
1136 //
1137 // - 'once' is not allowed in installed headers, so error out.
1138 //
1139 // - 'qt_sync_skip_header_check' avoid any header checks.
1140 //
1141 // - 'qt_sync_stop_processing' stops the header proccesing from a moment when pragma is
1142 // found. Important note: All the parsing artifacts were found before this point are
1143 // stored for further processing.
1144 //
1145 // - 'qt_sync_suspend_processing' pauses processing and skip lines inside a header until
1146 // 'qt_sync_resume_processing' is found. 'qt_sync_stop_processing' stops processing if
1147 // it's found before the 'qt_sync_resume_processing'.
1148 //
1149 // - 'qt_sync_resume_processing' resumes processing after 'qt_sync_suspend_processing'.
1150 //
1151 // - 'qt_class(<symbol>)' manually declares the 'symbol' that should be used to generate
1152 // the CaMeL case header alias.
1153 //
1154 // - 'qt_deprecates([module/]<deprecated header file>[,<major.minor>])' indicates that
1155 // this header file replaces the 'deprecated header file'. syncqt will create the
1156 // deprecated header file' with the special deprecation content. Pragma optionally
1157 // accepts the Qt version where file should be removed. If the current Qt version is
1158 // higher than the deprecation version, syncqt displays deprecation warning and skips
1159 // generating the deprecated header. If the module is specified and is different from
1160 // the one this header file belongs to, syncqt attempts to generate header files
1161 // for the specified module. Cross-module deprecation only works within the same repo.
1162 // See the 'generateDeprecatedHeaders' function for details.
1163 //
1164 // - 'qt_no_master_include' indicates that syncqt should avoid including this header
1165 // files into the module master header file.
1166 static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)");
1167 static const std::regex SkipHeaderCheckRegex("^#\\s*pragma qt_sync_skip_header_check$");
1168 static const std::regex StopProcessingRegex("^#\\s*pragma qt_sync_stop_processing$");
1169 static const std::regex SuspendProcessingRegex("^#\\s*pragma qt_sync_suspend_processing$");
1170 static const std::regex ResumeProcessingRegex("^#\\s*pragma qt_sync_resume_processing$");
1171 static const std::regex ExplixitClassPragmaRegex("^#\\s*pragma qt_class\\‍(([^\\‍)]+)\\‍)$");
1172 static const std::regex DeprecatesPragmaRegex("^#\\s*pragma qt_deprecates\\‍(([^\\‍)]+)\\‍)$");
1173 static const std::regex NoMasterIncludePragmaRegex("^#\\s*pragma qt_no_master_include$");
1174
1175 // This regex checks if header contains 'We mean it' disclaimer. All private headers should
1176 // contain them.
1177 static const std::string_view WeMeantItString("We mean it.");
1178
1179 // The regex's check if the content of header files is wrapped with the Qt namespace macros.
1180 static const std::regex BeginNamespaceRegex("^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1181 static const std::regex EndNamespaceRegex("^QT_END_NAMESPACE(_[A-Z_]+)?$");
1182
1183 // This regex checks if line contains the include macro of the following formats:
1184 // - #include <file>
1185 // - #include "file"
1186 // - # include <file>
1187 static const std::regex IncludeRegex("^#\\s*include\\s*[<\"](.+)[>\"]");
1188
1189 // This regex checks if line contains namespace definition.
1190 static const std::regex NamespaceRegex("\\s*namespace ([^ ]*)\\s+");
1191
1192 // This regex checks if line contains the Qt iterator declaration, that need to have
1193 // CaMel case header alias.
1194 static const std::regex DeclareIteratorRegex("^ *Q_DECLARE_\\w*ITERATOR\\‍((\\w+)\\‍);?$");
1195
1196 // This regex checks if header file contains the QT_REQUIRE_CONFIG call.
1197 // The macro argument is used to wrap an include of the header file inside the module master
1198 // header file with the '#if QT_CONFIG(<feature>)' guard.
1199 static const std::regex RequireConfigRegex("^ *QT_REQUIRE_CONFIG\\‍((\\w+)\\‍);?$");
1200
1201 // This regex looks for the ELFVERSION tag this is control key-word for the version script
1202 // content processing.
1203 // ELFVERSION tag accepts the following values:
1204 // - stop - stops the symbols lookup for a version script starting from this line.
1205 // - ignore-next - ignores the line followed by the current one.
1206 // - ignore - ignores the current line.
1207 static const std::regex ElfVersionTagRegex(".*ELFVERSION:(stop|ignore-next|ignore).*");
1208
1209 std::ifstream input(headerFile, std::ifstream::in);
1210 if (!input.is_open()) {
1211 std::cerr << "Unable to open " << headerFile << std::endl;
1212 return false;
1213 }
1214
1215 bool hasQtBeginNamespace = false;
1216 std::string qtBeginNamespace;
1217 std::string qtEndNamespace;
1218 bool hasWeMeantIt = false;
1219 bool isSuspended = false;
1220 bool isMultiLineComment = false;
1221 std::size_t bracesDepth = 0;
1222 std::size_t namespaceCount = 0;
1223 std::string namespaceString;
1224
1225 std::smatch match;
1226
1227 std::string buffer;
1228 std::string line;
1229 std::string tmpLine;
1230 std::size_t linesProcessed = 0;
1231 int faults = NoChecks;
1232
1233 const auto error = [&] () -> decltype(auto) {
1234 return std::cerr << ErrorMessagePreamble << m_currentFileString
1235 << ":" << m_currentFileLineNumber << " ";
1236 };
1237
1238 // Read file line by line
1239 while (std::getline(input, tmpLine)) {
1240 ++m_currentFileLineNumber;
1241 line.append(tmpLine);
1242 if (line.empty() || line.at(line.size() - 1) == '\\') {
1243 continue;
1244 }
1245 buffer.clear();
1246 buffer.reserve(line.size());
1247 // Optimize processing by looking for a special sequences such as:
1248 // - start-end of comments
1249 // - start-end of class/structures
1250 // And avoid processing of the the data inside these blocks.
1251 for (std::size_t i = 0; i < line.size(); ++i) {
1252 if (line[i] == '\r')
1253 continue;
1254 if (bracesDepth == namespaceCount) {
1255 if (line[i] == '/') {
1256 if ((i + 1) < line.size()) {
1257 if (line[i + 1] == '*') {
1258 isMultiLineComment = true;
1259 continue;
1260 } else if (line[i + 1] == '/') { // Single line comment
1261 if (!(skipChecks & WeMeantItChecks)
1262 && line.find(WeMeantItString) != std::string::npos) {
1263 hasWeMeantIt = true;
1264 continue;
1265 }
1266 if (m_versionScriptGeneratorState != Stopped
1267 && std::regex_match(line, match, ElfVersionTagRegex)) {
1268 if (match[1].str() == "ignore")
1269 m_versionScriptGeneratorState = Ignore;
1270 else if (match[1].str() == "ignore-next")
1271 m_versionScriptGeneratorState = IgnoreNext;
1272 else if (match[1].str() == "stop")
1273 m_versionScriptGeneratorState = Stopped;
1274 }
1275 break;
1276 }
1277 }
1278 } else if (line[i] == '*' && (i + 1) < line.size() && line[i + 1] == '/') {
1279 ++i;
1280 isMultiLineComment = false;
1281 continue;
1282 }
1283 }
1284
1285 if (isMultiLineComment) {
1286 if (!(skipChecks & WeMeantItChecks) &&
1287 line.find(WeMeantItString) != std::string::npos) {
1288 hasWeMeantIt = true;
1289 continue;
1290 }
1291 continue;
1292 }
1293
1294 if (line[i] == '{') {
1295 if (std::regex_match(buffer, match, NamespaceRegex)) {
1296 ++namespaceCount;
1297 namespaceString += "::";
1298 namespaceString += match[1].str();
1299 }
1300 ++bracesDepth;
1301 continue;
1302 } else if (line[i] == '}') {
1303 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1304 namespaceString.resize(namespaceString.rfind("::"));
1305 --namespaceCount;
1306 }
1307 --bracesDepth;
1308 } else if (bracesDepth == namespaceCount) {
1309 buffer += line[i];
1310 }
1311 }
1312 line.clear();
1313
1314 scannerDebug() << m_currentFilename << ": " << buffer << std::endl;
1315
1316 if (m_currentFileType & PrivateHeader) {
1317 parseVersionScriptContent(buffer, result);
1318 }
1319
1320 if (buffer.empty())
1321 continue;
1322
1323 ++linesProcessed;
1324
1325 bool skipSymbols =
1326 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1327 || (m_currentFileType & SsgHeader) || (m_currentFileType & SpiHeader);
1328
1329 // Parse pragmas
1330 if (std::regex_match(buffer, MacroRegex)) {
1331 if (std::regex_match(buffer, SkipHeaderCheckRegex)) {
1332 skipChecks = AllChecks;
1333 faults = NoChecks;
1334 } else if (std::regex_match(buffer, StopProcessingRegex)) {
1335 if (skipChecks == AllChecks)
1336 m_headerCheckExceptions.push_back(m_currentFileString);
1337 return true;
1338 } else if (std::regex_match(buffer, SuspendProcessingRegex)) {
1339 isSuspended = true;
1340 } else if (std::regex_match(buffer, ResumeProcessingRegex)) {
1341 isSuspended = false;
1342 } else if (std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
1343 if (!skipSymbols) {
1344 updateSymbolDescriptor(match[1].str(), m_currentFilename,
1345 SymbolDescriptor::Pragma);
1346 } else {
1347 // TODO: warn about skipping symbols that are defined explicitly
1348 }
1349 } else if (std::regex_match(buffer, NoMasterIncludePragmaRegex)) {
1350 result.masterInclude = false;
1351 } else if (std::regex_match(buffer, match, DeprecatesPragmaRegex)) {
1352 m_deprecatedHeaders[match[1].str()] =
1353 m_commandLineArgs->moduleName() + '/' + m_currentFilename;
1354 } else if (std::regex_match(buffer, OnceRegex)) {
1355 if (!(skipChecks & PragmaOnceChecks)) {
1356 faults |= PragmaOnceChecks;
1357 error() << "\"#pragma once\" is not allowed in installed header files: "
1358 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1359 << std::endl;
1360 }
1361 } else if (std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
1362 if (!(skipChecks & IncludeChecks)) {
1363 std::string includedHeader = match[1].str();
1364 if (!(skipChecks & PrivateHeaderChecks)
1365 && isHeaderPrivate(std::filesystem::path(includedHeader)
1366 .filename()
1367 .generic_string())) {
1368 faults |= PrivateHeaderChecks;
1369 error() << "includes private header " << includedHeader << std::endl;
1370 }
1371 for (const auto &module : m_commandLineArgs->knownModules()) {
1372 std::string suggestedHeader = "Qt" + module + '/' + includedHeader;
1373 const std::string suggestedHeaderReversePath = "/../" + suggestedHeader;
1374 if (std::filesystem::exists(m_commandLineArgs->includeDir()
1375 + suggestedHeaderReversePath)
1376 || std::filesystem::exists(m_commandLineArgs->installIncludeDir()
1377 + '/' + suggestedHeader)) {
1378 faults |= IncludeChecks;
1379 std::cerr << m_warningMessagePreamble << m_currentFileString
1380 << ":" << m_currentFileLineNumber
1381 << " includes " << includedHeader
1382 << " when it should include "
1383 << suggestedHeader << std::endl;
1384 }
1385 }
1386 }
1387 }
1388 continue;
1389 }
1390
1391 // Logic below this line is affected by the 'qt_sync_suspend_processing' and
1392 // 'qt_sync_resume_processing' pragmas.
1393 if (isSuspended)
1394 continue;
1395
1396 // Look for the symbols in header file.
1397 if (!skipSymbols) {
1398 std::string symbol;
1399 if (checkLineForSymbols(buffer, symbol)) {
1400 if (namespaceCount == 0
1401 || std::regex_match(namespaceString,
1402 m_commandLineArgs->publicNamespaceRegex())) {
1403 updateSymbolDescriptor(symbol, m_currentFilename,
1404 SymbolDescriptor::Declaration);
1405 }
1406 continue;
1407 } else if (std::regex_match(buffer, match, DeclareIteratorRegex)) {
1408 std::string iteratorSymbol = match[1].str() + "Iterator";
1409 updateSymbolDescriptor(std::string("Q") + iteratorSymbol, m_currentFilename,
1410 SymbolDescriptor::Declaration);
1411 updateSymbolDescriptor(std::string("QMutable") + iteratorSymbol,
1412 m_currentFilename, SymbolDescriptor::Declaration);
1413 continue;
1414 } else if (std::regex_match(buffer, match, RequireConfigRegex)) {
1415 result.requireConfig = match[1].str();
1416 continue;
1417 }
1418 }
1419
1420 // Check for both QT_BEGIN_NAMESPACE and QT_END_NAMESPACE macros are present in the
1421 // header file.
1422 if (!(skipChecks & NamespaceChecks)) {
1423 if (std::regex_match(buffer, match, BeginNamespaceRegex)) {
1424 qtBeginNamespace = match[1].str();
1425 hasQtBeginNamespace = true;
1426 } else if (std::regex_match(buffer, match, EndNamespaceRegex)) {
1427 qtEndNamespace = match[1].str();
1428 }
1429 }
1430 }
1431 input.close();
1432
1433 // Error out if namespace checks are failed.
1434 if (!(skipChecks & NamespaceChecks)) {
1435 if (hasQtBeginNamespace) {
1436 if (qtBeginNamespace != qtEndNamespace) {
1437 faults |= NamespaceChecks;
1438 std::cerr << m_warningMessagePreamble << m_currentFileString
1439 << " the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1440 << " doesn't match the end namespace macro QT_END_NAMESPACE"
1441 << qtEndNamespace << std::endl;
1442 }
1443 } else {
1444 faults |= NamespaceChecks;
1445 std::cerr << m_warningMessagePreamble << m_currentFileString
1446 << " does not include QT_BEGIN_NAMESPACE" << std::endl;
1447 }
1448 }
1449
1450 if (!(skipChecks & WeMeantItChecks) && !hasWeMeantIt) {
1451 faults |= WeMeantItChecks;
1452 std::cerr << m_warningMessagePreamble << m_currentFileString
1453 << " does not have the \"We mean it.\" warning"
1454 << std::endl;
1455 }
1456
1457 scannerDebug() << "linesTotal: " << m_currentFileLineNumber
1458 << " linesProcessed: " << linesProcessed << std::endl;
1459
1460 if (skipChecks == AllChecks)
1461 m_headerCheckExceptions.push_back(m_currentFileString);
1462
1463 // Exit with an error if any of critical checks are present.
1464 return !(faults & m_criticalChecks);
1465 }
1466
1467 // The function checks if line contains the symbol that needs to have a CaMeL-style alias.
1468 [[nodiscard]] bool checkLineForSymbols(const std::string &line, std::string &symbol)
1469 {
1470 scannerDebug() << "checkLineForSymbols: " << line << std::endl;
1471
1472 // This regex checks if line contains class or structure declaration like:
1473 // - <class|stuct> StructName
1474 // - template <> class ClassName
1475 // - class ClassName : [public|protected|private] BaseClassName
1476 // - class ClassName [QT_TEXT_STREAM_FINAL|Q_DECL_FINAL|final|sealed]
1477 // And possible combinations of the above variants.
1478 static const std::regex ClassRegex(
1479 "^ *(template *<.*> *)?(class|struct +)([^<>:]*\\s+)?" // Preceding part
1480 "((?!Q[A-Z_0-9]*_FINAL|final|sealed)Q[a-zA-Z0-9_]+)" // Actual symbol
1481 "(\\s+Q[A-Z_0-9]*_FINAL|\\s+final|\\s+sealed)?\\s*(:|$).*"); // Trailing part
1482
1483 // This regex checks if line contains function pointer typedef declaration like:
1484 // - typedef void (* QFunctionPointerType)(int, char);
1485 static const std::regex FunctionPointerRegex(
1486 "^ *typedef *.*\\‍(\\*(Q[^\\‍)]+)\\‍)\\‍(.*\\‍); *");
1487
1488 // This regex checks if line contains class or structure typedef declaration like:
1489 // - typedef AnySymbol<char> QAnySymbolType;
1490 static const std::regex TypedefRegex("^ *typedef\\s+(.*)\\s+(Q\\w+); *$");
1491
1492 std::smatch match;
1493 if (std::regex_match(line, match, FunctionPointerRegex)) {
1494 symbol = match[1].str();
1495 } else if (std::regex_match(line, match, TypedefRegex)) {
1496 symbol = match[2].str();
1497 } else if (std::regex_match(line, match, ClassRegex)) {
1498 symbol = match[4].str();
1499 } else {
1500 return false;
1501 }
1502 return !symbol.empty();
1503 }
1504
1505 [[nodiscard]] bool isHeaderQpa(const std::string &headerFileName)
1506 {
1507 return std::regex_match(headerFileName, m_commandLineArgs->qpaHeadersRegex());
1508 }
1509
1510 [[nodiscard]] bool isHeaderRhi(const std::string &headerFileName)
1511 {
1512 return std::regex_match(headerFileName, m_commandLineArgs->rhiHeadersRegex());
1513 }
1514
1515 [[nodiscard]] bool isHeaderSsg(const std::string &headerFileName)
1516 {
1517 return std::regex_match(headerFileName, m_commandLineArgs->ssgHeadersRegex());
1518 }
1519
1520 [[nodiscard]] bool isHeaderSpi(const std::string &headerFileName)
1521 {
1522 return std::regex_match(headerFileName, m_commandLineArgs->spiHeadersRegex());
1523 }
1524
1525 [[nodiscard]] bool isHeaderPrivate(const std::string &headerFile)
1526 {
1527 return std::regex_match(headerFile, m_commandLineArgs->privateHeadersRegex());
1528 }
1529
1530 [[nodiscard]] bool isHeaderPCH(const std::string &headerFilename)
1531 {
1532 static const std::string pchSuffix("_pch.h");
1533 return headerFilename.find(pchSuffix, headerFilename.size() - pchSuffix.size())
1534 != std::string::npos;
1535 }
1536
1537 [[nodiscard]] bool isHeader(const std::filesystem::path &path)
1538 {
1539 return path.extension().string() == ".h";
1540 }
1541
1542 [[nodiscard]] bool isDocFileHeuristic(const std::string &headerFilePath)
1543 {
1544 return headerFilePath.find("/doc/") != std::string::npos;
1545 }
1546
1547 [[nodiscard]] bool isHeaderGenerated(const std::string &header)
1548 {
1549 return m_commandLineArgs->generatedHeaders().find(header)
1550 != m_commandLineArgs->generatedHeaders().end();
1551 }
1552
1553 [[nodiscard]] bool generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath,
1554 const std::string &aliasedFilePath);
1555
1557 const std::string &outputFilePath, const std::string &aliasedFilePath,
1558 const FileStamp &originalStamp = FileStamp::clock::now());
1559
1560 bool writeIfDifferent(const std::string &outputFile, const std::string &buffer);
1561
1562 [[nodiscard]] bool generateMasterHeader()
1563 {
1564 if (m_masterHeaderContents.empty())
1565 return true;
1566
1567 std::string outputFile =
1568 m_commandLineArgs->includeDir() + '/' + m_commandLineArgs->moduleName();
1569
1570 std::string moduleUpper = utils::asciiToUpper(m_commandLineArgs->moduleName());
1571 std::stringstream buffer;
1572 buffer << "#ifndef QT_" << moduleUpper << "_MODULE_H\n"
1573 << "#define QT_" << moduleUpper << "_MODULE_H\n"
1574 << "#include <" << m_commandLineArgs->moduleName() << "/"
1575 << m_commandLineArgs->moduleName() << "Depends>\n";
1576 for (const auto &headerContents : m_masterHeaderContents) {
1577 if (!headerContents.second.empty()) {
1578 buffer << "#if QT_CONFIG(" << headerContents.second << ")\n"
1579 << "#include <" << m_commandLineArgs->moduleName() << "/"
1580 << headerContents.first << ">\n"
1581 << "#endif\n";
1582 } else {
1583 buffer << "#include <" << m_commandLineArgs->moduleName() << "/"
1584 << headerContents.first << ">\n";
1585 }
1586 }
1587 buffer << "#endif\n";
1588
1589 m_producedHeaders.insert(m_commandLineArgs->moduleName());
1590 return writeIfDifferent(outputFile, buffer.str());
1591 }
1592
1593 [[nodiscard]] bool generateVersionHeader(const std::string &outputFile)
1594 {
1595 std::string moduleNameUpper = utils::asciiToUpper( m_commandLineArgs->moduleName());
1596
1597 std::stringstream buffer;
1598 buffer << "/* This file was generated by syncqt. */\n"
1599 << "#ifndef QT_" << moduleNameUpper << "_VERSION_H\n"
1600 << "#define QT_" << moduleNameUpper << "_VERSION_H\n\n"
1601 << "#define " << moduleNameUpper << "_VERSION_STR \"" << QT_VERSION_STR << "\"\n\n"
1602 << "#define " << moduleNameUpper << "_VERSION "
1603 << "0x0" << QT_VERSION_MAJOR << "0" << QT_VERSION_MINOR << "0" << QT_VERSION_PATCH
1604 << "\n\n"
1605 << "#endif // QT_" << moduleNameUpper << "_VERSION_H\n";
1606
1607 return writeIfDifferent(outputFile, buffer.str());
1608 }
1609
1610 [[nodiscard]] bool generateDeprecatedHeaders()
1611 {
1612 static std::regex cIdentifierSymbolsRegex("[^a-zA-Z0-9_]");
1613 const std::string guard_base = "DEPRECATED_HEADER_" + m_commandLineArgs->moduleName();
1614 bool result = true;
1615 for (auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) {
1616 const std::string &descriptor = it->first;
1617 const std::string &replacement = it->second;
1618
1619 const auto separatorPos = descriptor.find(',');
1620 std::string headerPath = descriptor.substr(0, separatorPos);
1621 std::string versionDisclaimer;
1622 if (separatorPos != std::string::npos) {
1623 std::string version = descriptor.substr(separatorPos + 1);
1624 versionDisclaimer = " and will be removed in Qt " + version;
1625 int minor = 0;
1626 int major = 0;
1627 if (!utils::parseVersion(version, major, minor)) {
1628 std::cerr << ErrorMessagePreamble
1629 << "Invalid version format specified for the deprecated header file "
1630 << headerPath << ": '" << version
1631 << "'. Expected format: 'major.minor'.\n";
1632 result = false;
1633 continue;
1634 }
1635
1636 if (QT_VERSION_MAJOR > major
1637 || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) {
1638 std::cerr << WarningMessagePreamble << headerPath
1639 << " is marked as deprecated and will not be generated in Qt "
1640 << QT_VERSION_STR
1641 << ". The respective qt_deprecates pragma needs to be removed.\n";
1642 continue;
1643 }
1644 }
1645
1646 const auto moduleSeparatorPos = headerPath.find('/');
1647 std::string headerName = moduleSeparatorPos != std::string::npos
1648 ? headerPath.substr(moduleSeparatorPos + 1)
1649 : headerPath;
1650 const std::string moduleName = moduleSeparatorPos != std::string::npos
1651 ? headerPath.substr(0, moduleSeparatorPos)
1652 : m_commandLineArgs->moduleName();
1653
1654 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs->moduleName();
1655
1656 std::string qualifiedHeaderName =
1657 std::regex_replace(headerName, cIdentifierSymbolsRegex, "_");
1658 std::string guard = guard_base + "_" + qualifiedHeaderName;
1659 std::string warningText = "Header <" + moduleName + "/" + headerName + "> is deprecated"
1660 + versionDisclaimer + ". Please include <" + replacement + "> instead.";
1661 std::stringstream buffer;
1662 buffer << "#ifndef " << guard << "\n"
1663 << "#define " << guard << "\n"
1664 << "#if defined(__GNUC__)\n"
1665 << "# warning " << warningText << "\n"
1666 << "#elif defined(_MSC_VER)\n"
1667 << "# pragma message (\"" << warningText << "\")\n"
1668 << "#endif\n"
1669 << "#include <" << replacement << ">\n"
1670 << "#endif\n";
1671
1672 const std::string outputDir = isCrossModuleDeprecation
1673 ? m_commandLineArgs->includeDir() + "/../" + moduleName
1674 : m_commandLineArgs->includeDir();
1675 writeIfDifferent(outputDir + '/' + headerName, buffer.str());
1676
1677 // Add header file to staging installation directory for cross-module deprecation case.
1678 if (isCrossModuleDeprecation) {
1679 const std::string stagingDir = outputDir + "/.syncqt_staging/";
1680 writeIfDifferent(stagingDir + headerName, buffer.str());
1681 }
1682 m_producedHeaders.insert(headerName);
1683 }
1684 return result;
1685 }
1686
1688 {
1689 std::stringstream buffer;
1690 for (const auto &header : m_headerCheckExceptions)
1691 buffer << header << ";";
1692 return writeIfDifferent(m_commandLineArgs->binaryDir() + '/'
1693 + m_commandLineArgs->moduleName()
1694 + "_header_check_exceptions",
1695 buffer.str());
1696 }
1697
1698 [[nodiscard]] bool generateLinkerVersionScript()
1699 {
1700 std::stringstream buffer;
1701 for (const auto &content : m_versionScriptContents)
1702 buffer << content;
1703 return writeIfDifferent(m_commandLineArgs->versionScriptFile(), buffer.str());
1704 }
1705
1706 bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) noexcept;
1707 void updateSymbolDescriptor(const std::string &symbol, const std::string &file,
1708 SymbolDescriptor::SourceType type);
1709};
1710
1711// The function updates information about the symbol:
1712// - The path and modification time of the file where the symbol was found.
1713// - The source of finding
1714// Also displays a short info about a symbol in show only mode.
1715void SyncScanner::updateSymbolDescriptor(const std::string &symbol, const std::string &file,
1716 SymbolDescriptor::SourceType type)
1717{
1718 if (m_commandLineArgs->showOnly() || m_commandLineArgs->debug())
1719 std::cout << " SYMBOL: " << symbol << std::endl;
1720 m_symbols[symbol].update(file, type);
1721}
1722
1723[[nodiscard]] std::filesystem::path
1724SyncScanner::makeHeaderAbsolute(const std::string &filename) const
1725{
1726 if (std::filesystem::path(filename).is_relative())
1727 return utils::normilizedPath(m_commandLineArgs->sourceDir() + '/' + filename);
1728
1729 return utils::normilizedPath(filename);
1730}
1731
1732bool SyncScanner::updateOrCopy(const std::filesystem::path &src,
1733 const std::filesystem::path &dst) noexcept
1734{
1735 if (m_commandLineArgs->showOnly())
1736 return true;
1737
1738 if (src == dst) {
1739 std::cout << "Source and destination paths are same when copying " << src.string()
1740 << ". Skipping." << std::endl;
1741 return true;
1742 }
1743
1744 std::error_code ec;
1745 std::filesystem::copy(src, dst, std::filesystem::copy_options::update_existing, ec);
1746 if (ec) {
1747 ec.clear();
1748 std::filesystem::remove(dst, ec);
1749 if (ec) {
1750 // On some file systems(e.g. vboxfs) the std::filesystem::copy doesn't support
1751 // std::filesystem::copy_options::overwrite_existing remove file first and then copy.
1752 std::cerr << "Unable to remove file: " << src << " to " << dst << " error: ("
1753 << ec.value() << ")" << ec.message() << std::endl;
1754 return false;
1755 }
1756
1757 std::filesystem::copy(src, dst, std::filesystem::copy_options::overwrite_existing, ec);
1758 if (ec) {
1759 std::cerr << "Unable to copy file: " << src << " to " << dst << " error: ("
1760 << ec.value() << ")" << ec.message() << std::endl;
1761 return false;
1762 }
1763 }
1764 return true;
1765}
1766
1767// The function generates Qt CaMeL-case files.
1768// CaMeL-case files can have timestamp that is the same as or newer than timestamp of file that
1769// supposed to included there. It's not an issue when we generate regular aliases because the
1770// content of aliases is always the same, but we only want to "touch" them when content of original
1771// is changed.
1772bool SyncScanner::generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath,
1773 const std::string &aliasedFilePath)
1774{
1775 if (m_commandLineArgs->showOnly())
1776 return true;
1777
1778 // Safety check: aliasedFilePath should not be empty
1779 if (aliasedFilePath.empty()) {
1780 std::cerr << "ERROR: Empty aliasedFilePath for " << outputFilePath << std::endl;
1781 return false;
1782 }
1783
1784 std::string buffer = "#include <";
1785 buffer += m_commandLineArgs->moduleName() + "/";
1786 buffer += aliasedFilePath;
1787 buffer += "> // IWYU pragma: export\n";
1788
1789 return writeIfDifferent(outputFilePath, buffer);
1790}
1791
1792// The function generates aliases for files in source tree. Since the content of these aliases is
1793// always same, it's ok to check only timestamp and touch files in case if stamp of original is
1794// newer than the timestamp of an alias.
1796 const std::string &aliasedFilePath,
1797 const FileStamp &originalStamp)
1798{
1799 if (m_commandLineArgs->showOnly())
1800 return true;
1801
1802 std::filesystem::path aliased(aliasedFilePath);
1803 std::filesystem::path includeDir(m_commandLineArgs->includeDir());
1804
1805 // Check if paths have the same root (drive on Windows).
1806 // If they don't, the alias cannot be inside includeDir, so use absolute path.
1807 bool sameRoot = !aliased.is_absolute() || includeDir.root_name() == aliased.root_name();
1808
1809 auto relativePath = sameRoot ? std::filesystem::relative(aliased, includeDir).generic_string()
1810 : std::string();
1811 bool aliasIsInsideIncludeDir = sameRoot && relativePath.find("../") != 0;
1812
1813 if (std::filesystem::exists({ outputFilePath })
1814 && std::filesystem::last_write_time({ outputFilePath }) >= originalStamp) {
1815 return true;
1816 }
1817 scannerDebug() << "Rewrite " << outputFilePath << std::endl;
1818
1819 std::ofstream ofs;
1820 ofs.open(outputFilePath, std::ofstream::out | std::ofstream::trunc);
1821 if (!ofs.is_open()) {
1822 std::cerr << "Unable to write header file alias: " << outputFilePath << std::endl;
1823 return false;
1824 }
1825
1826 ofs << "#include ";
1827 if (aliasIsInsideIncludeDir)
1828 ofs << "<" << m_commandLineArgs->moduleName() + "/" << relativePath << ">";
1829 else
1830 ofs << "\"" << aliasedFilePath << "\"";
1831 ofs << " // IWYU pragma: export\n";
1832 ofs.close();
1833 return true;
1834}
1835
1836bool SyncScanner::writeIfDifferent(const std::string &outputFile, const std::string &buffer)
1837{
1838 if (m_commandLineArgs->showOnly())
1839 return true;
1840
1841 static const std::streamsize bufferSize = 1025;
1842 bool differs = false;
1843 std::filesystem::path outputFilePath(outputFile);
1844
1845 std::string outputDirectory = outputFilePath.parent_path().string();
1846
1847 if (!utils::createDirectories(outputDirectory, "Unable to create output directory"))
1848 return false;
1849
1850 auto expectedSize = buffer.size();
1851#ifdef _WINDOWS
1852 // File on disk has \r\n instead of just \n
1853 expectedSize += std::count(buffer.begin(), buffer.end(), '\n');
1854#endif
1855
1856 if (std::filesystem::exists(outputFilePath)
1857 && expectedSize == std::filesystem::file_size(outputFilePath)) {
1858 char rdBuffer[bufferSize];
1859 memset(rdBuffer, 0, bufferSize);
1860
1861 std::ifstream ifs(outputFile, std::fstream::in);
1862 if (!ifs.is_open()) {
1863 std::cerr << "Unable to open " << outputFile << " for comparison." << std::endl;
1864 return false;
1865 }
1866 std::streamsize currentPos = 0;
1867
1868 std::size_t bytesRead = 0;
1869 do {
1870 ifs.read(rdBuffer, bufferSize - 1); // Read by 1K
1871 bytesRead = ifs.gcount();
1872 if (buffer.compare(currentPos, bytesRead, rdBuffer) != 0) {
1873 differs = true;
1874 break;
1875 }
1876 currentPos += bytesRead;
1877 memset(rdBuffer, 0, bufferSize);
1878 } while (bytesRead > 0);
1879
1880 ifs.close();
1881 } else {
1882 differs = true;
1883 }
1884
1885 scannerDebug() << "Update: " << outputFile << " " << differs << std::endl;
1886 if (differs) {
1887 std::ofstream ofs;
1888 ofs.open(outputFilePath, std::fstream::out | std::ofstream::trunc);
1889 if (!ofs.is_open()) {
1890 std::cerr << "Unable to write header content to " << outputFilePath << std::endl;
1891 return false;
1892 }
1893 ofs << buffer;
1894
1895 ofs.close();
1896 }
1897 return true;
1898}
1899
1900int main(int argc, char *argv[])
1901{
1902 CommandLineOptions options(argc, argv);
1903 if (!options.isValid())
1904 return InvalidArguments;
1905
1906 if (options.printHelpOnly()) {
1907 options.printHelp();
1908 return NoError;
1909 }
1910
1911 SyncScanner scanner = SyncScanner(&options);
1912 return scanner.sync();
1913}
const std::string & rhiIncludeDir() const
Definition main.cpp:215
const std::string & privateIncludeDir() const
Definition main.cpp:211
const std::string & sourceDir() const
Definition main.cpp:203
const std::string & installIncludeDir() const
Definition main.cpp:209
bool copy() const
Definition main.cpp:253
const std::regex & spiHeadersRegex() const
Definition main.cpp:233
const std::string & binaryDir() const
Definition main.cpp:205
bool minimal() const
Definition main.cpp:255
const std::string & ssgIncludeDir() const
Definition main.cpp:217
const std::set< std::string > & generatedHeaders() const
Definition main.cpp:241
bool isValid() const
Definition main.cpp:199
const std::string & moduleName() const
Definition main.cpp:201
bool isInternal() const
Definition main.cpp:245
const std::string & versionScriptFile() const
Definition main.cpp:223
const std::regex & publicNamespaceRegex() const
Definition main.cpp:237
const std::string & stagingDir() const
Definition main.cpp:221
const std::string & qpaIncludeDir() const
Definition main.cpp:213
const std::set< std::string > & knownModules() const
Definition main.cpp:225
const std::string & includeDir() const
Definition main.cpp:207
bool isNonQtModule() const
Definition main.cpp:247
bool debug() const
Definition main.cpp:251
const std::string & spiIncludeDir() const
Definition main.cpp:219
const std::regex & qpaHeadersRegex() const
Definition main.cpp:227
void printHelp() const
Definition main.cpp:261
CommandLineOptions(int argc, char *argv[])
Definition main.cpp:197
const std::regex & ssgHeadersRegex() const
Definition main.cpp:231
const std::regex & privateHeadersRegex() const
Definition main.cpp:235
bool scanAllMode() const
Definition main.cpp:243
bool printHelpOnly() const
Definition main.cpp:249
bool warningsAreErrors() const
Definition main.cpp:259
const std::regex & rhiHeadersRegex() const
Definition main.cpp:229
bool showOnly() const
Definition main.cpp:257
const std::set< std::string > & headers() const
Definition main.cpp:239
bool generateVersionHeader(const std::string &outputFile)
Definition main.cpp:1593
bool generateLinkerVersionScript()
Definition main.cpp:1698
bool isHeaderQpa(const std::string &headerFileName)
Definition main.cpp:1505
void parseVersionScriptContent(const std::string buffer, ParsingResult &result)
Definition main.cpp:1047
bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) noexcept
Definition main.cpp:1732
bool checkLineForSymbols(const std::string &line, std::string &symbol)
Definition main.cpp:1468
void updateSymbolDescriptor(const std::string &symbol, const std::string &file, SymbolDescriptor::SourceType type)
Definition main.cpp:1715
bool isHeaderSpi(const std::string &headerFileName)
Definition main.cpp:1520
bool isHeaderGenerated(const std::string &header)
Definition main.cpp:1547
std::filesystem::path makeHeaderAbsolute(const std::string &filename) const
Definition main.cpp:1724
bool generateMasterHeader()
Definition main.cpp:1562
bool generateAliasedHeaderFileIfTimestampChanged(const std::string &outputFilePath, const std::string &aliasedFilePath, const FileStamp &originalStamp=FileStamp::clock::now())
Definition main.cpp:1795
ErrorCodes sync()
Definition main.cpp:665
bool isHeader(const std::filesystem::path &path)
Definition main.cpp:1537
bool generateDeprecatedHeaders()
Definition main.cpp:1610
bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory, bool skipCleanup=false)
Definition main.cpp:796
bool writeIfDifferent(const std::string &outputFile, const std::string &buffer)
Definition main.cpp:1836
bool isHeaderRhi(const std::string &headerFileName)
Definition main.cpp:1510
bool isHeaderSsg(const std::string &headerFileName)
Definition main.cpp:1515
bool parseHeader(const std::filesystem::path &headerFile, ParsingResult &result, unsigned int skipChecks)
Definition main.cpp:1126
bool isDocFileHeuristic(const std::string &headerFilePath)
Definition main.cpp:1542
bool isHeaderPCH(const std::string &headerFilename)
Definition main.cpp:1530
bool processHeader(const std::filesystem::path &headerFile)
Definition main.cpp:874
SyncScanner(CommandLineOptions *commandLineArgs)
Definition main.cpp:650
bool generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath, const std::string &aliasedFilePath)
Definition main.cpp:1772
bool generateHeaderCheckExceptions()
Definition main.cpp:1687
void resetCurrentFileInfoData(const std::filesystem::path &headerFile)
Definition main.cpp:843
bool isHeaderPrivate(const std::string &headerFile)
Definition main.cpp:1525
Definition main.cpp:80
bool parseVersion(const std::string &version, int &major, int &minor)
Definition main.cpp:95
std::string asciiToLower(std::string s)
Definition main.cpp:81
std::filesystem::path normilizedPath(const std::string &path)
Definition main.cpp:146
void printFilesystemError(const std::filesystem::filesystem_error &fserr, std::string_view errorMsg)
Definition main.cpp:140
void printInternalError()
Definition main.cpp:133
bool createDirectories(const std::string &path, std::string_view errorMsg, bool *exists=nullptr)
Definition main.cpp:157
std::string asciiToUpper(std::string s)
Definition main.cpp:88
static const std::regex GlobalHeaderRegex("^q(.*)global\\.h$")
HeaderChecks
Definition main.cpp:41
@ AllChecks
Definition main.cpp:50
@ NamespaceChecks
Definition main.cpp:43
@ PragmaOnceChecks
Definition main.cpp:47
@ NoChecks
Definition main.cpp:42
@ WeMeantItChecks
Definition main.cpp:46
@ CriticalChecks
Definition main.cpp:49
@ IncludeChecks
Definition main.cpp:45
@ PrivateHeaderChecks
Definition main.cpp:44
ErrorCodes
Definition main.cpp:34
@ SyncFailed
Definition main.cpp:37
@ InvalidArguments
Definition main.cpp:36
@ NoError
Definition main.cpp:35
constexpr std::string_view ErrorMessagePreamble
Definition main.cpp:57
constexpr int LinkerScriptCommentAlignment
Definition main.cpp:53
constexpr std::string_view WarningMessagePreamble
Definition main.cpp:58
bool MasterHeaderIncludeComparator(const std::string &a, const std::string &b)
Definition main.cpp:62
int main(int argc, char *argv[])
[ctor_close]