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