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