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 enum class HeaderOrigin { SourceTree, Generated };
697 const auto scanHeaderFile = [this, &error](const std::filesystem::path &path,
698 bool isRegularFile, HeaderOrigin origin) {
699 const std::string filePath = path.generic_string();
700 const bool isHeaderFlag = isHeader(path);
701 const bool isDocFileHeuristicFlag = isDocFileHeuristic(filePath);
702 const bool isGenerated = origin == HeaderOrigin::Generated;
703
704 if (isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag) {
705 scannerDebug() << "Processing header: " << filePath
706 << " isGenerated: " << isGenerated << std::endl;
708 error = SyncFailed;
709 } else {
710 scannerDebug()
711 << "Skipping processing header: " << filePath
712 << " isRegularFile: " << isRegularFile
713 << " isHeaderFlag: " << isHeaderFlag
714 << " isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
715 << " isGenerated: " << isGenerated
716 << std::endl;
717 }
718 };
719
720 for (auto const &entry :
721 std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) {
722 scanHeaderFile(entry.path(), entry.is_regular_file(),
723 HeaderOrigin::SourceTree);
724 }
725
726 // Headers that are generated into the build directory are not covered by the source
727 // directory scan above, so process them explicitly.
728 for (const auto &header : m_commandLineArgs->generatedHeaders()) {
729 if (header.empty())
730 continue;
731 const auto headerPath = makeHeaderAbsolute(header);
732 scanHeaderFile(headerPath, std::filesystem::is_regular_file(headerPath),
733 HeaderOrigin::Generated);
734 }
735 } else {
736 // Since the list of header file is quite big syncqt supports response files to avoid
737 // the issues with long command lines.
738 std::set<std::string> rspHeaders;
739 const auto &headers = m_commandLineArgs->headers();
740 for (auto it = headers.begin(); it != headers.end(); ++it) {
741 const auto &header = *it;
742 scannerDebug() << "Processing header: " << header << std::endl;
744 error = SyncFailed;
745 }
746 }
747 for (const auto &header : rspHeaders) {
748 scannerDebug() << "Processing header: " << header << std::endl;
749 if (!processHeader(makeHeaderAbsolute(header)))
750 error = SyncFailed;
751 }
752 }
753
754 // No further processing in minimal mode.
755 if (m_commandLineArgs->minimal())
756 return error;
757
758 // Generate aliases for all unique symbols collected during the header files parsing.
759 for (auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
760 const std::string &filename = it->second.file();
761 if (!filename.empty()) {
762 const auto camelCaseFile = m_commandLineArgs->includeDir() + '/' + it->first;
763 if (generateForwardingHeader(camelCaseFile, filename)) {
764 m_producedHeaders.insert(it->first);
765 m_moduleMapContents.insert({camelCaseFile, filename});
766 } else {
767 error = SyncFailed;
768 }
769 }
770 }
771
772 // Generate the header file containing version information.
773 if (!m_commandLineArgs->isNonQtModule()) {
774 std::string moduleNameLower = utils::asciiToLower(m_commandLineArgs->moduleName());
775 std::string versionHeaderFilename(moduleNameLower + "version.h");
776 std::string versionHeaderCamel(m_commandLineArgs->moduleName() + "Version");
777 std::string versionFile = m_commandLineArgs->includeDir() + '/' + versionHeaderFilename;
778
779 std::error_code ec;
780 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
781 if (ec)
782 originalStamp = FileStamp::clock::now();
783
784 if (generateVersionHeader(versionFile)) {
785 const auto camelCaseFile =
786 m_commandLineArgs->includeDir() + '/' + versionHeaderCamel;
787 if (!generateAliasedHeaderFileIfTimestampChanged(camelCaseFile,
788 versionFile, originalStamp)) {
789 error = SyncFailed;
790 }
791 m_masterHeaderContents[versionHeaderFilename] = {};
792 m_producedHeaders.insert(versionHeaderFilename);
793 m_producedHeaders.insert(versionHeaderCamel);
794 m_moduleMapContents.insert({versionFile, {}});
795 m_moduleMapContents.insert({camelCaseFile, versionFile});
796 } else {
797 error = SyncFailed;
798 }
799 }
800
801 if (!m_commandLineArgs->scanAllMode()) {
802 if (!m_commandLineArgs->isNonQtModule()) {
804 error = SyncFailed;
805
807 error = SyncFailed;
808 }
809
810 if (!m_commandLineArgs->versionScriptFile().empty()) {
812 error = SyncFailed;
813 }
814 }
815
816 if (!m_commandLineArgs->isNonQtModule()) {
818 error = SyncFailed;
819 }
820
821 if (!m_commandLineArgs->moduleMapFile().empty()) {
823 error = SyncFailed;
824 }
825
826 if (!m_commandLineArgs->scanAllMode() && !m_commandLineArgs->stagingDir().empty()) {
827 // Copy the generated files to a spearate staging directory to make the installation
828 // process eaiser.
830 error = SyncFailed;
831
832 // For framework builds the staging directory is installed into the non-framework
833 // include/<Module> dir alongside the framework, so that its headers can also be
834 // reached via a plain include path. Generate lowercase forwarding headers there
835 // (in addition to the CaMeL case aliases copied above) so that bare includes like
836 // #include <qstring.h> resolve into the framework. These are generated after the
837 // copy above so they are not overwritten by the build-tree source aliases.
838 if (m_commandLineArgs->isFramework()) {
839 for (const auto &header : m_publicHeaders) {
840 if (!generateForwardingHeader(m_commandLineArgs->stagingDir() + '/' + header,
841 header, /*useIncludeNext=*/true)) {
842 error = SyncFailed;
843 }
844 }
845 }
846 }
847 return error;
848 }
849
850 // The function copies files, that were generated while the sync procedure to a staging
851 // directory. This is necessary to simplify the installation of the generated files.
852 [[nodiscard]] bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory,
853 bool skipCleanup = false)
854 {
855 bool result = true;
856 bool outDirExists = false;
857 if (!utils::createDirectories(outputDirectory, "Unable to create staging directory",
858 &outDirExists))
859 return false;
860
861 if (outDirExists && !skipCleanup) {
862 try {
863 for (const auto &entry :
864 std::filesystem::recursive_directory_iterator(outputDirectory)) {
865 if (m_producedHeaders.find(entry.path().filename().generic_string())
866 == m_producedHeaders.end()) {
867 // Check if header file came from another module as result of the
868 // cross-module deprecation before removing it.
869 std::string firstLine;
870 {
871 std::ifstream input(entry.path(), std::ifstream::in);
872 if (input.is_open()) {
873 std::getline(input, firstLine);
874 input.close();
875 }
876 }
877 if (firstLine.find("#ifndef DEPRECATED_HEADER_"
878 + m_commandLineArgs->moduleName())
879 == 0
880 || firstLine.find("#ifndef DEPRECATED_HEADER_") != 0)
881 std::filesystem::remove(entry.path());
882 }
883 }
884 } catch (const std::filesystem::filesystem_error &fserr) {
885 utils::printFilesystemError(fserr, "Unable to clean the staging directory");
886 return false;
887 }
888 }
889
890 for (const auto &header : m_producedHeaders) {
891 std::filesystem::path src(m_commandLineArgs->includeDir() + '/' + header);
892 std::filesystem::path dst(outputDirectory + '/' + header);
893 if (!m_commandLineArgs->showOnly())
894 result &= updateOrCopy(src, dst);
895 }
896 return result;
897 }
898
899 void resetCurrentFileInfoData(const std::filesystem::path &headerFile)
900 {
901 // This regex filters the generated '*exports.h' and '*exports_p.h' header files.
902 static const std::regex ExportsHeaderRegex("^q(.*)exports(_p)?\\.h$");
903
904 m_currentFile = headerFile;
905 m_currentFileLineNumber = 0;
906 m_currentFilename = m_currentFile.filename().generic_string();
907 m_currentFileType = PublicHeader;
908 m_currentFileString = m_currentFile.generic_string();
909 m_currentFileInSourceDir = m_currentFileString.find(m_commandLineArgs->sourceDir()) == 0;
910
911 if (isHeaderPrivate(m_currentFilename))
912 m_currentFileType = PrivateHeader;
913
914 if (isHeaderQpa(m_currentFilename))
915 m_currentFileType = QpaHeader | PrivateHeader;
916
917 if (isHeaderRhi(m_currentFilename))
918 m_currentFileType = RhiHeader | PrivateHeader;
919
920 if (isHeaderSsg(m_currentFilename))
921 m_currentFileType = SsgHeader | PrivateHeader;
922
923 if (isHeaderSpi(m_currentFilename))
924 m_currentFileType = SpiHeader | PrivateHeader;
925
926 if (std::regex_match(m_currentFilename, ExportsHeaderRegex))
927 m_currentFileType |= ExportHeader;
928 }
929
930 [[nodiscard]] bool processHeader(const std::filesystem::path &headerFile)
931 {
932 // This regex filters any paths that contain the '3rdparty' directory.
933 static const std::regex ThirdPartyFolderRegex("(^|.+/)3rdparty/.+");
934
935 // This regex filters '-config.h' and '-config_p.h' header files.
936 static const std::regex ConfigHeaderRegex("^(q|.+-)config(_p)?\\.h");
937
939
940 bool isPrivate = m_currentFileType & PrivateHeader;
941 bool isQpa = m_currentFileType & QpaHeader;
942 bool isRhi = m_currentFileType & RhiHeader;
943 bool isSsg = m_currentFileType & SsgHeader;
944 bool isSpi = m_currentFileType & SpiHeader;
945 bool isExport = m_currentFileType & ExportHeader;
946 bool isPublic = !isPrivate && !isQpa && !isRhi && !isSsg && !isSpi;
947
948 // We assume that header files ouside of the module source or build directories do not
949 // belong to the module. Skip any processing.
950 if (!m_currentFileInSourceDir
951 && m_currentFileString.find(m_commandLineArgs->binaryDir()) != 0) {
952 scannerDebug() << "Header file: " << headerFile
953 << " is outside the sync directories. Skipping." << std::endl;
954 m_headerCheckExceptions.push_back(m_currentFileString);
955
956 // For some reason we don't treat the export header or Depends header as
957 // "belonging to the module", as per the comment above. Yet we do need it
958 // in the module map if it's in the include dir.
959 // And we need to create a forwarding header in the install include dir for
960 // framework builds.
961 if (m_currentFileString.find(m_commandLineArgs->includeDir()) == 0) {
962 m_moduleMapContents.insert({m_currentFileString, {}});
963
964 if (m_commandLineArgs->isFramework() && isPublic)
965 m_publicHeaders.insert(m_currentFilename);
966 }
967
968 return true;
969 }
970
971 // Check if a directory is passed as argument. That shouldn't happen, print error and exit.
972 if (m_currentFilename.empty()) {
973 std::cerr << "Header file name of " << m_currentFileString << "is empty" << std::endl;
974 return false;
975 }
976
977 std::error_code ec;
978 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
979 if (ec)
980 originalStamp = FileStamp::clock::now();
981 ec.clear();
982
983 scannerDebug()
984 << "processHeader:start: " << headerFile
985 << " m_currentFilename: " << m_currentFilename
986 << " isPrivate: " << isPrivate
987 << " isQpa: " << isQpa
988 << " isRhi: " << isRhi
989 << " isSsg: " << isSsg
990 << " isSpi: " << isSpi
991 << " isExport: " << isExport
992 << std::endl;
993
994 // Chose the directory where to generate the header aliases or to copy header file if
995 // the '-copy' argument is passed.
996 std::string outputDir = m_commandLineArgs->includeDir();
997 if (isQpa)
998 outputDir = m_commandLineArgs->qpaIncludeDir();
999 else if (isRhi)
1000 outputDir = m_commandLineArgs->rhiIncludeDir();
1001 else if (isSsg)
1002 outputDir = m_commandLineArgs->ssgIncludeDir();
1003 else if (isSpi)
1004 outputDir = m_commandLineArgs->spiIncludeDir();
1005 else if (isPrivate)
1006 outputDir = m_commandLineArgs->privateIncludeDir();
1007
1008 if (!utils::createDirectories(outputDir, "Unable to create output directory"))
1009 return false;
1010
1011 bool headerFileExists = std::filesystem::exists(headerFile);
1012
1013 std::string aliasedFilepath = headerFile.generic_string();
1014
1015 std::string aliasPath = outputDir + '/' + m_currentFilename;
1016
1017 // If the '-copy' argument is passed, we copy the original file to a corresponding output
1018 // directory otherwise we only create a header file alias that contains relative path to
1019 // the original header file in the module source or build tree.
1020 if (m_commandLineArgs->copy() && headerFileExists) {
1021 if (!updateOrCopy(headerFile, aliasPath))
1022 return false;
1023 } else {
1024 if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
1025 originalStamp))
1026 return false;
1027 }
1028
1029 // Remember the public headers so that, for framework builds, we can generate
1030 // forwarding headers that re-expose them via a plain (non-framework) include path
1031 // alongside the framework (see generateForwardingHeader).
1032 if (isPublic)
1033 m_publicHeaders.insert(m_currentFilename);
1034
1035 // No further processing in minimal mode.
1036 if (m_commandLineArgs->minimal())
1037 return true;
1038
1039 // Stop processing if header files doesn't exist. This happens at configure time, since
1040 // either header files are generated later than syncqt is running or header files only
1041 // generated at build time. These files will be processed at build time, if CMake files
1042 // contain the correct dependencies between the missing header files and the module
1043 // 'sync_headers' targets.
1044 if (!headerFileExists) {
1045 scannerDebug() << "Header file: " << headerFile
1046 << " doesn't exist, but is added to syncqt scanning. Skipping.";
1047 return true;
1048 }
1049
1050 bool isGenerated = isHeaderGenerated(m_currentFileString);
1051
1052 // Make sure that we detect the '3rdparty' directory inside the source directory only,
1053 // since full path to the Qt sources might contain '/3rdparty/' too.
1054 bool is3rdParty = std::regex_match(
1055 std::filesystem::relative(headerFile, m_commandLineArgs->sourceDir())
1056 .generic_string(),
1057 ThirdPartyFolderRegex);
1058
1059 // No processing of generated Qt config header files.
1060 if (!std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
1061 unsigned int skipChecks = m_commandLineArgs->scanAllMode() ? AllChecks : NoChecks;
1062
1063 // Collect checks that should skipped for the header file.
1064 if (m_commandLineArgs->isNonQtModule() || is3rdParty || isQpa || isRhi || isSsg
1065 || isSpi || !m_currentFileInSourceDir || isGenerated) {
1066 skipChecks = AllChecks;
1067 } else {
1068 if (std::regex_match(m_currentFilename, GlobalHeaderRegex) || isExport)
1069 skipChecks |= NamespaceChecks;
1070
1071 if (isHeaderPCH(m_currentFilename))
1072 skipChecks |= WeMeantItChecks;
1073
1074 if (isPrivate) {
1075 skipChecks |= NamespaceChecks;
1076 skipChecks |= PrivateHeaderChecks;
1077 skipChecks |= IncludeChecks;
1078 } else {
1079 skipChecks |= WeMeantItChecks;
1080 }
1081 }
1082
1083 ParsingResult parsingResult;
1084 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
1085 && isPublic && !isGenerated;
1086 if (!parseHeader(headerFile, parsingResult, skipChecks)) {
1087 scannerDebug() << "parseHeader failed: " << headerFile << std::endl;
1088 return false;
1089 }
1090
1091 // Record the private header file inside the version script content.
1092 if (isPrivate && !m_commandLineArgs->versionScriptFile().empty()
1093 && !parsingResult.versionScriptContent.empty()) {
1094 m_versionScriptContents.insert(m_versionScriptContents.end(),
1095 parsingResult.versionScriptContent.begin(),
1096 parsingResult.versionScriptContent.end());
1097 }
1098
1099 // Add the '#if QT_CONFIG(<feature>)' check for header files that supposed to be
1100 // included into the module master header only if corresponding feature is enabled.
1101 bool willBeInModuleMasterHeader = false;
1102 if (isPublic) {
1103 if (m_currentFilename.find('_') == std::string::npos
1104 && parsingResult.masterInclude) {
1105 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
1106 willBeInModuleMasterHeader = true;
1107 }
1108 }
1109
1110 scannerDebug()
1111 << "processHeader:end: " << headerFile
1112 << " is3rdParty: " << is3rdParty
1113 << " isGenerated: " << isGenerated
1114 << " m_currentFileInSourceDir: " << m_currentFileInSourceDir
1115 << " willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
1116 << std::endl;
1117 } else if (m_currentFilename == "qconfig.h") {
1118 // Hardcode generating of QtConfig alias
1119 updateSymbolDescriptor("QtConfig", "qconfig.h", SyncScanner::SymbolDescriptor::Pragma);
1120 }
1121
1122 m_moduleMapContents.insert({outputDir + "/" + m_currentFilename, {}});
1123
1124 return true;
1125 }
1126
1127 void parseVersionScriptContent(const std::string buffer, ParsingResult &result)
1128 {
1129 // This regex looks for the symbols that needs to be placed into linker version script.
1130 static const std::regex VersionScriptSymbolRegex(
1131 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1132
1133 // This regex looks for the namespaces that needs to be placed into linker version script.
1134 static const std::regex VersionScriptNamespaceRegex(
1135 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1136
1137 // This regex filters the tailing colon from the symbol name.
1138 static const std::regex TrailingColonRegex("([\\w]+):$");
1139
1140 switch (m_versionScriptGeneratorState) {
1141 case Ignore:
1142 scannerDebug() << "line ignored: " << buffer << std::endl;
1143 m_versionScriptGeneratorState = Active;
1144 return;
1145 case Stopped:
1146 return;
1147 case IgnoreNext:
1148 m_versionScriptGeneratorState = Ignore;
1149 break;
1150 case Active:
1151 break;
1152 }
1153
1154 if (buffer.empty())
1155 return;
1156
1157 std::smatch match;
1158 std::string symbol;
1159 if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
1160 symbol = match[1].str();
1161 else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex))
1162 symbol = match[1].str();
1163
1164 if (std::regex_match(symbol, match, TrailingColonRegex))
1165 symbol = match[1].str();
1166
1167 // checkLineForSymbols(buffer, symbol);
1168 if (!symbol.empty() && symbol[symbol.size() - 1] != ';') {
1169 std::string relPath = m_currentFileInSourceDir
1170 ? std::filesystem::relative(m_currentFile, m_commandLineArgs->sourceDir())
1171 .string()
1172 : std::filesystem::relative(m_currentFile, m_commandLineArgs->binaryDir())
1173 .string();
1174
1175 std::string versionStringRecord = " *";
1176 size_t startPos = 0;
1177 size_t endPos = 0;
1178 while (endPos != std::string::npos) {
1179 endPos = symbol.find("::", startPos);
1180 size_t length = endPos != std::string::npos ? (endPos - startPos)
1181 : (symbol.size() - startPos);
1182 if (length > 0) {
1183 std::string symbolPart = symbol.substr(startPos, length);
1184 versionStringRecord += std::to_string(symbolPart.size());
1185 versionStringRecord += symbolPart;
1186 }
1187 startPos = endPos + 2;
1188 }
1189 versionStringRecord += "*;";
1190 if (versionStringRecord.size() < LinkerScriptCommentAlignment)
1191 versionStringRecord +=
1192 std::string(LinkerScriptCommentAlignment - versionStringRecord.size(), ' ');
1193 versionStringRecord += " # ";
1194 versionStringRecord += relPath;
1195 versionStringRecord += ":";
1196 versionStringRecord += std::to_string(m_currentFileLineNumber);
1197 versionStringRecord += "\n";
1198 result.versionScriptContent.push_back(versionStringRecord);
1199 }
1200 }
1201
1202 // The function parses 'headerFile' and collect artifacts that are used at generating step.
1203 // 'timeStamp' is saved in internal structures to compare it when generating files.
1204 // 'result' the function output value that stores the result of parsing.
1205 // 'skipChecks' checks that are not applicable for the header file.
1206 [[nodiscard]] bool parseHeader(const std::filesystem::path &headerFile,
1207 ParsingResult &result,
1208 unsigned int skipChecks)
1209 {
1210 if (m_commandLineArgs->showOnly())
1211 std::cout << headerFile << " [" << m_commandLineArgs->moduleName() << "]" << std::endl;
1212 // This regex checks if line contains a macro.
1213 static const std::regex MacroRegex("^\\s*#.*");
1214
1215 // The regex's bellow check line for known pragmas:
1216 //
1217 // - 'once' is not allowed in installed headers, so error out.
1218 //
1219 // - 'qt_sync_skip_header_check' avoid any header checks.
1220 //
1221 // - 'qt_sync_stop_processing' stops the header proccesing from a moment when pragma is
1222 // found. Important note: All the parsing artifacts were found before this point are
1223 // stored for further processing.
1224 //
1225 // - 'qt_sync_suspend_processing' pauses processing and skip lines inside a header until
1226 // 'qt_sync_resume_processing' is found. 'qt_sync_stop_processing' stops processing if
1227 // it's found before the 'qt_sync_resume_processing'.
1228 //
1229 // - 'qt_sync_resume_processing' resumes processing after 'qt_sync_suspend_processing'.
1230 //
1231 // - 'qt_class(<symbol>)' manually declares the 'symbol' that should be used to generate
1232 // the CaMeL case header alias.
1233 //
1234 // - 'qt_deprecates([module/]<deprecated header file>[,<major.minor>])' indicates that
1235 // this header file replaces the 'deprecated header file'. syncqt will create the
1236 // deprecated header file' with the special deprecation content. Pragma optionally
1237 // accepts the Qt version where file should be removed. If the current Qt version is
1238 // higher than the deprecation version, syncqt displays deprecation warning and skips
1239 // generating the deprecated header. If the module is specified and is different from
1240 // the one this header file belongs to, syncqt attempts to generate header files
1241 // for the specified module. Cross-module deprecation only works within the same repo.
1242 // See the 'generateDeprecatedHeaders' function for details.
1243 //
1244 // - 'qt_no_master_include' indicates that syncqt should avoid including this header
1245 // files into the module master header file.
1246 static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)");
1247 static const std::regex SkipHeaderCheckRegex("^#\\s*pragma qt_sync_skip_header_check$");
1248 static const std::regex StopProcessingRegex("^#\\s*pragma qt_sync_stop_processing$");
1249 static const std::regex SuspendProcessingRegex("^#\\s*pragma qt_sync_suspend_processing$");
1250 static const std::regex ResumeProcessingRegex("^#\\s*pragma qt_sync_resume_processing$");
1251 static const std::regex ExplixitClassPragmaRegex("^#\\s*pragma qt_class\\‍(([^\\‍)]+)\\‍)$");
1252 static const std::regex DeprecatesPragmaRegex("^#\\s*pragma qt_deprecates\\‍(([^\\‍)]+)\\‍)$");
1253 static const std::regex NoMasterIncludePragmaRegex("^#\\s*pragma qt_no_master_include$");
1254
1255 // This regex checks if header contains 'We mean it' disclaimer. All private headers should
1256 // contain them.
1257 static const std::string_view WeMeantItString("We mean it.");
1258
1259 // The regex's check if the content of header files is wrapped with the Qt namespace macros.
1260 static const std::regex BeginNamespaceRegex("^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1261 static const std::regex EndNamespaceRegex("^QT_END_NAMESPACE(_[A-Z_]+)?$");
1262
1263 // This regex checks if line contains the include macro of the following formats:
1264 // - #include <file>
1265 // - #include "file"
1266 // - # include <file>
1267 static const std::regex IncludeRegex("^#\\s*include\\s*[<\"](.+)[>\"]");
1268
1269 // This regex checks if line contains namespace definition.
1270 static const std::regex NamespaceRegex("\\s*namespace ([^ ]*)\\s+");
1271
1272 // This regex checks if line contains the Qt iterator declaration, that need to have
1273 // CaMel case header alias.
1274 static const std::regex DeclareIteratorRegex("^ *Q_DECLARE_\\w*ITERATOR\\‍((\\w+)\\‍);?$");
1275
1276 // This regex checks if header file contains the QT_REQUIRE_CONFIG call.
1277 // The macro argument is used to wrap an include of the header file inside the module master
1278 // header file with the '#if QT_CONFIG(<feature>)' guard.
1279 static const std::regex RequireConfigRegex("^ *QT_REQUIRE_CONFIG\\‍((\\w+)\\‍);?$");
1280
1281 // This regex looks for the ELFVERSION tag this is control key-word for the version script
1282 // content processing.
1283 // ELFVERSION tag accepts the following values:
1284 // - stop - stops the symbols lookup for a version script starting from this line.
1285 // - ignore-next - ignores the line followed by the current one.
1286 // - ignore - ignores the current line.
1287 static const std::regex ElfVersionTagRegex(".*ELFVERSION:(stop|ignore-next|ignore).*");
1288
1289 std::ifstream input(headerFile, std::ifstream::in);
1290 if (!input.is_open()) {
1291 std::cerr << "Unable to open " << headerFile << std::endl;
1292 return false;
1293 }
1294
1295 bool hasQtBeginNamespace = false;
1296 std::string qtBeginNamespace;
1297 std::string qtEndNamespace;
1298 bool hasWeMeantIt = false;
1299 bool isSuspended = false;
1300 bool isMultiLineComment = false;
1301 std::size_t bracesDepth = 0;
1302 std::size_t namespaceCount = 0;
1303 std::string namespaceString;
1304
1305 std::smatch match;
1306
1307 std::string buffer;
1308 std::string line;
1309 std::string tmpLine;
1310 std::size_t linesProcessed = 0;
1311 int faults = NoChecks;
1312
1313 const auto error = [&] () -> decltype(auto) {
1314 return std::cerr << ErrorMessagePreamble << m_currentFileString
1315 << ":" << m_currentFileLineNumber << " ";
1316 };
1317
1318 // Read file line by line
1319 while (std::getline(input, tmpLine)) {
1320 ++m_currentFileLineNumber;
1321 line.append(tmpLine);
1322 if (line.empty() || line.at(line.size() - 1) == '\\') {
1323 continue;
1324 }
1325 buffer.clear();
1326 buffer.reserve(line.size());
1327 // Optimize processing by looking for a special sequences such as:
1328 // - start-end of comments
1329 // - start-end of class/structures
1330 // And avoid processing of the the data inside these blocks.
1331 for (std::size_t i = 0; i < line.size(); ++i) {
1332 if (line[i] == '\r')
1333 continue;
1334 if (bracesDepth == namespaceCount) {
1335 if (line[i] == '/') {
1336 if ((i + 1) < line.size()) {
1337 if (line[i + 1] == '*') {
1338 isMultiLineComment = true;
1339 continue;
1340 } else if (line[i + 1] == '/') { // Single line comment
1341 if (!(skipChecks & WeMeantItChecks)
1342 && line.find(WeMeantItString) != std::string::npos) {
1343 hasWeMeantIt = true;
1344 continue;
1345 }
1346 if (m_versionScriptGeneratorState != Stopped
1347 && std::regex_match(line, match, ElfVersionTagRegex)) {
1348 if (match[1].str() == "ignore")
1349 m_versionScriptGeneratorState = Ignore;
1350 else if (match[1].str() == "ignore-next")
1351 m_versionScriptGeneratorState = IgnoreNext;
1352 else if (match[1].str() == "stop")
1353 m_versionScriptGeneratorState = Stopped;
1354 }
1355 break;
1356 }
1357 }
1358 } else if (line[i] == '*' && (i + 1) < line.size() && line[i + 1] == '/') {
1359 ++i;
1360 isMultiLineComment = false;
1361 continue;
1362 }
1363 }
1364
1365 if (isMultiLineComment) {
1366 if (!(skipChecks & WeMeantItChecks) &&
1367 line.find(WeMeantItString) != std::string::npos) {
1368 hasWeMeantIt = true;
1369 continue;
1370 }
1371 continue;
1372 }
1373
1374 if (line[i] == '{') {
1375 if (std::regex_match(buffer, match, NamespaceRegex)) {
1376 ++namespaceCount;
1377 namespaceString += "::";
1378 namespaceString += match[1].str();
1379 }
1380 ++bracesDepth;
1381 continue;
1382 } else if (line[i] == '}') {
1383 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1384 namespaceString.resize(namespaceString.rfind("::"));
1385 --namespaceCount;
1386 }
1387 --bracesDepth;
1388 } else if (bracesDepth == namespaceCount) {
1389 buffer += line[i];
1390 }
1391 }
1392 line.clear();
1393
1394 scannerDebug() << m_currentFilename << ": " << buffer << std::endl;
1395
1396 if (m_currentFileType & PrivateHeader) {
1397 parseVersionScriptContent(buffer, result);
1398 }
1399
1400 if (buffer.empty())
1401 continue;
1402
1403 ++linesProcessed;
1404
1405 bool skipSymbols =
1406 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1407 || (m_currentFileType & SsgHeader) || (m_currentFileType & SpiHeader);
1408
1409 // Parse pragmas
1410 if (std::regex_match(buffer, MacroRegex)) {
1411 if (std::regex_match(buffer, SkipHeaderCheckRegex)) {
1412 skipChecks = AllChecks;
1413 faults = NoChecks;
1414 } else if (std::regex_match(buffer, StopProcessingRegex)) {
1415 if (skipChecks == AllChecks)
1416 m_headerCheckExceptions.push_back(m_currentFileString);
1417 return true;
1418 } else if (std::regex_match(buffer, SuspendProcessingRegex)) {
1419 isSuspended = true;
1420 } else if (std::regex_match(buffer, ResumeProcessingRegex)) {
1421 isSuspended = false;
1422 } else if (std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
1423 if (!skipSymbols) {
1424 updateSymbolDescriptor(match[1].str(), m_currentFilename,
1425 SymbolDescriptor::Pragma);
1426 } else {
1427 // TODO: warn about skipping symbols that are defined explicitly
1428 }
1429 } else if (std::regex_match(buffer, NoMasterIncludePragmaRegex)) {
1430 result.masterInclude = false;
1431 } else if (std::regex_match(buffer, match, DeprecatesPragmaRegex)) {
1432 m_deprecatedHeaders[match[1].str()] =
1433 m_commandLineArgs->moduleName() + '/' + m_currentFilename;
1434 } else if (std::regex_match(buffer, OnceRegex)) {
1435 if (!(skipChecks & PragmaOnceChecks)) {
1436 faults |= PragmaOnceChecks;
1437 error() << "\"#pragma once\" is not allowed in installed header files: "
1438 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1439 << std::endl;
1440 }
1441 } else if (std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
1442 if (!(skipChecks & IncludeChecks)) {
1443 std::string includedHeader = match[1].str();
1444 if (!(skipChecks & PrivateHeaderChecks)
1445 && isHeaderPrivate(std::filesystem::path(includedHeader)
1446 .filename()
1447 .generic_string())) {
1448 faults |= PrivateHeaderChecks;
1449 error() << "includes private header " << includedHeader << std::endl;
1450 }
1451 for (const auto &module : m_commandLineArgs->knownModules()) {
1452 std::string suggestedHeader = "Qt" + module + '/' + includedHeader;
1453 const std::string suggestedHeaderReversePath = "/../" + suggestedHeader;
1454 if (std::filesystem::exists(m_commandLineArgs->includeDir()
1455 + suggestedHeaderReversePath)
1456 || std::filesystem::exists(m_commandLineArgs->installIncludeDir()
1457 + '/' + suggestedHeader)) {
1458 faults |= IncludeChecks;
1459 std::cerr << m_warningMessagePreamble << m_currentFileString
1460 << ":" << m_currentFileLineNumber
1461 << " includes " << includedHeader
1462 << " when it should include "
1463 << suggestedHeader << std::endl;
1464 }
1465 }
1466 }
1467 }
1468 continue;
1469 }
1470
1471 // Logic below this line is affected by the 'qt_sync_suspend_processing' and
1472 // 'qt_sync_resume_processing' pragmas.
1473 if (isSuspended)
1474 continue;
1475
1476 // Look for the symbols in header file.
1477 if (!skipSymbols) {
1478 std::string symbol;
1479 if (checkLineForSymbols(buffer, symbol)) {
1480 if (namespaceCount == 0
1481 || std::regex_match(namespaceString,
1482 m_commandLineArgs->publicNamespaceRegex())) {
1483 updateSymbolDescriptor(symbol, m_currentFilename,
1484 SymbolDescriptor::Declaration);
1485 }
1486 continue;
1487 } else if (std::regex_match(buffer, match, DeclareIteratorRegex)) {
1488 std::string iteratorSymbol = match[1].str() + "Iterator";
1489 updateSymbolDescriptor(std::string("Q") + iteratorSymbol, m_currentFilename,
1490 SymbolDescriptor::Declaration);
1491 updateSymbolDescriptor(std::string("QMutable") + iteratorSymbol,
1492 m_currentFilename, SymbolDescriptor::Declaration);
1493 continue;
1494 } else if (std::regex_match(buffer, match, RequireConfigRegex)) {
1495 result.requireConfig = match[1].str();
1496 continue;
1497 }
1498 }
1499
1500 // Check for both QT_BEGIN_NAMESPACE and QT_END_NAMESPACE macros are present in the
1501 // header file.
1502 if (!(skipChecks & NamespaceChecks)) {
1503 if (std::regex_match(buffer, match, BeginNamespaceRegex)) {
1504 qtBeginNamespace = match[1].str();
1505 hasQtBeginNamespace = true;
1506 } else if (std::regex_match(buffer, match, EndNamespaceRegex)) {
1507 qtEndNamespace = match[1].str();
1508 }
1509 }
1510 }
1511 input.close();
1512
1513 // Error out if namespace checks are failed.
1514 if (!(skipChecks & NamespaceChecks)) {
1515 if (hasQtBeginNamespace) {
1516 if (qtBeginNamespace != qtEndNamespace) {
1517 faults |= NamespaceChecks;
1518 std::cerr << m_warningMessagePreamble << m_currentFileString
1519 << " the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1520 << " doesn't match the end namespace macro QT_END_NAMESPACE"
1521 << qtEndNamespace << std::endl;
1522 }
1523 } else {
1524 faults |= NamespaceChecks;
1525 std::cerr << m_warningMessagePreamble << m_currentFileString
1526 << " does not include QT_BEGIN_NAMESPACE" << std::endl;
1527 }
1528 }
1529
1530 if (!(skipChecks & WeMeantItChecks) && !hasWeMeantIt) {
1531 faults |= WeMeantItChecks;
1532 std::cerr << m_warningMessagePreamble << m_currentFileString
1533 << " does not have the \"We mean it.\" warning"
1534 << std::endl;
1535 }
1536
1537 scannerDebug() << "linesTotal: " << m_currentFileLineNumber
1538 << " linesProcessed: " << linesProcessed << std::endl;
1539
1540 if (skipChecks == AllChecks)
1541 m_headerCheckExceptions.push_back(m_currentFileString);
1542
1543 // Exit with an error if any of critical checks are present.
1544 return !(faults & m_criticalChecks);
1545 }
1546
1547 // The function checks if line contains the symbol that needs to have a CaMeL-style alias.
1548 [[nodiscard]] bool checkLineForSymbols(const std::string &line, std::string &symbol)
1549 {
1550 scannerDebug() << "checkLineForSymbols: " << line << std::endl;
1551
1552 // This regex checks if line contains class or structure declaration like:
1553 // - <class|stuct> StructName
1554 // - template <> class ClassName
1555 // - class ClassName : [public|protected|private] BaseClassName
1556 // - class ClassName [QT_TEXT_STREAM_FINAL|Q_DECL_FINAL|final|sealed]
1557 // And possible combinations of the above variants.
1558 static const std::regex ClassRegex(
1559 "^ *(template *<.*> *)?(class|struct +)([^<>:]*\\s+)?" // Preceding part
1560 "((?!Q[A-Z_0-9]*_FINAL|final|sealed)Q[a-zA-Z0-9_]+)" // Actual symbol
1561 "(\\s+Q[A-Z_0-9]*_FINAL|\\s+final|\\s+sealed)?\\s*(:|$).*"); // Trailing part
1562
1563 // This regex checks if line contains function pointer typedef declaration like:
1564 // - typedef void (* QFunctionPointerType)(int, char);
1565 static const std::regex FunctionPointerRegex(
1566 "^ *typedef *.*\\‍(\\*(Q[^\\‍)]+)\\‍)\\‍(.*\\‍); *");
1567
1568 // This regex checks if line contains class or structure typedef declaration like:
1569 // - typedef AnySymbol<char> QAnySymbolType;
1570 static const std::regex TypedefRegex("^ *typedef\\s+(.*)\\s+(Q\\w+); *$");
1571
1572 std::smatch match;
1573 if (std::regex_match(line, match, FunctionPointerRegex)) {
1574 symbol = match[1].str();
1575 } else if (std::regex_match(line, match, TypedefRegex)) {
1576 symbol = match[2].str();
1577 } else if (std::regex_match(line, match, ClassRegex)) {
1578 symbol = match[4].str();
1579 } else {
1580 return false;
1581 }
1582 return !symbol.empty();
1583 }
1584
1585 [[nodiscard]] bool isHeaderQpa(const std::string &headerFileName)
1586 {
1587 return std::regex_match(headerFileName, m_commandLineArgs->qpaHeadersRegex());
1588 }
1589
1590 [[nodiscard]] bool isHeaderRhi(const std::string &headerFileName)
1591 {
1592 return std::regex_match(headerFileName, m_commandLineArgs->rhiHeadersRegex());
1593 }
1594
1595 [[nodiscard]] bool isHeaderSsg(const std::string &headerFileName)
1596 {
1597 return std::regex_match(headerFileName, m_commandLineArgs->ssgHeadersRegex());
1598 }
1599
1600 [[nodiscard]] bool isHeaderSpi(const std::string &headerFileName)
1601 {
1602 return std::regex_match(headerFileName, m_commandLineArgs->spiHeadersRegex());
1603 }
1604
1605 [[nodiscard]] bool isHeaderPrivate(const std::string &headerFile)
1606 {
1607 return std::regex_match(headerFile, m_commandLineArgs->privateHeadersRegex());
1608 }
1609
1610 [[nodiscard]] bool isHeaderPCH(const std::string &headerFilename)
1611 {
1612 static const std::string pchSuffix("_pch.h");
1613 return headerFilename.find(pchSuffix, headerFilename.size() - pchSuffix.size())
1614 != std::string::npos;
1615 }
1616
1617 [[nodiscard]] bool isHeaderImpl(const std::string &headerFilename) const
1618 {
1619 static const std::string implSuffix("_impl.h");
1620 return headerFilename.find(implSuffix, headerFilename.size() - implSuffix.size())
1621 != std::string::npos;
1622 }
1623
1624 [[nodiscard]] bool isHeaderDeprecated(const std::string &headerFilename) const
1625 {
1626 static const std::string deprecatedSuffix("_deprecated.h");
1627 return headerFilename.find(deprecatedSuffix,
1628 headerFilename.size() - deprecatedSuffix.size())
1629 != std::string::npos;
1630 }
1631
1632 [[nodiscard]] bool isHeader(const std::filesystem::path &path)
1633 {
1634 return path.extension().string() == ".h";
1635 }
1636
1637 [[nodiscard]] bool isDocFileHeuristic(const std::string &headerFilePath)
1638 {
1639 return headerFilePath.find("/doc/") != std::string::npos;
1640 }
1641
1642 [[nodiscard]] bool isHeaderGenerated(const std::string &header)
1643 {
1644 return m_commandLineArgs->generatedHeaders().find(header)
1645 != m_commandLineArgs->generatedHeaders().end();
1646 }
1647
1648 [[nodiscard]] bool generateForwardingHeader(const std::string &outputFilePath,
1649 const std::string &aliasedFilePath,
1650 bool useIncludeNext = false);
1651
1653 const std::string &outputFilePath, const std::string &aliasedFilePath,
1654 const FileStamp &originalStamp = FileStamp::clock::now());
1655
1656 [[nodiscard]] bool generateModuleMapFile();
1657
1658 bool writeIfDifferent(const std::string &outputFile, const std::string &buffer) const;
1659
1660 [[nodiscard]] bool generateMasterHeader()
1661 {
1662 if (m_masterHeaderContents.empty())
1663 return true;
1664
1665 std::string outputFile =
1666 m_commandLineArgs->includeDir() + '/' + m_commandLineArgs->moduleName();
1667
1668 std::string moduleUpper = utils::asciiToUpper(m_commandLineArgs->moduleName());
1669 std::stringstream buffer;
1670 buffer << "#ifndef QT_" << moduleUpper << "_MODULE_H\n"
1671 << "#define QT_" << moduleUpper << "_MODULE_H\n"
1672 << "#include <" << m_commandLineArgs->moduleName() << "/"
1673 << m_commandLineArgs->moduleName() << "Depends>\n";
1674 for (const auto &headerContents : m_masterHeaderContents) {
1675 if (!headerContents.second.empty()) {
1676 buffer << "#if QT_CONFIG(" << headerContents.second << ")\n"
1677 << "#include <" << m_commandLineArgs->moduleName() << "/"
1678 << headerContents.first << ">\n"
1679 << "#endif\n";
1680 } else {
1681 buffer << "#include <" << m_commandLineArgs->moduleName() << "/"
1682 << headerContents.first << ">\n";
1683 }
1684 }
1685 buffer << "#endif\n";
1686
1687 m_producedHeaders.insert(m_commandLineArgs->moduleName());
1688 m_moduleMapContents.insert({outputFile, {}});
1689 return writeIfDifferent(outputFile, buffer.str());
1690 }
1691
1692 [[nodiscard]] bool generateVersionHeader(const std::string &outputFile)
1693 {
1694 std::string moduleNameUpper = utils::asciiToUpper( m_commandLineArgs->moduleName());
1695
1696 std::stringstream buffer;
1697 buffer << "/* This file was generated by syncqt. */\n"
1698 << "#ifndef QT_" << moduleNameUpper << "_VERSION_H\n"
1699 << "#define QT_" << moduleNameUpper << "_VERSION_H\n\n"
1700 << "#define " << moduleNameUpper << "_VERSION_STR \"" << QT_VERSION_STR << "\"\n\n"
1701 << "#define " << moduleNameUpper << "_VERSION "
1702 << "0x0" << QT_VERSION_MAJOR << "0" << QT_VERSION_MINOR << "0" << QT_VERSION_PATCH
1703 << "\n\n"
1704 << "#endif // QT_" << moduleNameUpper << "_VERSION_H\n";
1705
1706 return writeIfDifferent(outputFile, buffer.str());
1707 }
1708
1709 [[nodiscard]] bool generateDeprecatedHeaders()
1710 {
1711 static std::regex cIdentifierSymbolsRegex("[^a-zA-Z0-9_]");
1712 const std::string guard_base = "DEPRECATED_HEADER_" + m_commandLineArgs->moduleName();
1713 bool result = true;
1714 for (auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) {
1715 const std::string &descriptor = it->first;
1716 const std::string &replacement = it->second;
1717
1718 const auto separatorPos = descriptor.find(',');
1719 std::string headerPath = descriptor.substr(0, separatorPos);
1720 std::string versionDisclaimer;
1721 if (separatorPos != std::string::npos) {
1722 std::string version = descriptor.substr(separatorPos + 1);
1723 versionDisclaimer = " and will be removed in Qt " + version;
1724 int minor = 0;
1725 int major = 0;
1726 if (!utils::parseVersion(version, major, minor)) {
1727 std::cerr << ErrorMessagePreamble
1728 << "Invalid version format specified for the deprecated header file "
1729 << headerPath << ": '" << version
1730 << "'. Expected format: 'major.minor'.\n";
1731 result = false;
1732 continue;
1733 }
1734
1735 if (QT_VERSION_MAJOR > major
1736 || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) {
1737 std::cerr << WarningMessagePreamble << headerPath
1738 << " is marked as deprecated and will not be generated in Qt "
1739 << QT_VERSION_STR
1740 << ". The respective qt_deprecates pragma needs to be removed.\n";
1741 continue;
1742 }
1743 }
1744
1745 const auto moduleSeparatorPos = headerPath.find('/');
1746 std::string headerName = moduleSeparatorPos != std::string::npos
1747 ? headerPath.substr(moduleSeparatorPos + 1)
1748 : headerPath;
1749 const std::string moduleName = moduleSeparatorPos != std::string::npos
1750 ? headerPath.substr(0, moduleSeparatorPos)
1751 : m_commandLineArgs->moduleName();
1752
1753 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs->moduleName();
1754
1755 std::string qualifiedHeaderName =
1756 std::regex_replace(headerName, cIdentifierSymbolsRegex, "_");
1757 std::string guard = guard_base + "_" + qualifiedHeaderName;
1758 std::string warningText = "Header <" + moduleName + "/" + headerName + "> is deprecated"
1759 + versionDisclaimer + ". Please include <" + replacement + "> instead.";
1760 std::stringstream buffer;
1761 buffer << "#ifndef " << guard << "\n"
1762 << "#define " << guard << "\n"
1763 << "#if defined(__GNUC__)\n"
1764 << "# warning " << warningText << "\n"
1765 << "#elif defined(_MSC_VER)\n"
1766 << "# pragma message (\"" << warningText << "\")\n"
1767 << "#endif\n"
1768 << "#include <" << replacement << ">\n"
1769 << "#endif\n";
1770
1771 const std::string outputDir = isCrossModuleDeprecation
1772 ? m_commandLineArgs->includeDir() + "/../" + moduleName
1773 : m_commandLineArgs->includeDir();
1774 writeIfDifferent(outputDir + '/' + headerName, buffer.str());
1775
1776 // Add header file to staging installation directory for cross-module deprecation case.
1777 if (isCrossModuleDeprecation) {
1778 const std::string stagingDir = outputDir + "/.syncqt_staging/";
1779 writeIfDifferent(stagingDir + headerName, buffer.str());
1780 }
1781 m_producedHeaders.insert(headerName);
1782 }
1783 return result;
1784 }
1785
1787 {
1788 std::stringstream buffer;
1789 for (const auto &header : m_headerCheckExceptions)
1790 buffer << header << ";";
1791 return writeIfDifferent(m_commandLineArgs->binaryDir() + '/'
1792 + m_commandLineArgs->moduleName()
1793 + "_header_check_exceptions",
1794 buffer.str());
1795 }
1796
1797 [[nodiscard]] bool generateLinkerVersionScript()
1798 {
1799 std::stringstream buffer;
1800 for (const auto &content : m_versionScriptContents)
1801 buffer << content;
1802 return writeIfDifferent(m_commandLineArgs->versionScriptFile(), buffer.str());
1803 }
1804
1805 bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) noexcept;
1806 void updateSymbolDescriptor(const std::string &symbol, const std::string &file,
1807 SymbolDescriptor::SourceType type);
1808};
1809
1810// The function updates information about the symbol:
1811// - The path and modification time of the file where the symbol was found.
1812// - The source of finding
1813// Also displays a short info about a symbol in show only mode.
1814void SyncScanner::updateSymbolDescriptor(const std::string &symbol, const std::string &file,
1815 SymbolDescriptor::SourceType type)
1816{
1817 if (m_commandLineArgs->showOnly() || m_commandLineArgs->debug())
1818 std::cout << " SYMBOL: " << symbol << std::endl;
1819 m_symbols[symbol].update(file, type);
1820}
1821
1822[[nodiscard]] std::filesystem::path
1823SyncScanner::makeHeaderAbsolute(const std::string &filename) const
1824{
1825 if (std::filesystem::path(filename).is_relative())
1826 return utils::normilizedPath(m_commandLineArgs->sourceDir() + '/' + filename);
1827
1828 return utils::normilizedPath(filename);
1829}
1830
1831bool SyncScanner::updateOrCopy(const std::filesystem::path &src,
1832 const std::filesystem::path &dst) noexcept
1833{
1834 if (m_commandLineArgs->showOnly())
1835 return true;
1836
1837 if (src == dst) {
1838 std::cout << "Source and destination paths are same when copying " << src.string()
1839 << ". Skipping." << std::endl;
1840 return true;
1841 }
1842
1843 std::error_code ec;
1844 std::filesystem::copy(src, dst, std::filesystem::copy_options::update_existing, ec);
1845 if (ec) {
1846 ec.clear();
1847 std::filesystem::remove(dst, ec);
1848 if (ec) {
1849 // On some file systems(e.g. vboxfs) the std::filesystem::copy doesn't support
1850 // std::filesystem::copy_options::overwrite_existing remove file first and then copy.
1851 std::cerr << "Unable to remove file: " << src << " to " << dst << " error: ("
1852 << ec.value() << ")" << ec.message() << std::endl;
1853 return false;
1854 }
1855
1856 std::filesystem::copy(src, dst, std::filesystem::copy_options::overwrite_existing, ec);
1857 if (ec) {
1858 std::cerr << "Unable to copy file: " << src << " to " << dst << " error: ("
1859 << ec.value() << ")" << ec.message() << std::endl;
1860 return false;
1861 }
1862 }
1863 return true;
1864}
1865
1866// The function generates a forwarding header at outputFilePath that includes aliasedFilePath
1867// from the current module (that is, <Module/aliasedFilePath>).
1868//
1869// With useIncludeNext the forwarder uses #include_next instead of a plain #include. This is
1870// needed when the forwarder itself is found under the same spelling as it forwards to,
1871// as is the case for the lowercase framework forwarders that re-expose a framework's own
1872// headers via a plain include path: a plain #include would resolve back to the forwarder
1873// itself and recurse, whereas #include_next continues the include search past the forwarder
1874// into the framework.
1875bool SyncScanner::generateForwardingHeader(const std::string &outputFilePath,
1876 const std::string &aliasedFilePath,
1877 bool useIncludeNext)
1878{
1879 if (m_commandLineArgs->showOnly())
1880 return true;
1881
1882 // Safety check: aliasedFilePath should not be empty
1883 if (aliasedFilePath.empty()) {
1884 std::cerr << "ERROR: Empty aliasedFilePath for " << outputFilePath << std::endl;
1885 return false;
1886 }
1887
1888 std::string buffer;
1889 if (useIncludeNext)
1890 buffer += "#include_next <";
1891 else
1892 buffer += "#include <";
1893
1894 buffer += m_commandLineArgs->moduleName() + "/";
1895 buffer += aliasedFilePath;
1896 buffer += "> // IWYU pragma: export\n";
1897
1898 return writeIfDifferent(outputFilePath, buffer);
1899}
1900
1901// The function generates aliases for files in source tree. Since the content of these aliases is
1902// always same, it's ok to check only timestamp and touch files in case if stamp of original is
1903// newer than the timestamp of an alias.
1905 const std::string &aliasedFilePath,
1906 const FileStamp &originalStamp)
1907{
1908 if (m_commandLineArgs->showOnly())
1909 return true;
1910
1911 std::filesystem::path aliased(aliasedFilePath);
1912 std::filesystem::path includeDir(m_commandLineArgs->includeDir());
1913
1914 // Check if paths have the same root (drive on Windows).
1915 // If they don't, the alias cannot be inside includeDir, so use absolute path.
1916 bool sameRoot = !aliased.is_absolute() || includeDir.root_name() == aliased.root_name();
1917
1918 auto relativePath = sameRoot ? std::filesystem::relative(aliased, includeDir).generic_string()
1919 : std::string();
1920 bool aliasIsInsideIncludeDir = sameRoot && relativePath.find("../") != 0;
1921
1922 if (std::filesystem::exists({ outputFilePath })
1923 && std::filesystem::last_write_time({ outputFilePath }) >= originalStamp) {
1924 return true;
1925 }
1926 scannerDebug() << "Rewrite " << outputFilePath << std::endl;
1927
1928 std::ofstream ofs;
1929 ofs.open(outputFilePath, std::ofstream::out | std::ofstream::trunc);
1930 if (!ofs.is_open()) {
1931 std::cerr << "Unable to write header file alias: " << outputFilePath << std::endl;
1932 return false;
1933 }
1934
1935 ofs << "#include ";
1936 if (aliasIsInsideIncludeDir)
1937 ofs << "<" << m_commandLineArgs->moduleName() + "/" << relativePath << ">";
1938 else
1939 ofs << "\"" << aliasedFilePath << "\"";
1940 ofs << " // IWYU pragma: export\n";
1941 ofs.close();
1942 return true;
1943}
1944
1946{
1947 std::string content;
1948 for (const auto& [header, aliasHeader] : m_moduleMapContents) {
1949 auto relativePath = std::filesystem::relative(header, m_commandLineArgs->includeDir());
1950 const auto &nonCamelCaseHeader = aliasHeader.empty() ? header : aliasHeader;
1951 resetCurrentFileInfoData(nonCamelCaseHeader);
1952 const bool isPrivate = m_currentFileType & PrivateHeader;
1953 const bool isDeprecated =
1954 m_deprecatedHeaders.find(nonCamelCaseHeader) != m_deprecatedHeaders.end()
1955 || isHeaderDeprecated(nonCamelCaseHeader);
1956
1957 content += " ";
1958 if (isPrivate || isDeprecated)
1959 content += "exclude ";
1960 if (isHeaderImpl(header))
1961 content += "textual ";
1962
1963 content += "header \"";
1964 content += relativePath.string();
1965 content += "\"\n";
1966 }
1967
1968 std::filesystem::path moduleMapsTemplatePath =
1969 m_commandLineArgs->binaryDir() + "/" +
1970 m_commandLineArgs->moduleName() + "." + "module.modulemap.in";
1971 if (!std::filesystem::exists(moduleMapsTemplatePath)) {
1972 std::cerr << "Unable to read the modulemaps template file: " << moduleMapsTemplatePath
1973 << std::endl;
1974 return false;
1975 }
1976
1977 std::ifstream input(moduleMapsTemplatePath, std::ifstream::in);
1978 if (!input.is_open()) {
1979 std::cerr << "Unable to open " << moduleMapsTemplatePath << std::endl;
1980 return false;
1981 }
1982
1983 std::string output;
1984 std::string tmpLine;
1985 while (std::getline(input, tmpLine)) {
1986 if (tmpLine == "@SYNCQT_GENERATED_HEADER_LIST@") {
1987 output += content;
1988 } else {
1989 output += tmpLine;
1990 output += "\n";
1991 }
1992 }
1993
1994 std::string moduleMapsPath = m_commandLineArgs->moduleMapFile();
1995 return writeIfDifferent(moduleMapsPath, output);
1996}
1997
1998
1999bool SyncScanner::writeIfDifferent(const std::string &outputFile, const std::string &buffer) const
2000{
2001 if (m_commandLineArgs->showOnly())
2002 return true;
2003
2004 static const std::streamsize bufferSize = 1025;
2005 bool differs = false;
2006 std::filesystem::path outputFilePath(outputFile);
2007
2008 std::string outputDirectory = outputFilePath.parent_path().string();
2009
2010 if (!utils::createDirectories(outputDirectory, "Unable to create output directory"))
2011 return false;
2012
2013 auto expectedSize = buffer.size();
2014#ifdef _WINDOWS
2015 // File on disk has \r\n instead of just \n
2016 expectedSize += std::count(buffer.begin(), buffer.end(), '\n');
2017#endif
2018
2019 if (std::filesystem::exists(outputFilePath)
2020 && expectedSize == std::filesystem::file_size(outputFilePath)) {
2021 char rdBuffer[bufferSize];
2022 memset(rdBuffer, 0, bufferSize);
2023
2024 std::ifstream ifs(outputFile, std::fstream::in);
2025 if (!ifs.is_open()) {
2026 std::cerr << "Unable to open " << outputFile << " for comparison." << std::endl;
2027 return false;
2028 }
2029 std::streamsize currentPos = 0;
2030
2031 std::size_t bytesRead = 0;
2032 do {
2033 ifs.read(rdBuffer, bufferSize - 1); // Read by 1K
2034 bytesRead = ifs.gcount();
2035 if (buffer.compare(currentPos, bytesRead, rdBuffer) != 0) {
2036 differs = true;
2037 break;
2038 }
2039 currentPos += bytesRead;
2040 memset(rdBuffer, 0, bufferSize);
2041 } while (bytesRead > 0);
2042
2043 ifs.close();
2044 } else {
2045 differs = true;
2046 }
2047
2048 scannerDebug() << "Update: " << outputFile << " " << differs << std::endl;
2049 if (differs) {
2050 std::ofstream ofs;
2051 ofs.open(outputFilePath, std::fstream::out | std::ofstream::trunc);
2052 if (!ofs.is_open()) {
2053 std::cerr << "Unable to write header content to " << outputFilePath << std::endl;
2054 return false;
2055 }
2056 ofs << buffer;
2057
2058 ofs.close();
2059 }
2060 return true;
2061}
2062
2063int main(int argc, char *argv[])
2064{
2065 CommandLineOptions options(argc, argv);
2066 if (!options.isValid())
2067 return InvalidArguments;
2068
2069 if (options.printHelpOnly()) {
2070 options.printHelp();
2071 return NoError;
2072 }
2073
2074 SyncScanner scanner = SyncScanner(&options);
2075 return scanner.sync();
2076}
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:1999
bool generateVersionHeader(const std::string &outputFile)
Definition main.cpp:1692
bool generateLinkerVersionScript()
Definition main.cpp:1797
bool isHeaderQpa(const std::string &headerFileName)
Definition main.cpp:1585
void parseVersionScriptContent(const std::string buffer, ParsingResult &result)
Definition main.cpp:1127
bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) noexcept
Definition main.cpp:1831
bool checkLineForSymbols(const std::string &line, std::string &symbol)
Definition main.cpp:1548
bool isHeaderDeprecated(const std::string &headerFilename) const
Definition main.cpp:1624
bool generateForwardingHeader(const std::string &outputFilePath, const std::string &aliasedFilePath, bool useIncludeNext=false)
Definition main.cpp:1875
void updateSymbolDescriptor(const std::string &symbol, const std::string &file, SymbolDescriptor::SourceType type)
Definition main.cpp:1814
bool isHeaderSpi(const std::string &headerFileName)
Definition main.cpp:1600
bool isHeaderGenerated(const std::string &header)
Definition main.cpp:1642
std::filesystem::path makeHeaderAbsolute(const std::string &filename) const
Definition main.cpp:1823
bool generateModuleMapFile()
Definition main.cpp:1945
bool generateMasterHeader()
Definition main.cpp:1660
bool isHeaderImpl(const std::string &headerFilename) const
Definition main.cpp:1617
bool generateAliasedHeaderFileIfTimestampChanged(const std::string &outputFilePath, const std::string &aliasedFilePath, const FileStamp &originalStamp=FileStamp::clock::now())
Definition main.cpp:1904
ErrorCodes sync()
Definition main.cpp:682
bool isHeader(const std::filesystem::path &path)
Definition main.cpp:1632
bool generateDeprecatedHeaders()
Definition main.cpp:1709
bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory, bool skipCleanup=false)
Definition main.cpp:852
bool isHeaderRhi(const std::string &headerFileName)
Definition main.cpp:1590
bool isHeaderSsg(const std::string &headerFileName)
Definition main.cpp:1595
bool parseHeader(const std::filesystem::path &headerFile, ParsingResult &result, unsigned int skipChecks)
Definition main.cpp:1206
bool isDocFileHeuristic(const std::string &headerFilePath)
Definition main.cpp:1637
bool isHeaderPCH(const std::string &headerFilename)
Definition main.cpp:1610
bool processHeader(const std::filesystem::path &headerFile)
Definition main.cpp:930
SyncScanner(CommandLineOptions *commandLineArgs)
Definition main.cpp:667
bool generateHeaderCheckExceptions()
Definition main.cpp:1786
void resetCurrentFileInfoData(const std::filesystem::path &headerFile)
Definition main.cpp:899
bool isHeaderPrivate(const std::string &headerFile)
Definition main.cpp:1605
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]