25#include <unordered_map>
68 return amatch[1].str().empty()
69 || (!bmatch[1].str().empty() && amatch[1].str() < bmatch[1].str());
82 std::transform(
s.begin(),
s.end(),
s.begin(),
83 [](
unsigned char c) { return (c >=
'A' && c <=
'Z') ? c | 0x20 : c; });
89 std::transform(
s.begin(),
s.end(),
s.begin(),
90 [](
unsigned char c) { return (c >=
'a' && c <=
'z') ? c & 0xdf : c; });
94bool parseVersion(
const std::string &version,
int &major,
int &minor)
96 const size_t separatorPos = version.find(
'.');
97 if (separatorPos == std::string::npos || separatorPos == (version.size() - 1)
103 major = std::stoi(version.substr(0, separatorPos), &
pos);
104 if (
pos != separatorPos)
107 const size_t nextPart = separatorPos + 1;
109 minor = std::stoi(version.substr(nextPart), &
pos);
110 if (
pos != (version.size() - nextPart))
112 }
catch (
const std::invalid_argument &) {
114 }
catch (
const std::out_of_range &) {
123 struct :
public std::streambuf
125 int overflow(
int c)
override {
return c; }
134 std::cerr <<
"Internal error. Please create bugreport at https://bugreports.qt.io "
135 "using 'Build tools: Other component.'"
141 std::cerr << errorMsg <<
": " << fserr.path1() <<
".\n"
142 << fserr.what() <<
"(" << fserr.code().value() <<
")" << std::endl;
148 auto result = std::filesystem::path(std::filesystem::weakly_canonical(
path).generic_string());
150 }
catch (
const std::filesystem::filesystem_error &fserr) {
160 if (!std::filesystem::exists(
path)) {
163 std::filesystem::create_directories(
path);
168 }
catch (
const std::filesystem::filesystem_error &fserr) {
170 std::cerr << errorMsg <<
": " <<
path <<
".\n"
171 << fserr.code().message() <<
"(" << fserr.code().value() <<
"):" << fserr.what()
184 struct CommandLineOption
186 CommandLineOption(T *_value,
bool _isOptional =
false)
187 :
value(_value), isOptional(_isOptional)
200 const std::string &
moduleName()
const {
return m_moduleName; }
202 const std::string &
sourceDir()
const {
return m_sourceDir; }
204 const std::string &
binaryDir()
const {
return m_binaryDir; }
206 const std::string &
includeDir()
const {
return m_includeDir; }
216 const std::string &
stagingDir()
const {
return m_stagingDir; }
220 const std::set<std::string> &
knownModules()
const {
return m_knownModules; }
232 const std::set<std::string> &
headers()
const {
return m_headers; }
244 bool debug()
const {
return m_debug; }
246 bool copy()
const {
return m_copy; }
256 std::cout <<
"Usage: syncqt -sourceDir <dir> -binaryDir <dir> -module <module name>"
257 " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir> -rhiIncludeDir <dir> -ssgIncludeDir <dir>"
258 " -stagingDir <dir> <-headers <header list>|-all> [-debug]"
259 " [-versionScript <path>] [-qpaHeadersFilter <regex>] [-rhiHeadersFilter <regex>]"
260 " [-knownModules <module1> <module2>... <moduleN>]"
261 " [-nonQt] [-internal] [-copy]\n"
263 "Mandatory arguments:\n"
264 " -module Module name.\n"
265 " -headers List of header files.\n"
266 " -all In 'all' mode syncqt scans source\n"
267 " directory for public qt headers and\n"
268 " artifacts not considering CMake source\n"
269 " tree. The main use cases are the \n"
270 " generating of documentation and creating\n"
271 " API review changes.\n"
272 " -sourceDir Module source directory.\n"
273 " -binaryDir Module build directory.\n"
274 " -includeDir Module include directory where the\n"
275 " generated header files will be located.\n"
276 " -privateIncludeDir Module include directory for the\n"
277 " generated private header files.\n"
278 " -qpaIncludeDir Module include directory for the \n"
279 " generated QPA header files.\n"
280 " -rhiIncludeDir Module include directory for the \n"
281 " generated RHI header files.\n"
282 " -ssgIncludeDir Module include directory for the \n"
283 " generated SSG header files.\n"
284 " -stagingDir Temporary staging directory to collect\n"
285 " artifacts that need to be installed.\n"
286 " -knownModules list of known modules. syncqt uses the\n"
287 " list to check the #include macros\n"
289 "Optional arguments:\n"
290 " -internal Indicates that the module is internal.\n"
291 " -nonQt Indicates that the module is not a Qt\n"
293 " -privateHeadersFilter Regex that filters private header files\n"
294 " from the list of 'headers'.\n"
295 " -qpaHeadersFilter Regex that filters qpa header files from.\n"
296 " the list of 'headers'.\n"
297 " -rhiHeadersFilter Regex that filters rhi header files from.\n"
298 " the list of 'headers'.\n"
299 " -ssgHeadersFilter Regex that filters ssg files from.\n"
300 " the list of 'headers'.\n"
301 " -publicNamespaceFilter Symbols that are in the specified\n"
303 " are treated as public symbols.\n"
304 " -versionScript Generate linker version script by\n"
306 " -debug Enable debug output.\n"
307 " -copy Copy header files instead of creating\n"
309 " -minimal Do not create CaMeL case headers for the\n"
310 " public C++ symbols.\n"
311 " -showonly Show actions, but not perform them.\n"
312 " -warningsAreErrors Treat all warnings as errors.\n"
313 " -help Print this help.\n";
318 [[nodiscard]]
bool checkRequiredArguments(
const std::unordered_map<std::string, T> &
arguments)
324 std::cerr <<
"Missing argument: " <<
argument.first << std::endl;
331 [[nodiscard]]
bool parseArguments(
int argc,
char *argv[])
333 std::string qpaHeadersFilter;
334 std::string rhiHeadersFilter;
335 std::string ssgHeadersFilter;
336 std::string privateHeadersFilter;
337 std::string publicNamespaceFilter;
338 static std::unordered_map<std::string, CommandLineOption<std::string>> stringArgumentMap = {
339 {
"-module", { &m_moduleName } },
340 {
"-sourceDir", { &m_sourceDir } },
341 {
"-binaryDir", { &m_binaryDir } },
342 {
"-privateHeadersFilter", { &privateHeadersFilter,
true } },
343 {
"-qpaHeadersFilter", { &qpaHeadersFilter,
true } },
344 {
"-rhiHeadersFilter", { &rhiHeadersFilter,
true } },
345 {
"-ssgHeadersFilter", { &ssgHeadersFilter,
true } },
346 {
"-includeDir", { &m_includeDir } },
347 {
"-privateIncludeDir", { &m_privateIncludeDir } },
348 {
"-qpaIncludeDir", { &m_qpaIncludeDir } },
349 {
"-rhiIncludeDir", { &m_rhiIncludeDir } },
350 {
"-ssgIncludeDir", { &m_ssgIncludeDir } },
351 {
"-stagingDir", { &m_stagingDir,
true } },
352 {
"-versionScript", { &m_versionScriptFile,
true } },
353 {
"-publicNamespaceFilter", { &publicNamespaceFilter,
true } },
356 static const std::unordered_map<std::string, CommandLineOption<std::set<std::string>>>
358 {
"-headers", { &m_headers,
true } },
359 {
"-generatedHeaders", { &m_generatedHeaders,
true } },
360 {
"-knownModules", { &m_knownModules,
true } },
363 static const std::unordered_map<std::string, CommandLineOption<bool>> boolArgumentMap = {
364 {
"-nonQt", { &m_isNonQtModule,
true } }, {
"-debug", { &m_debug,
true } },
365 {
"-help", { &m_printHelpOnly,
true } },
366 {
"-internal", { &m_isInternal,
true } }, {
"-all", { &m_scanAllMode,
true } },
367 {
"-copy", { &m_copy,
true } }, {
"-minimal", { &m_minimal,
true } },
368 {
"-showonly", { &m_showOnly,
true } }, {
"-showOnly", { &m_showOnly,
true } },
369 {
"-warningsAreErrors", { &m_warningsAreErrors,
true } }
373 std::set<std::string> *currentListValue =
nullptr;
375 auto parseArgument = [&
currentValue, ¤tListValue](
const std::string &
arg) ->
bool {
378 currentListValue =
nullptr;
381 if (
it != stringArgumentMap.
end()) {
382 if (
it->second.value ==
nullptr) {
393 if (
it != boolArgumentMap.
end()) {
394 if (
it->second.value ==
nullptr) {
398 *(
it->second.value) =
true;
405 if (
it != listArgumentMap.
end()) {
406 if (
it->second.value ==
nullptr) {
410 currentListValue =
it->second.value;
411 currentListValue->
insert(
"");
416 std::cerr <<
"Unknown argument: " <<
arg << std::endl;
423 }
else if (currentListValue !=
nullptr) {
424 currentListValue->insert(
arg);
426 std::cerr <<
"Unknown argument: " <<
arg << std::endl;
432 for (
int i = 1;
i < argc; ++
i) {
433 std::string
arg(argv[
i]);
438 std::ifstream ifs(
arg.substr(1), std::ifstream::in);
439 if (!ifs.is_open()) {
440 std::cerr <<
"Unable to open rsp file: " <<
arg[0] << std::endl;
443 std::string argFromFile;
444 while (std::getline(ifs, argFromFile)) {
445 if (argFromFile.empty())
447 if (!parseArgument(argFromFile))
454 if (!parseArgument(
arg))
461 if (!qpaHeadersFilter.empty())
462 m_qpaHeadersRegex = std::regex(qpaHeadersFilter);
464 if (!rhiHeadersFilter.empty())
465 m_rhiHeadersRegex = std::regex(rhiHeadersFilter);
467 if (!ssgHeadersFilter.empty())
468 m_ssgHeadersRegex = std::regex(ssgHeadersFilter);
470 if (!privateHeadersFilter.empty())
471 m_privateHeadersRegex = std::regex(privateHeadersFilter);
473 if (!publicNamespaceFilter.empty())
474 m_publicNamespaceRegex = std::regex(publicNamespaceFilter);
476 if (m_headers.empty() && !m_scanAllMode) {
477 std::cerr <<
"You need to specify either -headers or -all option." << std::endl;
481 if (!m_headers.empty() && m_scanAllMode) {
482 std::cerr <<
"Both -headers and -all are specified. Need to choose only one"
483 "operational mode." << std::endl;
487 for (
const auto &
argument : listArgumentMap)
491 ret &= checkRequiredArguments(stringArgumentMap);
492 ret &= checkRequiredArguments(listArgumentMap);
500 void normilizePaths()
502 static std::array<std::string *, 8>
paths = {
503 &m_sourceDir, &m_binaryDir, &m_includeDir, &m_privateIncludeDir,
504 &m_qpaIncludeDir, &m_rhiIncludeDir, &m_stagingDir,
505 &m_versionScriptFile,
513 std::string m_moduleName;
514 std::string m_sourceDir;
515 std::string m_binaryDir;
516 std::string m_includeDir;
517 std::string m_privateIncludeDir;
518 std::string m_qpaIncludeDir;
519 std::string m_rhiIncludeDir;
520 std::string m_ssgIncludeDir;
521 std::string m_stagingDir;
522 std::string m_versionScriptFile;
523 std::set<std::string> m_knownModules;
524 std::set<std::string> m_headers;
525 std::set<std::string> m_generatedHeaders;
526 bool m_scanAllMode =
false;
528 bool m_isNonQtModule =
false;
529 bool m_isInternal =
false;
530 bool m_printHelpOnly =
false;
531 bool m_debug =
false;
532 bool m_minimal =
false;
533 bool m_showOnly =
false;
534 bool m_warningsAreErrors =
false;
535 std::regex m_qpaHeadersRegex;
536 std::regex m_rhiHeadersRegex;
537 std::regex m_ssgHeadersRegex;
538 std::regex m_privateHeadersRegex;
539 std::regex m_publicNamespaceRegex;
546 class SymbolDescriptor
556 void update(
const std::string &file, SourceType
type)
565 const std::string &
file()
const {
return m_file; }
568 SourceType m_type = MaxSourceType;
571 using SymbolContainer = std::unordered_map<std::string, SymbolDescriptor>;
575 std::vector<std::string> versionScriptContent;
576 std::string requireConfig;
577 bool masterInclude =
true;
582 std::map<std::string , std::string ,
584 m_masterHeaderContents;
586 std::unordered_map<std::string ,
589 std::vector<std::string> m_versionScriptContents;
590 std::set<std::string> m_producedHeaders;
591 std::vector<std::string> m_headerCheckExceptions;
592 SymbolContainer m_symbols;
593 std::ostream &scannerDebug()
const
595 if (m_commandLineArgs->
debug())
600 enum { Active, Stopped, IgnoreNext, Ignore } m_versionScriptGeneratorState = Active;
602 std::filesystem::path m_outputRootName;
603 std::filesystem::path m_currentFile;
604 std::string m_currentFilename;
605 std::string m_currentFileString;
606 size_t m_currentFileLineNumber = 0;
607 bool m_currentFileInSourceDir =
false;
609 enum FileType { PublicHeader = 0, PrivateHeader = 1, QpaHeader = 2, ExportHeader = 4, RhiHeader = 8, SsgHeader = 16 };
610 unsigned int m_currentFileType = PublicHeader;
613 std::string_view m_warningMessagePreamble;
617 : m_commandLineArgs(commandLineArgs),
620 std::filesystem::weakly_canonical(m_commandLineArgs->includeDir()).root_name()),
629 [[nodiscard]] std::filesystem::path
makeHeaderAbsolute(
const std::string &filename)
const;
638 m_versionScriptGeneratorState =
645 for (
auto const &
entry :
646 std::filesystem::recursive_directory_iterator(m_commandLineArgs->
sourceDir())) {
648 const bool isRegularFile =
entry.is_regular_file();
650 const bool isDocFileHeuristicFlag =
652 const bool shouldProcessHeader =
653 isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag;
654 const std::string filePath =
entry.path().generic_string();
656 if (shouldProcessHeader) {
657 scannerDebug() <<
"Processing header: " << filePath << std::endl;
662 <<
"Skipping processing header: " << filePath
663 <<
" isRegularFile: " << isRegularFile
664 <<
" isHeaderFlag: " << isHeaderFlag
665 <<
" isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
672 std::set<std::string> rspHeaders;
673 const auto &headers = m_commandLineArgs->
headers();
676 scannerDebug() <<
"Processing header: " <<
header << std::endl;
681 for (
const auto &
header : rspHeaders) {
682 scannerDebug() <<
"Processing header: " <<
header << std::endl;
689 if (m_commandLineArgs->
minimal())
694 const std::string &filename =
it->second.file();
695 if (!filename.empty()) {
697 m_commandLineArgs->
includeDir() +
'/' +
it->first, filename)) {
698 m_producedHeaders.
insert(
it->first);
708 std::string versionHeaderFilename(moduleNameLower +
"version.h");
709 std::string versionHeaderCamel(m_commandLineArgs->
moduleName() +
"Version");
710 std::string versionFile = m_commandLineArgs->
includeDir() +
'/' + versionHeaderFilename;
713 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
715 originalStamp = FileStamp::clock::now();
719 m_commandLineArgs->
includeDir() +
'/' + versionHeaderCamel,
720 versionHeaderFilename, originalStamp)) {
723 m_masterHeaderContents[versionHeaderFilename] = {};
724 m_producedHeaders.insert(versionHeaderFilename);
725 m_producedHeaders.insert(versionHeaderCamel);
763 bool skipCleanup =
false)
766 bool outDirExists =
false;
771 if (outDirExists && !skipCleanup) {
773 for (
const auto &
entry :
774 std::filesystem::recursive_directory_iterator(outputDirectory)) {
775 if (m_producedHeaders.find(
entry.path().filename().generic_string())
776 == m_producedHeaders.end()) {
779 std::string firstLine;
781 std::ifstream
input(
entry.path(), std::ifstream::in);
782 if (
input.is_open()) {
783 std::getline(
input, firstLine);
787 if (firstLine.find(
"#ifndef DEPRECATED_HEADER_"
790 || firstLine.find(
"#ifndef DEPRECATED_HEADER_") != 0)
791 std::filesystem::remove(
entry.path());
794 }
catch (
const std::filesystem::filesystem_error &fserr) {
800 for (
const auto &
header : m_producedHeaders) {
802 std::filesystem::path
dst(outputDirectory +
'/' +
header);
812 static const std::regex ExportsHeaderRegex(
"^q(.*)exports(_p)?\\.h$");
814 m_currentFile = headerFile;
815 m_currentFileLineNumber = 0;
816 m_currentFilename = m_currentFile.filename().generic_string();
817 m_currentFileType = PublicHeader;
818 m_currentFileString = m_currentFile.generic_string();
819 m_currentFileInSourceDir = m_currentFileString.find(m_commandLineArgs->
sourceDir()) == 0;
822 m_currentFileType = PrivateHeader;
825 m_currentFileType = QpaHeader | PrivateHeader;
828 m_currentFileType = RhiHeader | PrivateHeader;
831 m_currentFileType = SsgHeader | PrivateHeader;
833 if (std::regex_match(m_currentFilename, ExportsHeaderRegex))
834 m_currentFileType |= ExportHeader;
840 static const std::regex ThirdPartyFolderRegex(
"(^|.+/)3rdparty/.+");
843 static const std::regex ConfigHeaderRegex(
"^(q|.+-)config(_p)?\\.h");
848 if (!m_currentFileInSourceDir
849 && m_currentFileString.find(m_commandLineArgs->
binaryDir()) != 0) {
850 scannerDebug() <<
"Header file: " << headerFile
851 <<
" is outside the sync directories. Skipping." << std::endl;
852 m_headerCheckExceptions.push_back(m_currentFileString);
857 if (m_currentFilename.empty()) {
858 std::cerr <<
"Header file name of " << m_currentFileString <<
"is empty" << std::endl;
863 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
865 originalStamp = FileStamp::clock::now();
868 bool isPrivate = m_currentFileType & PrivateHeader;
869 bool isQpa = m_currentFileType & QpaHeader;
870 bool isRhi = m_currentFileType & RhiHeader;
871 bool isSsg = m_currentFileType & SsgHeader;
872 bool isExport = m_currentFileType & ExportHeader;
874 <<
"processHeader:start: " << headerFile
875 <<
" m_currentFilename: " << m_currentFilename
876 <<
" isPrivate: " << isPrivate
877 <<
" isQpa: " << isQpa
878 <<
" isRhi: " << isRhi
879 <<
" isSsg: " << isSsg
884 std::string outputDir = m_commandLineArgs->
includeDir();
897 bool headerFileExists = std::filesystem::exists(headerFile);
899 std::string aliasedFilepath = headerFile.generic_string();
901 std::string aliasPath = outputDir +
'/' + m_currentFilename;
906 if (m_commandLineArgs->
copy() && headerFileExists) {
916 if (m_commandLineArgs->
minimal())
924 if (!headerFileExists) {
925 scannerDebug() <<
"Header file: " << headerFile
926 <<
" doesn't exist, but is added to syncqt scanning. Skipping.";
934 bool is3rdParty = std::regex_match(
935 std::filesystem::relative(headerFile, m_commandLineArgs->
sourceDir())
937 ThirdPartyFolderRegex);
940 if (!std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
944 if (m_commandLineArgs->
isNonQtModule() || is3rdParty || isQpa || isRhi || isSsg
945 || !m_currentFileInSourceDir || isGenerated) {
963 ParsingResult parsingResult;
964 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
965 && !isQpa && !isRhi && !isSsg && !isPrivate && !isGenerated;
966 if (!
parseHeader(headerFile, parsingResult, skipChecks)) {
967 scannerDebug() <<
"parseHeader failed: " << headerFile << std::endl;
973 && !parsingResult.versionScriptContent.empty()) {
974 m_versionScriptContents.insert(m_versionScriptContents.end(),
975 parsingResult.versionScriptContent.begin(),
976 parsingResult.versionScriptContent.end());
981 bool willBeInModuleMasterHeader =
false;
982 if (!isQpa && !isRhi && !isSsg && !isPrivate) {
983 if (m_currentFilename.find(
'_') == std::string::npos
984 && parsingResult.masterInclude) {
985 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
986 willBeInModuleMasterHeader =
true;
991 <<
"processHeader:end: " << headerFile
992 <<
" is3rdParty: " << is3rdParty
993 <<
" isGenerated: " << isGenerated
994 <<
" m_currentFileInSourceDir: " << m_currentFileInSourceDir
995 <<
" willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
997 }
else if (m_currentFilename ==
"qconfig.h") {
1008 static const std::regex VersionScriptSymbolRegex(
1009 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1012 static const std::regex VersionScriptNamespaceRegex(
1013 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1016 static const std::regex TrailingColonRegex(
"([\\w]+):$");
1018 switch (m_versionScriptGeneratorState) {
1020 scannerDebug() <<
"line ignored: " <<
buffer << std::endl;
1021 m_versionScriptGeneratorState = Active;
1026 m_versionScriptGeneratorState = Ignore;
1038 symbol =
match[1].str();
1039 else if (std::regex_match(
buffer,
match, VersionScriptNamespaceRegex))
1040 symbol =
match[1].str();
1042 if (std::regex_match(symbol,
match, TrailingColonRegex))
1043 symbol =
match[1].str();
1046 if (!symbol.empty() && symbol[symbol.size() - 1] !=
';') {
1047 std::string relPath = m_currentFileInSourceDir
1048 ? std::filesystem::relative(m_currentFile, m_commandLineArgs->
sourceDir())
1050 : std::filesystem::relative(m_currentFile, m_commandLineArgs->
binaryDir())
1053 std::string versionStringRecord =
" *";
1054 size_t startPos = 0;
1056 while (endPos != std::string::npos) {
1057 endPos = symbol.find(
"::", startPos);
1058 size_t length = endPos != std::string::npos ? (endPos - startPos)
1059 : (symbol.size() - startPos);
1061 std::string symbolPart = symbol.substr(startPos,
length);
1062 versionStringRecord += std::to_string(symbolPart.size());
1063 versionStringRecord += symbolPart;
1065 startPos = endPos + 2;
1067 versionStringRecord +=
"*;";
1069 versionStringRecord +=
1071 versionStringRecord +=
" # ";
1072 versionStringRecord += relPath;
1073 versionStringRecord +=
":";
1074 versionStringRecord += std::to_string(m_currentFileLineNumber);
1075 versionStringRecord +=
"\n";
1076 result.versionScriptContent.push_back(versionStringRecord);
1084 [[nodiscard]]
bool parseHeader(
const std::filesystem::path &headerFile,
1086 unsigned int skipChecks)
1089 std::cout << headerFile <<
" [" << m_commandLineArgs->
moduleName() <<
"]" << std::endl;
1091 static const std::regex MacroRegex(
"^\\s*#.*");
1124 static const std::regex OnceRegex(R
"(^#\s*pragma\s+once$)");
1125 static const std::regex SkipHeaderCheckRegex(
"^#\\s*pragma qt_sync_skip_header_check$");
1126 static const std::regex StopProcessingRegex(
"^#\\s*pragma qt_sync_stop_processing$");
1127 static const std::regex SuspendProcessingRegex(
"^#\\s*pragma qt_sync_suspend_processing$");
1128 static const std::regex ResumeProcessingRegex(
"^#\\s*pragma qt_sync_resume_processing$");
1129 static const std::regex ExplixitClassPragmaRegex(
"^#\\s*pragma qt_class\\(([^\\)]+)\\)$");
1130 static const std::regex DeprecatesPragmaRegex(
"^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$");
1131 static const std::regex NoMasterIncludePragmaRegex(
"^#\\s*pragma qt_no_master_include$");
1135 static const std::string_view WeMeantItString(
"We mean it.");
1138 static const std::regex BeginNamespaceRegex(
"^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1139 static const std::regex EndNamespaceRegex(
"^QT_END_NAMESPACE(_[A-Z_]+)?$");
1145 static const std::regex IncludeRegex(
"^#\\s*include\\s*[<\"](.+)[>\"]");
1148 static const std::regex NamespaceRegex(
"\\s*namespace ([^ ]*)\\s+");
1152 static const std::regex DeclareIteratorRegex(
"^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
1157 static const std::regex RequireConfigRegex(
"^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
1165 static const std::regex ElfVersionTagRegex(
".*ELFVERSION:(stop|ignore-next|ignore).*");
1167 std::ifstream
input(headerFile, std::ifstream::in);
1168 if (!
input.is_open()) {
1169 std::cerr <<
"Unable to open " << headerFile << std::endl;
1173 bool hasQtBeginNamespace =
false;
1174 std::string qtBeginNamespace;
1175 std::string qtEndNamespace;
1176 bool hasWeMeantIt =
false;
1177 bool isSuspended =
false;
1178 bool isMultiLineComment =
false;
1179 std::size_t bracesDepth = 0;
1180 std::size_t namespaceCount = 0;
1181 std::string namespaceString;
1187 std::string tmpLine;
1188 std::size_t linesProcessed = 0;
1191 const auto error = [&] () ->
decltype(
auto) {
1193 <<
":" << m_currentFileLineNumber <<
" ";
1197 while (std::getline(
input, tmpLine)) {
1198 ++m_currentFileLineNumber;
1210 if (
line[
i] ==
'\r')
1212 if (bracesDepth == namespaceCount) {
1213 if (
line[
i] ==
'/') {
1215 if (
line[
i + 1] ==
'*') {
1216 isMultiLineComment =
true;
1218 }
else if (
line[
i + 1] ==
'/') {
1220 &&
line.find(WeMeantItString) != std::string::npos) {
1221 hasWeMeantIt =
true;
1224 if (m_versionScriptGeneratorState != Stopped
1225 && std::regex_match(
line,
match, ElfVersionTagRegex)) {
1227 m_versionScriptGeneratorState = Ignore;
1228 else if (
match[1].
str() ==
"ignore-next")
1229 m_versionScriptGeneratorState = IgnoreNext;
1231 m_versionScriptGeneratorState = Stopped;
1238 isMultiLineComment =
false;
1243 if (isMultiLineComment) {
1245 line.find(WeMeantItString) != std::string::npos) {
1246 hasWeMeantIt =
true;
1252 if (
line[
i] ==
'{') {
1253 if (std::regex_match(
buffer,
match, NamespaceRegex)) {
1255 namespaceString +=
"::";
1256 namespaceString +=
match[1].str();
1260 }
else if (
line[
i] ==
'}') {
1261 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1262 namespaceString.resize(namespaceString.rfind(
"::"));
1266 }
else if (bracesDepth == namespaceCount) {
1272 scannerDebug() << m_currentFilename <<
": " <<
buffer << std::endl;
1274 if (m_currentFileType & PrivateHeader) {
1284 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1285 || (m_currentFileType & SsgHeader);
1288 if (std::regex_match(
buffer, MacroRegex)) {
1289 if (std::regex_match(
buffer, SkipHeaderCheckRegex)) {
1292 }
else if (std::regex_match(
buffer, StopProcessingRegex)) {
1294 m_headerCheckExceptions.push_back(m_currentFileString);
1296 }
else if (std::regex_match(
buffer, SuspendProcessingRegex)) {
1298 }
else if (std::regex_match(
buffer, ResumeProcessingRegex)) {
1299 isSuspended =
false;
1300 }
else if (std::regex_match(
buffer,
match, ExplixitClassPragmaRegex)) {
1303 SymbolDescriptor::Pragma);
1307 }
else if (std::regex_match(
buffer, NoMasterIncludePragmaRegex)) {
1308 result.masterInclude =
false;
1309 }
else if (std::regex_match(
buffer,
match, DeprecatesPragmaRegex)) {
1310 m_deprecatedHeaders[
match[1].str()] =
1311 m_commandLineArgs->
moduleName() +
'/' + m_currentFilename;
1312 }
else if (std::regex_match(
buffer, OnceRegex)) {
1315 error() <<
"\"#pragma once\" is not allowed in installed header files: "
1316 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1319 }
else if (std::regex_match(
buffer,
match, IncludeRegex) && !isSuspended) {
1321 std::string includedHeader =
match[1].str();
1325 .generic_string())) {
1327 error() <<
"includes private header " << includedHeader << std::endl;
1329 for (
const auto &module : m_commandLineArgs->
knownModules()) {
1330 std::string suggestedHeader =
"Qt" +
module + '/' + includedHeader;
1331 if (std::filesystem::exists(m_commandLineArgs->
includeDir() +
"/../"
1332 + suggestedHeader)) {
1334 std::cerr << m_warningMessagePreamble << m_currentFileString
1335 <<
":" << m_currentFileLineNumber
1336 <<
" includes " << includedHeader
1337 <<
" when it should include "
1338 << suggestedHeader << std::endl;
1355 if (namespaceCount == 0
1356 || std::regex_match(namespaceString,
1359 SymbolDescriptor::Declaration);
1362 }
else if (std::regex_match(
buffer,
match, DeclareIteratorRegex)) {
1363 std::string iteratorSymbol =
match[1].str() +
"Iterator";
1365 SymbolDescriptor::Declaration);
1367 m_currentFilename, SymbolDescriptor::Declaration);
1369 }
else if (std::regex_match(
buffer,
match, RequireConfigRegex)) {
1378 if (std::regex_match(
buffer,
match, BeginNamespaceRegex)) {
1379 qtBeginNamespace =
match[1].str();
1380 hasQtBeginNamespace =
true;
1381 }
else if (std::regex_match(
buffer,
match, EndNamespaceRegex)) {
1382 qtEndNamespace =
match[1].str();
1390 if (hasQtBeginNamespace) {
1391 if (qtBeginNamespace != qtEndNamespace) {
1393 std::cerr << m_warningMessagePreamble << m_currentFileString
1394 <<
" the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1395 <<
" doesn't match the end namespace macro QT_END_NAMESPACE"
1396 << qtEndNamespace << std::endl;
1400 std::cerr << m_warningMessagePreamble << m_currentFileString
1401 <<
" does not include QT_BEGIN_NAMESPACE" << std::endl;
1407 std::cerr << m_warningMessagePreamble << m_currentFileString
1408 <<
" does not have the \"We mean it.\" warning"
1412 scannerDebug() <<
"linesTotal: " << m_currentFileLineNumber
1413 <<
" linesProcessed: " << linesProcessed << std::endl;
1416 m_headerCheckExceptions.push_back(m_currentFileString);
1419 return !(faults & m_criticalChecks);
1425 scannerDebug() <<
"checkLineForSymbols: " <<
line << std::endl;
1433 static const std::regex ClassRegex(
1434 "^ *(template *<.*> *)?(class|struct) +([^ <>]* "
1435 "+)?((?!Q_DECL_FINAL|final|sealed)[^<\\s\\:]+) ?(<[^>\\:]*> "
1436 "?)?\\s*(?:Q_DECL_FINAL|final|sealed)?\\s*((,|:)\\s*(public|protected|private)? "
1441 static const std::regex FunctionPointerRegex(
1442 "^ *typedef *.*\\(\\*(Q[^\\)]+)\\)\\(.*\\); *");
1446 static const std::regex TypedefRegex(
"^ *typedef\\s+(.*)\\s+(Q\\w+); *$");
1450 static const std::regex QtClassRegex(
"^Q\\w+$");
1453 if (std::regex_match(
line,
match, FunctionPointerRegex)) {
1454 symbol =
match[1].str();
1455 }
else if (std::regex_match(
line,
match, TypedefRegex)) {
1456 symbol =
match[2].str();
1457 }
else if (std::regex_match(
line,
match, ClassRegex)) {
1458 symbol =
match[4].str();
1459 if (!std::regex_match(symbol, QtClassRegex))
1464 return !symbol.empty();
1469 return std::regex_match(headerFileName, m_commandLineArgs->
qpaHeadersRegex());
1474 return std::regex_match(headerFileName, m_commandLineArgs->
rhiHeadersRegex());
1479 return std::regex_match(headerFileName, m_commandLineArgs->
ssgHeadersRegex());
1489 static const std::string pchSuffix(
"_pch.h");
1490 return headerFilename.find(pchSuffix, headerFilename.size() - pchSuffix.size())
1491 != std::string::npos;
1496 return path.extension().string() ==
".h";
1501 return headerFilePath.find(
"/doc/") != std::string::npos;
1511 const std::string &aliasedFilePath);
1514 const std::string &outputFilePath,
const std::string &aliasedFilePath,
1515 const FileStamp &originalStamp = FileStamp::clock::now());
1521 if (m_masterHeaderContents.empty())
1528 std::stringstream
buffer;
1529 buffer <<
"#ifndef QT_" << moduleUpper <<
"_MODULE_H\n"
1530 <<
"#define QT_" << moduleUpper <<
"_MODULE_H\n"
1531 <<
"#include <" << m_commandLineArgs->
moduleName() <<
"/"
1532 << m_commandLineArgs->
moduleName() <<
"Depends>\n";
1533 for (
const auto &headerContents : m_masterHeaderContents) {
1534 if (!headerContents.second.empty()) {
1535 buffer <<
"#if QT_CONFIG(" << headerContents.second <<
")\n"
1536 <<
"#include \"" << headerContents.first <<
"\"\n"
1539 buffer <<
"#include \"" << headerContents.first <<
"\"\n";
1544 m_producedHeaders.insert(m_commandLineArgs->
moduleName());
1552 std::stringstream
buffer;
1553 buffer <<
"/* This file was generated by syncqt. */\n"
1554 <<
"#ifndef QT_" << moduleNameUpper <<
"_VERSION_H\n"
1555 <<
"#define QT_" << moduleNameUpper <<
"_VERSION_H\n\n"
1556 <<
"#define " << moduleNameUpper <<
"_VERSION_STR \"" << QT_VERSION_STR <<
"\"\n\n"
1557 <<
"#define " << moduleNameUpper <<
"_VERSION "
1558 <<
"0x0" << QT_VERSION_MAJOR <<
"0" << QT_VERSION_MINOR <<
"0" << QT_VERSION_PATCH
1560 <<
"#endif // QT_" << moduleNameUpper <<
"_VERSION_H\n";
1567 static std::regex cIdentifierSymbolsRegex(
"[^a-zA-Z0-9_]");
1568 static std::string guard_base =
"DEPRECATED_HEADER_" + m_commandLineArgs->
moduleName();
1570 for (
auto it = m_deprecatedHeaders.
begin();
it != m_deprecatedHeaders.
end(); ++
it) {
1571 const std::string &descriptor =
it->first;
1572 const std::string &replacement =
it->second;
1574 const auto separatorPos = descriptor.
find(
',');
1575 std::string headerPath = descriptor.substr(0, separatorPos);
1576 std::string versionDisclaimer;
1577 if (separatorPos != std::string::npos) {
1578 std::string version = descriptor.substr(separatorPos + 1);
1579 versionDisclaimer =
" and will be removed in Qt " + version;
1584 <<
"Invalid version format specified for the deprecated header file "
1585 << headerPath <<
": '" << version
1586 <<
"'. Expected format: 'major.minor'.\n";
1591 if (QT_VERSION_MAJOR > major
1592 || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) {
1594 <<
" is marked as deprecated and will not be generated in Qt "
1596 <<
". The respective qt_deprecates pragma needs to be removed.\n";
1601 const auto moduleSeparatorPos = headerPath.find(
'/');
1602 std::string headerName = moduleSeparatorPos != std::string::npos
1603 ? headerPath.substr(moduleSeparatorPos + 1)
1605 const std::string moduleName = moduleSeparatorPos != std::string::npos
1606 ? headerPath.substr(0, moduleSeparatorPos)
1609 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs->
moduleName();
1611 std::string qualifiedHeaderName =
1612 std::regex_replace(headerName, cIdentifierSymbolsRegex,
"_");
1613 std::string guard = guard_base +
"_" + qualifiedHeaderName;
1614 std::string warningText =
"Header <" + moduleName +
"/" + headerName +
"> is deprecated"
1615 + versionDisclaimer +
". Please include <" + replacement +
"> instead.";
1616 std::stringstream
buffer;
1617 buffer <<
"#ifndef " << guard <<
"\n"
1618 <<
"#define " << guard <<
"\n"
1619 <<
"#if defined(__GNUC__)\n"
1620 <<
"# warning " << warningText <<
"\n"
1621 <<
"#elif defined(_MSC_VER)\n"
1622 <<
"# pragma message (\"" << warningText <<
"\")\n"
1624 <<
"#include <" << replacement <<
">\n"
1627 const std::string outputDir = isCrossModuleDeprecation
1628 ? m_commandLineArgs->
includeDir() +
"/../" + moduleName
1633 if (isCrossModuleDeprecation) {
1634 const std::string stagingDir = outputDir +
"/.syncqt_staging/";
1637 m_producedHeaders.insert(headerName);
1644 std::stringstream
buffer;
1645 for (
const auto &
header : m_headerCheckExceptions)
1649 +
"_header_check_exceptions",
1655 std::stringstream
buffer;
1656 for (
const auto &content : m_versionScriptContents)
1661 bool updateOrCopy(
const std::filesystem::path &
src,
const std::filesystem::path &
dst)
noexcept;
1663 SymbolDescriptor::SourceType
type);
1671 SymbolDescriptor::SourceType
type)
1674 std::cout <<
" SYMBOL: " << symbol << std::endl;
1675 m_symbols[symbol].update(
file,
type);
1678[[nodiscard]] std::filesystem::path
1681 if (std::filesystem::path(filename).is_relative())
1688 const std::filesystem::path &
dst)
noexcept
1694 std::cout <<
"Source and destination paths are same when copying " <<
src.string()
1695 <<
". Skipping." << std::endl;
1700 std::filesystem::copy(
src,
dst, std::filesystem::copy_options::update_existing, ec);
1703 std::filesystem::remove(
dst, ec);
1707 std::cerr <<
"Unable to remove file: " <<
src <<
" to " <<
dst <<
" error: ("
1708 << ec.value() <<
")" << ec.message() << std::endl;
1712 std::filesystem::copy(
src,
dst, std::filesystem::copy_options::overwrite_existing, ec);
1714 std::cerr <<
"Unable to copy file: " <<
src <<
" to " <<
dst <<
" error: ("
1715 << ec.value() <<
")" << ec.message() << std::endl;
1728 const std::string &aliasedFilePath)
1733 std::string
buffer =
"#include \"";
1734 buffer += aliasedFilePath;
1735 buffer +=
"\" // IWYU pragma: export\n";
1744 const std::string &aliasedFilePath,
1750 if (std::filesystem::exists({ outputFilePath })
1751 && std::filesystem::last_write_time({ outputFilePath }) >= originalStamp) {
1754 scannerDebug() <<
"Rewrite " << outputFilePath << std::endl;
1757 ofs.open(outputFilePath, std::ofstream::out | std::ofstream::trunc);
1758 if (!ofs.is_open()) {
1759 std::cerr <<
"Unable to write header file alias: " << outputFilePath << std::endl;
1762 ofs <<
"#include \"" << aliasedFilePath <<
"\" // IWYU pragma: export\n";
1772 static const std::streamsize bufferSize = 1025;
1773 bool differs =
false;
1774 std::filesystem::path outputFilePath(
outputFile);
1776 std::string outputDirectory = outputFilePath.parent_path().string();
1781 auto expectedSize =
buffer.size();
1784 expectedSize += std::count(
buffer.begin(),
buffer.end(),
'\n');
1787 if (std::filesystem::exists(outputFilePath)
1788 && expectedSize == std::filesystem::file_size(outputFilePath)) {
1789 char rdBuffer[bufferSize];
1790 memset(rdBuffer, 0, bufferSize);
1792 std::ifstream ifs(
outputFile, std::fstream::in);
1793 if (!ifs.is_open()) {
1794 std::cerr <<
"Unable to open " <<
outputFile <<
" for comparison." << std::endl;
1797 std::streamsize currentPos = 0;
1799 std::size_t bytesRead = 0;
1801 ifs.read(rdBuffer, bufferSize - 1);
1802 bytesRead = ifs.gcount();
1803 if (
buffer.compare(currentPos, bytesRead, rdBuffer) != 0) {
1807 currentPos += bytesRead;
1808 memset(rdBuffer, 0, bufferSize);
1809 }
while (bytesRead > 0);
1816 scannerDebug() <<
"Update: " <<
outputFile <<
" " << differs << std::endl;
1819 ofs.open(outputFilePath, std::fstream::out | std::ofstream::trunc);
1820 if (!ofs.is_open()) {
1821 std::cerr <<
"Unable to write header content to " << outputFilePath << std::endl;
1843 return scanner.sync();
const std::string & rhiIncludeDir() const
const std::string & privateIncludeDir() const
const std::string & sourceDir() const
const std::string & binaryDir() const
const std::string & ssgIncludeDir() const
const std::set< std::string > & generatedHeaders() const
const std::string & moduleName() const
const std::string & versionScriptFile() const
const std::regex & publicNamespaceRegex() const
const std::string & stagingDir() const
const std::string & qpaIncludeDir() const
const std::set< std::string > & knownModules() const
const std::string & includeDir() const
bool isNonQtModule() const
const std::regex & qpaHeadersRegex() const
CommandLineOptions(int argc, char *argv[])
const std::regex & ssgHeadersRegex() const
const std::regex & privateHeadersRegex() const
bool printHelpOnly() const
bool warningsAreErrors() const
const std::regex & rhiHeadersRegex() const
const std::set< std::string > & headers() const
iterator find(const T &value)
iterator insert(const T &value)
void clear()
Clears the contents of the string and makes it null.
qsizetype size() const noexcept
Returns the number of characters in this string.
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
QString & append(QChar c)
bool generateVersionHeader(const std::string &outputFile)
bool generateLinkerVersionScript()
bool isHeaderQpa(const std::string &headerFileName)
void parseVersionScriptContent(const std::string buffer, ParsingResult &result)
bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) noexcept
bool checkLineForSymbols(const std::string &line, std::string &symbol)
void updateSymbolDescriptor(const std::string &symbol, const std::string &file, SymbolDescriptor::SourceType type)
bool isHeaderGenerated(const std::string &header)
std::filesystem::path makeHeaderAbsolute(const std::string &filename) const
bool generateMasterHeader()
bool generateAliasedHeaderFileIfTimestampChanged(const std::string &outputFilePath, const std::string &aliasedFilePath, const FileStamp &originalStamp=FileStamp::clock::now())
bool isHeader(const std::filesystem::path &path)
bool generateDeprecatedHeaders()
bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory, bool skipCleanup=false)
bool writeIfDifferent(const std::string &outputFile, const std::string &buffer)
bool isHeaderRhi(const std::string &headerFileName)
bool isHeaderSsg(const std::string &headerFileName)
bool parseHeader(const std::filesystem::path &headerFile, ParsingResult &result, unsigned int skipChecks)
bool isDocFileHeuristic(const std::string &headerFilePath)
bool isHeaderPCH(const std::string &headerFilename)
bool processHeader(const std::filesystem::path &headerFile)
SyncScanner(CommandLineOptions *commandLineArgs)
bool generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath, const std::string &aliasedFilePath)
bool generateHeaderCheckExceptions()
void resetCurrentFileInfoData(const std::filesystem::path &headerFile)
bool isHeaderPrivate(const std::string &headerFile)
QSet< QString >::iterator it
QList< QVariant > arguments
bool parseVersion(const std::string &version, int &major, int &minor)
std::string asciiToLower(std::string s)
std::filesystem::path normilizedPath(const std::string &path)
utils::DummyOutputStream DummyOutput
void printFilesystemError(const std::filesystem::filesystem_error &fserr, std::string_view errorMsg)
void printInternalError()
bool createDirectories(const std::string &path, std::string_view errorMsg, bool *exists=nullptr)
std::string asciiToUpper(std::string s)
qsizetype erase(QByteArray &ba, const T &t)
DBusConnection const char DBusError * error
static QString outputFile
static QString header(const QString &name)
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
GLboolean GLboolean GLboolean b
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLenum GLsizei length
GLsizei const GLuint * paths
GLsizei const GLchar *const * path
GLenum GLenum GLenum input
static const std::regex GlobalHeaderRegex("^q(.*)global\\.h$")
constexpr std::string_view ErrorMessagePreamble
std::filesystem::file_time_type FileStamp
constexpr int LinkerScriptCommentAlignment
constexpr std::string_view WarningMessagePreamble
bool MasterHeaderIncludeComparator(const std::string &a, const std::string &b)
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)