686 m_warningMessagePreamble = ErrorMessagePreamble;
689 m_versionScriptGeneratorState =
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();
702 const bool isGenerated = origin == HeaderOrigin::Generated;
704 if (isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag) {
705 scannerDebug() <<
"Processing header: " << filePath
706 <<
" isGenerated: " << isGenerated <<
std::endl;
711 <<
"Skipping processing header: " << filePath
712 <<
" isRegularFile: " << isRegularFile
713 <<
" isHeaderFlag: " << isHeaderFlag
714 <<
" isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
715 <<
" isGenerated: " << isGenerated
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);
728 for (
const auto &header : m_commandLineArgs->generatedHeaders()) {
731 const auto headerPath = makeHeaderAbsolute(header);
732 scanHeaderFile(headerPath, std::filesystem::is_regular_file(headerPath),
733 HeaderOrigin::Generated);
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;
747 for (
const auto &header : rspHeaders) {
748 scannerDebug() <<
"Processing header: " << header << std::endl;
749 if (!processHeader(makeHeaderAbsolute(header)))
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;
764 m_producedHeaders.insert(it->first);
765 m_moduleMapContents.insert({camelCaseFile, filename});
775 std::string versionHeaderFilename(moduleNameLower +
"version.h");
777 std::string versionFile = m_commandLineArgs
->includeDir() +
'/' + versionHeaderFilename;
780 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
782 originalStamp = FileStamp::clock::now();
785 const auto camelCaseFile =
787 if (!generateAliasedHeaderFileIfTimestampChanged(camelCaseFile,
788 versionFile, originalStamp)) {
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});
839 for (
const auto &header : m_publicHeaders) {
840 if (!generateForwardingHeader(m_commandLineArgs->stagingDir() +
'/' + header,
933 static const std::regex ThirdPartyFolderRegex(
"(^|.+/)3rdparty/.+");
936 static const std::regex ConfigHeaderRegex(
"^(q|.+-)config(_p)?\\.h");
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;
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);
961 if (m_currentFileString.find(m_commandLineArgs
->includeDir()) == 0) {
962 m_moduleMapContents.insert({m_currentFileString, {}});
964 if (m_commandLineArgs->isFramework() && isPublic)
965 m_publicHeaders.insert(m_currentFilename);
972 if (m_currentFilename.empty()) {
973 std::cerr <<
"Header file name of " << m_currentFileString <<
"is empty" <<
std::endl;
978 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
980 originalStamp = FileStamp::clock::now();
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
1008 if (!utils::createDirectories(outputDir,
"Unable to create output directory"))
1011 bool headerFileExists =
std::filesystem::exists(headerFile);
1013 std::string aliasedFilepath = headerFile.generic_string();
1015 std::string aliasPath = outputDir +
'/' + m_currentFilename;
1020 if (m_commandLineArgs
->copy() && headerFileExists) {
1024 if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
1033 m_publicHeaders.insert(m_currentFilename);
1044 if (!headerFileExists) {
1045 scannerDebug() <<
"Header file: " << headerFile
1046 <<
" doesn't exist, but is added to syncqt scanning. Skipping.";
1054 bool is3rdParty =
std::regex_match(
1057 ThirdPartyFolderRegex);
1060 if (!
std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
1065 || isSpi || !m_currentFileInSourceDir || isGenerated) {
1083 ParsingResult parsingResult;
1084 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
1085 && isPublic && !isGenerated;
1087 scannerDebug() <<
"parseHeader failed: " << headerFile <<
std::endl;
1093 && !parsingResult.versionScriptContent.empty()) {
1094 m_versionScriptContents.insert(m_versionScriptContents.end(),
1095 parsingResult.versionScriptContent.begin(),
1096 parsingResult.versionScriptContent.end());
1101 bool willBeInModuleMasterHeader =
false;
1103 if (m_currentFilename.find(
'_') == std::string::npos
1104 && parsingResult.masterInclude) {
1105 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
1106 willBeInModuleMasterHeader =
true;
1111 <<
"processHeader:end: " << headerFile
1112 <<
" is3rdParty: " << is3rdParty
1113 <<
" isGenerated: " << isGenerated
1114 <<
" m_currentFileInSourceDir: " << m_currentFileInSourceDir
1115 <<
" willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
1117 }
else if (m_currentFilename ==
"qconfig.h") {
1122 m_moduleMapContents.insert({outputDir +
"/" + m_currentFilename, {}});
1130 static const std::regex VersionScriptSymbolRegex(
1131 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1134 static const std::regex VersionScriptNamespaceRegex(
1135 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1138 static const std::regex TrailingColonRegex(
"([\\w]+):$");
1140 switch (m_versionScriptGeneratorState) {
1142 scannerDebug() <<
"line ignored: " << buffer <<
std::endl;
1143 m_versionScriptGeneratorState = Active;
1148 m_versionScriptGeneratorState = Ignore;
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();
1164 if (std::regex_match(symbol, match, TrailingColonRegex))
1165 symbol = match[1].str();
1168 if (!symbol.empty() && symbol[symbol.size() - 1] !=
';') {
1169 std::string relPath = m_currentFileInSourceDir
1175 std::string versionStringRecord =
" *";
1176 size_t startPos = 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);
1183 std::string symbolPart = symbol.substr(startPos, length);
1184 versionStringRecord +=
std::to_string(symbolPart.size());
1185 versionStringRecord += symbolPart;
1187 startPos = endPos + 2;
1189 versionStringRecord +=
"*;";
1191 versionStringRecord +=
1193 versionStringRecord +=
" # ";
1194 versionStringRecord += relPath;
1195 versionStringRecord +=
":";
1196 versionStringRecord +=
std::to_string(m_currentFileLineNumber);
1197 versionStringRecord +=
"\n";
1198 result.versionScriptContent.push_back(versionStringRecord);
1207 ParsingResult &result,
1208 unsigned int skipChecks)
1213 static const std::regex MacroRegex(
"^\\s*#.*");
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$");
1257 static const std::string_view WeMeantItString(
"We mean it.");
1260 static const std::regex BeginNamespaceRegex(
"^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1261 static const std::regex EndNamespaceRegex(
"^QT_END_NAMESPACE(_[A-Z_]+)?$");
1267 static const std::regex IncludeRegex(
"^#\\s*include\\s*[<\"](.+)[>\"]");
1270 static const std::regex NamespaceRegex(
"\\s*namespace ([^ ]*)\\s+");
1274 static const std::regex DeclareIteratorRegex(
"^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
1279 static const std::regex RequireConfigRegex(
"^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
1287 static const std::regex ElfVersionTagRegex(
".*ELFVERSION:(stop|ignore-next|ignore).*");
1289 std::ifstream input(headerFile, std::ifstream::in);
1290 if (!input.is_open()) {
1291 std::cerr <<
"Unable to open " << headerFile <<
std::endl;
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;
1309 std::string tmpLine;
1310 std::size_t linesProcessed = 0;
1313 const auto error = [&] () ->
decltype(
auto) {
1314 return std::cerr << ErrorMessagePreamble << m_currentFileString
1315 <<
":" << m_currentFileLineNumber <<
" ";
1319 while (
std::getline(input, tmpLine)) {
1320 ++m_currentFileLineNumber;
1321 line.append(tmpLine);
1322 if (line.empty() || line.at(line.size() - 1) ==
'\\') {
1326 buffer.reserve(line.size());
1331 for (
std::size_t i = 0; i < line.size(); ++i) {
1332 if (line[i] ==
'\r')
1334 if (bracesDepth == namespaceCount) {
1335 if (line[i] ==
'/') {
1336 if ((i + 1) < line.size()) {
1337 if (line[i + 1] ==
'*') {
1338 isMultiLineComment =
true;
1340 }
else if (line[i + 1] ==
'/') {
1341 if (!(skipChecks & WeMeantItChecks)
1342 && line.find(WeMeantItString) != std::string::npos) {
1343 hasWeMeantIt =
true;
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;
1358 }
else if (line[i] ==
'*' && (i + 1) < line.size() && line[i + 1] ==
'/') {
1360 isMultiLineComment =
false;
1365 if (isMultiLineComment) {
1366 if (!(skipChecks & WeMeantItChecks) &&
1367 line.find(WeMeantItString) != std::string::npos) {
1368 hasWeMeantIt =
true;
1374 if (line[i] ==
'{') {
1375 if (
std::regex_match(buffer, match, NamespaceRegex)) {
1377 namespaceString +=
"::";
1378 namespaceString += match[1].str();
1382 }
else if (line[i] ==
'}') {
1383 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1384 namespaceString.resize(namespaceString.rfind(
"::"));
1388 }
else if (bracesDepth == namespaceCount) {
1394 scannerDebug() << m_currentFilename <<
": " << buffer <<
std::endl;
1396 if (m_currentFileType & PrivateHeader) {
1406 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1407 || (m_currentFileType & SsgHeader) || (m_currentFileType & SpiHeader);
1410 if (
std::regex_match(buffer, MacroRegex)) {
1411 if (
std::regex_match(buffer, SkipHeaderCheckRegex)) {
1414 }
else if (
std::regex_match(buffer, StopProcessingRegex)) {
1415 if (skipChecks == AllChecks)
1416 m_headerCheckExceptions.push_back(m_currentFileString);
1418 }
else if (
std::regex_match(buffer, SuspendProcessingRegex)) {
1420 }
else if (
std::regex_match(buffer, ResumeProcessingRegex)) {
1421 isSuspended =
false;
1422 }
else if (
std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
1425 SymbolDescriptor::Pragma);
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)) {
1437 error() <<
"\"#pragma once\" is not allowed in installed header files: "
1438 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1441 }
else if (
std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
1443 std::string includedHeader = match[1].str();
1447 .generic_string()
)) {
1449 error() <<
"includes private header " << includedHeader <<
std::endl;
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;
1480 if (namespaceCount == 0
1481 ||
std::regex_match(namespaceString,
1484 SymbolDescriptor::Declaration
);
1487 }
else if (
std::regex_match(buffer, match, DeclareIteratorRegex)) {
1488 std::string iteratorSymbol = match[1].str() +
"Iterator";
1490 SymbolDescriptor::Declaration
);
1492 m_currentFilename
, SymbolDescriptor::Declaration
);
1494 }
else if (
std::regex_match(buffer, match, RequireConfigRegex)) {
1495 result.requireConfig = match[1].str();
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();
1515 if (hasQtBeginNamespace) {
1516 if (qtBeginNamespace != qtEndNamespace) {
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;
1525 std::cerr << m_warningMessagePreamble << m_currentFileString
1526 <<
" does not include QT_BEGIN_NAMESPACE" << std::endl;
1532 std::cerr << m_warningMessagePreamble << m_currentFileString
1533 <<
" does not have the \"We mean it.\" warning"
1537 scannerDebug() <<
"linesTotal: " << m_currentFileLineNumber
1538 <<
" linesProcessed: " << linesProcessed << std::endl;
1540 if (skipChecks == AllChecks)
1541 m_headerCheckExceptions.push_back(m_currentFileString);
1544 return !(faults & m_criticalChecks);
1711 static std::regex cIdentifierSymbolsRegex(
"[^a-zA-Z0-9_]");
1712 const std::string guard_base =
"DEPRECATED_HEADER_" + m_commandLineArgs
->moduleName();
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;
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;
1727 std::cerr << ErrorMessagePreamble
1728 <<
"Invalid version format specified for the deprecated header file "
1729 << headerPath <<
": '" << version
1730 <<
"'. Expected format: 'major.minor'.\n";
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 "
1740 <<
". The respective qt_deprecates pragma needs to be removed.\n";
1745 const auto moduleSeparatorPos = headerPath.find(
'/');
1746 std::string headerName = moduleSeparatorPos != std::string::npos
1747 ? headerPath.substr(moduleSeparatorPos + 1)
1749 const std::string moduleName = moduleSeparatorPos != std::string::npos
1750 ? headerPath.substr(0, moduleSeparatorPos)
1751 : m_commandLineArgs->moduleName();
1753 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs
->moduleName();
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"
1768 <<
"#include <" << replacement <<
">\n"
1771 const std::string outputDir = isCrossModuleDeprecation
1777 if (isCrossModuleDeprecation) {
1778 const std::string stagingDir = outputDir +
"/.syncqt_staging/";
1781 m_producedHeaders.insert(headerName);