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