686 m_warningMessagePreamble = ErrorMessagePreamble;
689 m_versionScriptGeneratorState =
696 for (
auto const &entry :
697 std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) {
699 const bool isRegularFile = entry.is_regular_file();
700 const bool isHeaderFlag = isHeader(entry);
701 const bool isDocFileHeuristicFlag =
702 isDocFileHeuristic(entry.path().generic_string());
703 const bool shouldProcessHeader =
704 isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag;
705 const std::string filePath = entry.path().generic_string();
707 if (shouldProcessHeader) {
708 scannerDebug() <<
"Processing header: " << filePath << std::endl;
709 if (!processHeader(makeHeaderAbsolute(filePath)))
713 <<
"Skipping processing header: " << filePath
714 <<
" isRegularFile: " << isRegularFile
715 <<
" isHeaderFlag: " << isHeaderFlag
716 <<
" isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
723 std::set<std::string> rspHeaders;
724 const auto &headers = m_commandLineArgs->headers();
725 for (
auto it = headers.begin(); it != headers.end(); ++it) {
726 const auto &header = *it;
727 scannerDebug() <<
"Processing header: " << header <<
std::endl;
732 for (
const auto &header : rspHeaders) {
733 scannerDebug() <<
"Processing header: " << header << std::endl;
734 if (!processHeader(makeHeaderAbsolute(header)))
744 for (
auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
745 const std::string &filename = it->second.file();
746 if (!filename.empty()) {
747 const auto camelCaseFile = m_commandLineArgs
->includeDir() +
'/' + it->first;
749 m_producedHeaders.insert(it->first);
750 m_moduleMapContents.insert({camelCaseFile, filename});
760 std::string versionHeaderFilename(moduleNameLower +
"version.h");
762 std::string versionFile = m_commandLineArgs
->includeDir() +
'/' + versionHeaderFilename;
765 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
767 originalStamp = FileStamp::clock::now();
770 const auto camelCaseFile =
772 if (!generateAliasedHeaderFileIfTimestampChanged(camelCaseFile,
773 versionFile, originalStamp)) {
776 m_masterHeaderContents[versionHeaderFilename] = {};
777 m_producedHeaders.insert(versionHeaderFilename);
778 m_producedHeaders.insert(versionHeaderCamel);
779 m_moduleMapContents.insert({versionFile, {}});
780 m_moduleMapContents.insert({camelCaseFile, versionFile});
824 for (
const auto &header : m_publicHeaders) {
825 if (!generateForwardingHeader(m_commandLineArgs->stagingDir() +
'/' + header,
918 static const std::regex ThirdPartyFolderRegex(
"(^|.+/)3rdparty/.+");
921 static const std::regex ConfigHeaderRegex(
"^(q|.+-)config(_p)?\\.h");
925 bool isPrivate = m_currentFileType & PrivateHeader;
926 bool isQpa = m_currentFileType & QpaHeader;
927 bool isRhi = m_currentFileType & RhiHeader;
928 bool isSsg = m_currentFileType & SsgHeader;
929 bool isSpi = m_currentFileType & SpiHeader;
930 bool isExport = m_currentFileType & ExportHeader;
931 bool isPublic = !isPrivate && !isQpa && !isRhi && !isSsg && !isSpi;
935 if (!m_currentFileInSourceDir
936 && m_currentFileString.find(m_commandLineArgs
->binaryDir()) != 0) {
937 scannerDebug() <<
"Header file: " << headerFile
938 <<
" is outside the sync directories. Skipping." <<
std::endl;
939 m_headerCheckExceptions.push_back(m_currentFileString);
946 if (m_currentFileString.find(m_commandLineArgs
->includeDir()) == 0) {
947 m_moduleMapContents.insert({m_currentFileString, {}});
949 if (m_commandLineArgs->isFramework() && isPublic)
950 m_publicHeaders.insert(m_currentFilename);
957 if (m_currentFilename.empty()) {
958 std::cerr <<
"Header file name of " << m_currentFileString <<
"is empty" <<
std::endl;
963 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
965 originalStamp = FileStamp::clock::now();
969 <<
"processHeader:start: " << headerFile
970 <<
" m_currentFilename: " << m_currentFilename
971 <<
" isPrivate: " << isPrivate
972 <<
" isQpa: " << isQpa
973 <<
" isRhi: " << isRhi
974 <<
" isSsg: " << isSsg
975 <<
" isSpi: " << isSpi
976 <<
" isExport: " << isExport
993 if (!utils::createDirectories(outputDir,
"Unable to create output directory"))
996 bool headerFileExists =
std::filesystem::exists(headerFile);
998 std::string aliasedFilepath = headerFile.generic_string();
1000 std::string aliasPath = outputDir +
'/' + m_currentFilename;
1005 if (m_commandLineArgs
->copy() && headerFileExists) {
1009 if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
1018 m_publicHeaders.insert(m_currentFilename);
1029 if (!headerFileExists) {
1030 scannerDebug() <<
"Header file: " << headerFile
1031 <<
" doesn't exist, but is added to syncqt scanning. Skipping.";
1039 bool is3rdParty =
std::regex_match(
1042 ThirdPartyFolderRegex);
1045 if (!
std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
1050 || isSpi || !m_currentFileInSourceDir || isGenerated) {
1068 ParsingResult parsingResult;
1069 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
1070 && isPublic && !isGenerated;
1072 scannerDebug() <<
"parseHeader failed: " << headerFile <<
std::endl;
1078 && !parsingResult.versionScriptContent.empty()) {
1079 m_versionScriptContents.insert(m_versionScriptContents.end(),
1080 parsingResult.versionScriptContent.begin(),
1081 parsingResult.versionScriptContent.end());
1086 bool willBeInModuleMasterHeader =
false;
1088 if (m_currentFilename.find(
'_') == std::string::npos
1089 && parsingResult.masterInclude) {
1090 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
1091 willBeInModuleMasterHeader =
true;
1096 <<
"processHeader:end: " << headerFile
1097 <<
" is3rdParty: " << is3rdParty
1098 <<
" isGenerated: " << isGenerated
1099 <<
" m_currentFileInSourceDir: " << m_currentFileInSourceDir
1100 <<
" willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
1102 }
else if (m_currentFilename ==
"qconfig.h") {
1107 m_moduleMapContents.insert({outputDir +
"/" + m_currentFilename, {}});
1115 static const std::regex VersionScriptSymbolRegex(
1116 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1119 static const std::regex VersionScriptNamespaceRegex(
1120 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1123 static const std::regex TrailingColonRegex(
"([\\w]+):$");
1125 switch (m_versionScriptGeneratorState) {
1127 scannerDebug() <<
"line ignored: " << buffer <<
std::endl;
1128 m_versionScriptGeneratorState = Active;
1133 m_versionScriptGeneratorState = Ignore;
1144 if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
1145 symbol = match[1].str();
1146 else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex))
1147 symbol = match[1].str();
1149 if (std::regex_match(symbol, match, TrailingColonRegex))
1150 symbol = match[1].str();
1153 if (!symbol.empty() && symbol[symbol.size() - 1] !=
';') {
1154 std::string relPath = m_currentFileInSourceDir
1160 std::string versionStringRecord =
" *";
1161 size_t startPos = 0;
1163 while (endPos != std::string::npos) {
1164 endPos = symbol.find(
"::", startPos);
1165 size_t length = endPos != std::string::npos ? (endPos - startPos)
1166 : (symbol.size() - startPos);
1168 std::string symbolPart = symbol.substr(startPos, length);
1169 versionStringRecord +=
std::to_string(symbolPart.size());
1170 versionStringRecord += symbolPart;
1172 startPos = endPos + 2;
1174 versionStringRecord +=
"*;";
1176 versionStringRecord +=
1178 versionStringRecord +=
" # ";
1179 versionStringRecord += relPath;
1180 versionStringRecord +=
":";
1181 versionStringRecord +=
std::to_string(m_currentFileLineNumber);
1182 versionStringRecord +=
"\n";
1183 result.versionScriptContent.push_back(versionStringRecord);
1192 ParsingResult &result,
1193 unsigned int skipChecks)
1198 static const std::regex MacroRegex(
"^\\s*#.*");
1231 static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)");
1232 static const std::regex SkipHeaderCheckRegex(
"^#\\s*pragma qt_sync_skip_header_check$");
1233 static const std::regex StopProcessingRegex(
"^#\\s*pragma qt_sync_stop_processing$");
1234 static const std::regex SuspendProcessingRegex(
"^#\\s*pragma qt_sync_suspend_processing$");
1235 static const std::regex ResumeProcessingRegex(
"^#\\s*pragma qt_sync_resume_processing$");
1236 static const std::regex ExplixitClassPragmaRegex(
"^#\\s*pragma qt_class\\(([^\\)]+)\\)$");
1237 static const std::regex DeprecatesPragmaRegex(
"^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$");
1238 static const std::regex NoMasterIncludePragmaRegex(
"^#\\s*pragma qt_no_master_include$");
1242 static const std::string_view WeMeantItString(
"We mean it.");
1245 static const std::regex BeginNamespaceRegex(
"^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1246 static const std::regex EndNamespaceRegex(
"^QT_END_NAMESPACE(_[A-Z_]+)?$");
1252 static const std::regex IncludeRegex(
"^#\\s*include\\s*[<\"](.+)[>\"]");
1255 static const std::regex NamespaceRegex(
"\\s*namespace ([^ ]*)\\s+");
1259 static const std::regex DeclareIteratorRegex(
"^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
1264 static const std::regex RequireConfigRegex(
"^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
1272 static const std::regex ElfVersionTagRegex(
".*ELFVERSION:(stop|ignore-next|ignore).*");
1274 std::ifstream input(headerFile, std::ifstream::in);
1275 if (!input.is_open()) {
1276 std::cerr <<
"Unable to open " << headerFile <<
std::endl;
1280 bool hasQtBeginNamespace =
false;
1281 std::string qtBeginNamespace;
1282 std::string qtEndNamespace;
1283 bool hasWeMeantIt =
false;
1284 bool isSuspended =
false;
1285 bool isMultiLineComment =
false;
1286 std::size_t bracesDepth = 0;
1287 std::size_t namespaceCount = 0;
1288 std::string namespaceString;
1294 std::string tmpLine;
1295 std::size_t linesProcessed = 0;
1298 const auto error = [&] () ->
decltype(
auto) {
1299 return std::cerr << ErrorMessagePreamble << m_currentFileString
1300 <<
":" << m_currentFileLineNumber <<
" ";
1304 while (
std::getline(input, tmpLine)) {
1305 ++m_currentFileLineNumber;
1306 line.append(tmpLine);
1307 if (line.empty() || line.at(line.size() - 1) ==
'\\') {
1311 buffer.reserve(line.size());
1316 for (
std::size_t i = 0; i < line.size(); ++i) {
1317 if (line[i] ==
'\r')
1319 if (bracesDepth == namespaceCount) {
1320 if (line[i] ==
'/') {
1321 if ((i + 1) < line.size()) {
1322 if (line[i + 1] ==
'*') {
1323 isMultiLineComment =
true;
1325 }
else if (line[i + 1] ==
'/') {
1326 if (!(skipChecks & WeMeantItChecks)
1327 && line.find(WeMeantItString) != std::string::npos) {
1328 hasWeMeantIt =
true;
1331 if (m_versionScriptGeneratorState != Stopped
1332 &&
std::regex_match(line, match, ElfVersionTagRegex)) {
1333 if (match[1].str() ==
"ignore")
1334 m_versionScriptGeneratorState = Ignore;
1335 else if (match[1].str() ==
"ignore-next")
1336 m_versionScriptGeneratorState = IgnoreNext;
1337 else if (match[1].str() ==
"stop")
1338 m_versionScriptGeneratorState = Stopped;
1343 }
else if (line[i] ==
'*' && (i + 1) < line.size() && line[i + 1] ==
'/') {
1345 isMultiLineComment =
false;
1350 if (isMultiLineComment) {
1351 if (!(skipChecks & WeMeantItChecks) &&
1352 line.find(WeMeantItString) != std::string::npos) {
1353 hasWeMeantIt =
true;
1359 if (line[i] ==
'{') {
1360 if (
std::regex_match(buffer, match, NamespaceRegex)) {
1362 namespaceString +=
"::";
1363 namespaceString += match[1].str();
1367 }
else if (line[i] ==
'}') {
1368 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1369 namespaceString.resize(namespaceString.rfind(
"::"));
1373 }
else if (bracesDepth == namespaceCount) {
1379 scannerDebug() << m_currentFilename <<
": " << buffer <<
std::endl;
1381 if (m_currentFileType & PrivateHeader) {
1391 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1392 || (m_currentFileType & SsgHeader) || (m_currentFileType & SpiHeader);
1395 if (
std::regex_match(buffer, MacroRegex)) {
1396 if (
std::regex_match(buffer, SkipHeaderCheckRegex)) {
1399 }
else if (
std::regex_match(buffer, StopProcessingRegex)) {
1400 if (skipChecks == AllChecks)
1401 m_headerCheckExceptions.push_back(m_currentFileString);
1403 }
else if (
std::regex_match(buffer, SuspendProcessingRegex)) {
1405 }
else if (
std::regex_match(buffer, ResumeProcessingRegex)) {
1406 isSuspended =
false;
1407 }
else if (
std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
1410 SymbolDescriptor::Pragma);
1414 }
else if (
std::regex_match(buffer, NoMasterIncludePragmaRegex)) {
1415 result.masterInclude =
false;
1416 }
else if (
std::regex_match(buffer, match, DeprecatesPragmaRegex)) {
1417 m_deprecatedHeaders[match[1].str()] =
1418 m_commandLineArgs->moduleName() +
'/' + m_currentFilename;
1419 }
else if (
std::regex_match(buffer, OnceRegex)) {
1422 error() <<
"\"#pragma once\" is not allowed in installed header files: "
1423 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1426 }
else if (
std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
1428 std::string includedHeader = match[1].str();
1432 .generic_string()
)) {
1434 error() <<
"includes private header " << includedHeader <<
std::endl;
1436 for (
const auto &module : m_commandLineArgs->knownModules()) {
1437 std::string suggestedHeader =
"Qt" + module +
'/' + includedHeader;
1438 const std::string suggestedHeaderReversePath =
"/../" + suggestedHeader;
1439 if (std::filesystem::exists(m_commandLineArgs->includeDir()
1440 + suggestedHeaderReversePath)
1441 || std::filesystem::exists(m_commandLineArgs->installIncludeDir()
1442 +
'/' + suggestedHeader)) {
1443 faults |= IncludeChecks;
1444 std::cerr << m_warningMessagePreamble << m_currentFileString
1445 <<
":" << m_currentFileLineNumber
1446 <<
" includes " << includedHeader
1447 <<
" when it should include "
1448 << suggestedHeader << std::endl;
1465 if (namespaceCount == 0
1466 ||
std::regex_match(namespaceString,
1469 SymbolDescriptor::Declaration
);
1472 }
else if (
std::regex_match(buffer, match, DeclareIteratorRegex)) {
1473 std::string iteratorSymbol = match[1].str() +
"Iterator";
1475 SymbolDescriptor::Declaration
);
1477 m_currentFilename
, SymbolDescriptor::Declaration
);
1479 }
else if (
std::regex_match(buffer, match, RequireConfigRegex)) {
1480 result.requireConfig = match[1].str();
1488 if (
std::regex_match(buffer, match, BeginNamespaceRegex)) {
1489 qtBeginNamespace = match[1].str();
1490 hasQtBeginNamespace =
true;
1491 }
else if (
std::regex_match(buffer, match, EndNamespaceRegex)) {
1492 qtEndNamespace = match[1].str();
1500 if (hasQtBeginNamespace) {
1501 if (qtBeginNamespace != qtEndNamespace) {
1503 std::cerr << m_warningMessagePreamble << m_currentFileString
1504 <<
" the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1505 <<
" doesn't match the end namespace macro QT_END_NAMESPACE"
1506 << qtEndNamespace << std::endl;
1510 std::cerr << m_warningMessagePreamble << m_currentFileString
1511 <<
" does not include QT_BEGIN_NAMESPACE" << std::endl;
1517 std::cerr << m_warningMessagePreamble << m_currentFileString
1518 <<
" does not have the \"We mean it.\" warning"
1522 scannerDebug() <<
"linesTotal: " << m_currentFileLineNumber
1523 <<
" linesProcessed: " << linesProcessed << std::endl;
1525 if (skipChecks == AllChecks)
1526 m_headerCheckExceptions.push_back(m_currentFileString);
1529 return !(faults & m_criticalChecks);
1696 static std::regex cIdentifierSymbolsRegex(
"[^a-zA-Z0-9_]");
1697 const std::string guard_base =
"DEPRECATED_HEADER_" + m_commandLineArgs
->moduleName();
1699 for (
auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) {
1700 const std::string &descriptor = it->first;
1701 const std::string &replacement = it->second;
1703 const auto separatorPos = descriptor.find(
',');
1704 std::string headerPath = descriptor.substr(0, separatorPos);
1705 std::string versionDisclaimer;
1706 if (separatorPos != std::string::npos) {
1707 std::string version = descriptor.substr(separatorPos + 1);
1708 versionDisclaimer =
" and will be removed in Qt " + version;
1712 std::cerr << ErrorMessagePreamble
1713 <<
"Invalid version format specified for the deprecated header file "
1714 << headerPath <<
": '" << version
1715 <<
"'. Expected format: 'major.minor'.\n";
1720 if (QT_VERSION_MAJOR > major
1721 || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) {
1722 std::cerr << WarningMessagePreamble << headerPath
1723 <<
" is marked as deprecated and will not be generated in Qt "
1725 <<
". The respective qt_deprecates pragma needs to be removed.\n";
1730 const auto moduleSeparatorPos = headerPath.find(
'/');
1731 std::string headerName = moduleSeparatorPos != std::string::npos
1732 ? headerPath.substr(moduleSeparatorPos + 1)
1734 const std::string moduleName = moduleSeparatorPos != std::string::npos
1735 ? headerPath.substr(0, moduleSeparatorPos)
1736 : m_commandLineArgs->moduleName();
1738 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs
->moduleName();
1740 std::string qualifiedHeaderName =
1741 std::regex_replace(headerName, cIdentifierSymbolsRegex,
"_");
1742 std::string guard = guard_base +
"_" + qualifiedHeaderName;
1743 std::string warningText =
"Header <" + moduleName +
"/" + headerName +
"> is deprecated"
1744 + versionDisclaimer +
". Please include <" + replacement +
"> instead.";
1745 std::stringstream buffer;
1746 buffer <<
"#ifndef " << guard <<
"\n"
1747 <<
"#define " << guard <<
"\n"
1748 <<
"#if defined(__GNUC__)\n"
1749 <<
"# warning " << warningText <<
"\n"
1750 <<
"#elif defined(_MSC_VER)\n"
1751 <<
"# pragma message (\"" << warningText <<
"\")\n"
1753 <<
"#include <" << replacement <<
">\n"
1756 const std::string outputDir = isCrossModuleDeprecation
1762 if (isCrossModuleDeprecation) {
1763 const std::string stagingDir = outputDir +
"/.syncqt_staging/";
1766 m_producedHeaders.insert(headerName);