640 m_warningMessagePreamble = ErrorMessagePreamble;
643 m_versionScriptGeneratorState =
650 for (
auto const &entry :
651 std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) {
653 const bool isRegularFile = entry.is_regular_file();
654 const bool isHeaderFlag = isHeader(entry);
655 const bool isDocFileHeuristicFlag =
656 isDocFileHeuristic(entry.path().generic_string());
657 const bool shouldProcessHeader =
658 isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag;
659 const std::string filePath = entry.path().generic_string();
661 if (shouldProcessHeader) {
662 scannerDebug() <<
"Processing header: " << filePath << std::endl;
663 if (!processHeader(makeHeaderAbsolute(filePath)))
667 <<
"Skipping processing header: " << filePath
668 <<
" isRegularFile: " << isRegularFile
669 <<
" isHeaderFlag: " << isHeaderFlag
670 <<
" isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
677 std::set<std::string> rspHeaders;
678 const auto &headers = m_commandLineArgs->headers();
679 for (
auto it = headers.begin(); it != headers.end(); ++it) {
680 const auto &header = *it;
681 scannerDebug() <<
"Processing header: " << header <<
std::endl;
686 for (
const auto &header : rspHeaders) {
687 scannerDebug() <<
"Processing header: " << header << std::endl;
688 if (!processHeader(makeHeaderAbsolute(header)))
698 for (
auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
699 const std::string &filename = it->second.file();
700 if (!filename.empty()) {
703 m_producedHeaders.insert(it->first);
713 std::string versionHeaderFilename(moduleNameLower +
"version.h");
715 std::string versionFile = m_commandLineArgs
->includeDir() +
'/' + versionHeaderFilename;
718 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
720 originalStamp = FileStamp::clock::now();
723 if (!generateAliasedHeaderFileIfTimestampChanged(
724 m_commandLineArgs->includeDir() +
'/' + versionHeaderCamel,
725 versionFile, originalStamp)) {
728 m_masterHeaderContents[versionHeaderFilename] = {};
729 m_producedHeaders.insert(versionHeaderFilename);
730 m_producedHeaders.insert(versionHeaderCamel);
845 static const std::regex ThirdPartyFolderRegex(
"(^|.+/)3rdparty/.+");
848 static const std::regex ConfigHeaderRegex(
"^(q|.+-)config(_p)?\\.h");
853 if (!m_currentFileInSourceDir
854 && m_currentFileString.find(m_commandLineArgs
->binaryDir()) != 0) {
855 scannerDebug() <<
"Header file: " << headerFile
856 <<
" is outside the sync directories. Skipping." <<
std::endl;
857 m_headerCheckExceptions.push_back(m_currentFileString);
862 if (m_currentFilename.empty()) {
863 std::cerr <<
"Header file name of " << m_currentFileString <<
"is empty" <<
std::endl;
868 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
870 originalStamp = FileStamp::clock::now();
873 bool isPrivate = m_currentFileType & PrivateHeader;
874 bool isQpa = m_currentFileType & QpaHeader;
875 bool isRhi = m_currentFileType & RhiHeader;
876 bool isSsg = m_currentFileType & SsgHeader;
877 bool isExport = m_currentFileType & ExportHeader;
879 <<
"processHeader:start: " << headerFile
880 <<
" m_currentFilename: " << m_currentFilename
881 <<
" isPrivate: " << isPrivate
882 <<
" isQpa: " << isQpa
883 <<
" isRhi: " << isRhi
884 <<
" isSsg: " << isSsg
899 if (!utils::createDirectories(outputDir,
"Unable to create output directory"))
902 bool headerFileExists =
std::filesystem::exists(headerFile);
904 std::string aliasedFilepath = headerFile.generic_string();
906 std::string aliasPath = outputDir +
'/' + m_currentFilename;
911 if (m_commandLineArgs
->copy() && headerFileExists) {
915 if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
929 if (!headerFileExists) {
930 scannerDebug() <<
"Header file: " << headerFile
931 <<
" doesn't exist, but is added to syncqt scanning. Skipping.";
939 bool is3rdParty =
std::regex_match(
942 ThirdPartyFolderRegex);
945 if (!
std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
950 || !m_currentFileInSourceDir || isGenerated) {
968 ParsingResult parsingResult;
969 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
970 && !isQpa && !isRhi && !isSsg && !isPrivate && !isGenerated;
972 scannerDebug() <<
"parseHeader failed: " << headerFile <<
std::endl;
978 && !parsingResult.versionScriptContent.empty()) {
979 m_versionScriptContents.insert(m_versionScriptContents.end(),
980 parsingResult.versionScriptContent.begin(),
981 parsingResult.versionScriptContent.end());
986 bool willBeInModuleMasterHeader =
false;
987 if (!isQpa && !isRhi && !isSsg && !isPrivate) {
988 if (m_currentFilename.find(
'_') == std::string::npos
989 && parsingResult.masterInclude) {
990 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
991 willBeInModuleMasterHeader =
true;
996 <<
"processHeader:end: " << headerFile
997 <<
" is3rdParty: " << is3rdParty
998 <<
" isGenerated: " << isGenerated
999 <<
" m_currentFileInSourceDir: " << m_currentFileInSourceDir
1000 <<
" willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
1002 }
else if (m_currentFilename ==
"qconfig.h") {
1013 static const std::regex VersionScriptSymbolRegex(
1014 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1017 static const std::regex VersionScriptNamespaceRegex(
1018 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1021 static const std::regex TrailingColonRegex(
"([\\w]+):$");
1023 switch (m_versionScriptGeneratorState) {
1025 scannerDebug() <<
"line ignored: " << buffer <<
std::endl;
1026 m_versionScriptGeneratorState = Active;
1031 m_versionScriptGeneratorState = Ignore;
1042 if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
1043 symbol = match[1].str();
1044 else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex))
1045 symbol = match[1].str();
1047 if (std::regex_match(symbol, match, TrailingColonRegex))
1048 symbol = match[1].str();
1051 if (!symbol.empty() && symbol[symbol.size() - 1] !=
';') {
1052 std::string relPath = m_currentFileInSourceDir
1053 ? std::filesystem::relative(m_currentFile, m_commandLineArgs->sourceDir())
1055 : std::filesystem::relative(m_currentFile, m_commandLineArgs->binaryDir())
1058 std::string versionStringRecord =
" *";
1059 size_t startPos = 0;
1061 while (endPos != std::string::npos) {
1062 endPos = symbol.find(
"::", startPos);
1063 size_t length = endPos != std::string::npos ? (endPos - startPos)
1064 : (symbol.size() - startPos);
1066 std::string symbolPart = symbol.substr(startPos, length);
1067 versionStringRecord +=
std::to_string(symbolPart.size());
1068 versionStringRecord += symbolPart;
1070 startPos = endPos + 2;
1072 versionStringRecord +=
"*;";
1074 versionStringRecord +=
1076 versionStringRecord +=
" # ";
1077 versionStringRecord += relPath;
1078 versionStringRecord +=
":";
1079 versionStringRecord += std::to_string(m_currentFileLineNumber);
1080 versionStringRecord +=
"\n";
1081 result.versionScriptContent.push_back(versionStringRecord);
1090 ParsingResult &result,
1091 unsigned int skipChecks)
1096 static const std::regex MacroRegex(
"^\\s*#.*");
1129 static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)");
1130 static const std::regex SkipHeaderCheckRegex(
"^#\\s*pragma qt_sync_skip_header_check$");
1131 static const std::regex StopProcessingRegex(
"^#\\s*pragma qt_sync_stop_processing$");
1132 static const std::regex SuspendProcessingRegex(
"^#\\s*pragma qt_sync_suspend_processing$");
1133 static const std::regex ResumeProcessingRegex(
"^#\\s*pragma qt_sync_resume_processing$");
1134 static const std::regex ExplixitClassPragmaRegex(
"^#\\s*pragma qt_class\\(([^\\)]+)\\)$");
1135 static const std::regex DeprecatesPragmaRegex(
"^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$");
1136 static const std::regex NoMasterIncludePragmaRegex(
"^#\\s*pragma qt_no_master_include$");
1140 static const std::string_view WeMeantItString(
"We mean it.");
1143 static const std::regex BeginNamespaceRegex(
"^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1144 static const std::regex EndNamespaceRegex(
"^QT_END_NAMESPACE(_[A-Z_]+)?$");
1150 static const std::regex IncludeRegex(
"^#\\s*include\\s*[<\"](.+)[>\"]");
1153 static const std::regex NamespaceRegex(
"\\s*namespace ([^ ]*)\\s+");
1157 static const std::regex DeclareIteratorRegex(
"^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
1162 static const std::regex RequireConfigRegex(
"^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
1170 static const std::regex ElfVersionTagRegex(
".*ELFVERSION:(stop|ignore-next|ignore).*");
1172 std::ifstream input(headerFile, std::ifstream::in);
1173 if (!input.is_open()) {
1174 std::cerr <<
"Unable to open " << headerFile <<
std::endl;
1178 bool hasQtBeginNamespace =
false;
1179 std::string qtBeginNamespace;
1180 std::string qtEndNamespace;
1181 bool hasWeMeantIt =
false;
1182 bool isSuspended =
false;
1183 bool isMultiLineComment =
false;
1184 std::size_t bracesDepth = 0;
1185 std::size_t namespaceCount = 0;
1186 std::string namespaceString;
1192 std::string tmpLine;
1193 std::size_t linesProcessed = 0;
1196 const auto error = [&] () ->
decltype(
auto) {
1197 return std::cerr << ErrorMessagePreamble << m_currentFileString
1198 <<
":" << m_currentFileLineNumber <<
" ";
1202 while (
std::getline(input, tmpLine)) {
1203 ++m_currentFileLineNumber;
1204 line.append(tmpLine);
1205 if (line.empty() || line.at(line.size() - 1) ==
'\\') {
1209 buffer.reserve(line.size());
1214 for (
std::size_t i = 0; i < line.size(); ++i) {
1215 if (line[i] ==
'\r')
1217 if (bracesDepth == namespaceCount) {
1218 if (line[i] ==
'/') {
1219 if ((i + 1) < line.size()) {
1220 if (line[i + 1] ==
'*') {
1221 isMultiLineComment =
true;
1223 }
else if (line[i + 1] ==
'/') {
1224 if (!(skipChecks & WeMeantItChecks)
1225 && line.find(WeMeantItString) != std::string::npos) {
1226 hasWeMeantIt =
true;
1229 if (m_versionScriptGeneratorState != Stopped
1230 &&
std::regex_match(line, match, ElfVersionTagRegex)) {
1231 if (match[1].str() ==
"ignore")
1232 m_versionScriptGeneratorState = Ignore;
1233 else if (match[1].str() ==
"ignore-next")
1234 m_versionScriptGeneratorState = IgnoreNext;
1235 else if (match[1].str() ==
"stop")
1236 m_versionScriptGeneratorState = Stopped;
1241 }
else if (line[i] ==
'*' && (i + 1) < line.size() && line[i + 1] ==
'/') {
1243 isMultiLineComment =
false;
1248 if (isMultiLineComment) {
1249 if (!(skipChecks & WeMeantItChecks) &&
1250 line.find(WeMeantItString) != std::string::npos) {
1251 hasWeMeantIt =
true;
1257 if (line[i] ==
'{') {
1258 if (
std::regex_match(buffer, match, NamespaceRegex)) {
1260 namespaceString +=
"::";
1261 namespaceString += match[1].str();
1265 }
else if (line[i] ==
'}') {
1266 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1267 namespaceString.resize(namespaceString.rfind(
"::"));
1271 }
else if (bracesDepth == namespaceCount) {
1277 scannerDebug() << m_currentFilename <<
": " << buffer <<
std::endl;
1279 if (m_currentFileType & PrivateHeader) {
1289 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1290 || (m_currentFileType & SsgHeader);
1293 if (
std::regex_match(buffer, MacroRegex)) {
1294 if (
std::regex_match(buffer, SkipHeaderCheckRegex)) {
1297 }
else if (
std::regex_match(buffer, StopProcessingRegex)) {
1298 if (skipChecks == AllChecks)
1299 m_headerCheckExceptions.push_back(m_currentFileString);
1301 }
else if (
std::regex_match(buffer, SuspendProcessingRegex)) {
1303 }
else if (
std::regex_match(buffer, ResumeProcessingRegex)) {
1304 isSuspended =
false;
1305 }
else if (
std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
1307 updateSymbolDescriptor(match[1].str(), m_currentFilename,
1308 SymbolDescriptor::Pragma);
1312 }
else if (
std::regex_match(buffer, NoMasterIncludePragmaRegex)) {
1313 result.masterInclude =
false;
1314 }
else if (
std::regex_match(buffer, match, DeprecatesPragmaRegex)) {
1315 m_deprecatedHeaders[match[1].str()] =
1316 m_commandLineArgs->moduleName() +
'/' + m_currentFilename;
1317 }
else if (
std::regex_match(buffer, OnceRegex)) {
1320 error() <<
"\"#pragma once\" is not allowed in installed header files: "
1321 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1324 }
else if (
std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
1326 std::string includedHeader = match[1].str();
1330 .generic_string()
)) {
1332 error() <<
"includes private header " << includedHeader <<
std::endl;
1334 for (
const auto &module : m_commandLineArgs->knownModules()) {
1335 std::string suggestedHeader =
"Qt" + module +
'/' + includedHeader;
1336 const std::string suggestedHeaderReversePath =
"/../" + suggestedHeader;
1337 if (std::filesystem::exists(m_commandLineArgs->includeDir()
1338 + suggestedHeaderReversePath)
1339 || std::filesystem::exists(m_commandLineArgs->installIncludeDir()
1340 +
'/' + suggestedHeader)) {
1341 faults |= IncludeChecks;
1342 std::cerr << m_warningMessagePreamble << m_currentFileString
1343 <<
":" << m_currentFileLineNumber
1344 <<
" includes " << includedHeader
1345 <<
" when it should include "
1346 << suggestedHeader << std::endl;
1363 if (namespaceCount == 0
1364 ||
std::regex_match(namespaceString,
1367 SymbolDescriptor::Declaration
);
1370 }
else if (
std::regex_match(buffer, match, DeclareIteratorRegex)) {
1371 std::string iteratorSymbol = match[1].str() +
"Iterator";
1373 SymbolDescriptor::Declaration
);
1375 m_currentFilename
, SymbolDescriptor::Declaration
);
1377 }
else if (
std::regex_match(buffer, match, RequireConfigRegex)) {
1378 result.requireConfig = match[1].str();
1386 if (
std::regex_match(buffer, match, BeginNamespaceRegex)) {
1387 qtBeginNamespace = match[1].str();
1388 hasQtBeginNamespace =
true;
1389 }
else if (
std::regex_match(buffer, match, EndNamespaceRegex)) {
1390 qtEndNamespace = match[1].str();
1398 if (hasQtBeginNamespace) {
1399 if (qtBeginNamespace != qtEndNamespace) {
1401 std::cerr << m_warningMessagePreamble << m_currentFileString
1402 <<
" the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1403 <<
" doesn't match the end namespace macro QT_END_NAMESPACE"
1404 << qtEndNamespace << std::endl;
1408 std::cerr << m_warningMessagePreamble << m_currentFileString
1409 <<
" does not include QT_BEGIN_NAMESPACE" << std::endl;
1415 std::cerr << m_warningMessagePreamble << m_currentFileString
1416 <<
" does not have the \"We mean it.\" warning"
1420 scannerDebug() <<
"linesTotal: " << m_currentFileLineNumber
1421 <<
" linesProcessed: " << linesProcessed << std::endl;
1423 if (skipChecks == AllChecks)
1424 m_headerCheckExceptions.push_back(m_currentFileString);
1427 return !(faults & m_criticalChecks);
1570 static std::regex cIdentifierSymbolsRegex(
"[^a-zA-Z0-9_]");
1571 const std::string guard_base =
"DEPRECATED_HEADER_" + m_commandLineArgs
->moduleName();
1573 for (
auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) {
1574 const std::string &descriptor = it->first;
1575 const std::string &replacement = it->second;
1577 const auto separatorPos = descriptor.find(
',');
1578 std::string headerPath = descriptor.substr(0, separatorPos);
1579 std::string versionDisclaimer;
1580 if (separatorPos != std::string::npos) {
1581 std::string version = descriptor.substr(separatorPos + 1);
1582 versionDisclaimer =
" and will be removed in Qt " + version;
1586 std::cerr << ErrorMessagePreamble
1587 <<
"Invalid version format specified for the deprecated header file "
1588 << headerPath <<
": '" << version
1589 <<
"'. Expected format: 'major.minor'.\n";
1594 if (QT_VERSION_MAJOR > major
1595 || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) {
1596 std::cerr << WarningMessagePreamble << headerPath
1597 <<
" is marked as deprecated and will not be generated in Qt "
1599 <<
". The respective qt_deprecates pragma needs to be removed.\n";
1604 const auto moduleSeparatorPos = headerPath.find(
'/');
1605 std::string headerName = moduleSeparatorPos != std::string::npos
1606 ? headerPath.substr(moduleSeparatorPos + 1)
1608 const std::string moduleName = moduleSeparatorPos != std::string::npos
1609 ? headerPath.substr(0, moduleSeparatorPos)
1610 : m_commandLineArgs->moduleName();
1612 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs
->moduleName();
1614 std::string qualifiedHeaderName =
1615 std::regex_replace(headerName, cIdentifierSymbolsRegex,
"_");
1616 std::string guard = guard_base +
"_" + qualifiedHeaderName;
1617 std::string warningText =
"Header <" + moduleName +
"/" + headerName +
"> is deprecated"
1618 + versionDisclaimer +
". Please include <" + replacement +
"> instead.";
1619 std::stringstream buffer;
1620 buffer <<
"#ifndef " << guard <<
"\n"
1621 <<
"#define " << guard <<
"\n"
1622 <<
"#if defined(__GNUC__)\n"
1623 <<
"# warning " << warningText <<
"\n"
1624 <<
"#elif defined(_MSC_VER)\n"
1625 <<
"# pragma message (\"" << warningText <<
"\")\n"
1627 <<
"#include <" << replacement <<
">\n"
1630 const std::string outputDir = isCrossModuleDeprecation
1636 if (isCrossModuleDeprecation) {
1637 const std::string stagingDir = outputDir +
"/.syncqt_staging/";
1640 m_producedHeaders.insert(headerName);