740 m_warningMessagePreamble = ErrorMessagePreamble;
743 m_versionScriptGeneratorState =
750 enum class HeaderOrigin { SourceTree, Generated };
751 const auto scanHeaderFile = [
this, &error](
const std::filesystem::path &path,
752 bool isRegularFile, HeaderOrigin origin) {
753 const std::string filePath = path.generic_string();
756 const bool isGenerated = origin == HeaderOrigin::Generated;
758 if (isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag) {
759 scannerDebug() <<
"Processing header: " << filePath
760 <<
" isGenerated: " << isGenerated <<
std::endl;
765 <<
"Skipping processing header: " << filePath
766 <<
" isRegularFile: " << isRegularFile
767 <<
" isHeaderFlag: " << isHeaderFlag
768 <<
" isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
769 <<
" isGenerated: " << isGenerated
774 for (
auto const &entry :
775 std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) {
776 scanHeaderFile(entry.path(), entry.is_regular_file(),
777 HeaderOrigin::SourceTree);
782 for (
const auto &header : m_commandLineArgs->generatedHeaders()) {
785 const auto headerPath = makeHeaderAbsolute(header);
786 scanHeaderFile(headerPath, std::filesystem::is_regular_file(headerPath),
787 HeaderOrigin::Generated);
792 std::set<std::string> rspHeaders;
793 const auto &headers = m_commandLineArgs->headers();
794 for (
auto it = headers.begin(); it != headers.end(); ++it) {
795 const auto &header = *it;
796 scannerDebug() <<
"Processing header: " << header <<
std::endl;
801 for (
const auto &header : rspHeaders) {
802 scannerDebug() <<
"Processing header: " << header << std::endl;
803 if (!processHeader(makeHeaderAbsolute(header)))
813 for (
auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
814 const std::string &filename = it->second.file();
815 if (!filename.empty()) {
816 const auto camelCaseFile = m_commandLineArgs
->includeDir() +
'/' + it->first;
818 m_producedHeaders.insert(it->first);
819 m_moduleMapContents.insert({camelCaseFile, filename});
829 std::string versionHeaderFilename(moduleNameLower +
"version.h");
831 std::string versionFile = m_commandLineArgs
->includeDir() +
'/' + versionHeaderFilename;
834 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
836 originalStamp = FileStamp::clock::now();
839 const auto camelCaseFile =
841 if (!generateAliasedHeaderFileIfTimestampChanged(camelCaseFile,
842 versionFile, originalStamp)) {
845 m_masterHeaderContents[versionHeaderFilename] = {};
846 m_producedHeaders.insert(versionHeaderFilename);
847 m_producedHeaders.insert(versionHeaderCamel);
848 m_moduleMapContents.insert({versionFile, {}});
849 m_moduleMapContents.insert({camelCaseFile, versionFile});
893 for (
const auto &header : m_publicHeaders) {
894 if (!generateForwardingHeader(m_commandLineArgs->stagingDir() +
'/' + header,
987 static const std::regex ThirdPartyFolderRegex(
"(^|.+/)3rdparty/.+");
990 static const std::regex ConfigHeaderRegex(
"^(q|.+-)config(_p)?\\.h");
994 bool isPrivate = m_currentFileType & PrivateHeader;
995 bool isQpa = m_currentFileType & QpaHeader;
996 bool isRhi = m_currentFileType & RhiHeader;
997 bool isSsg = m_currentFileType & SsgHeader;
998 bool isSpi = m_currentFileType & SpiHeader;
999 bool isExport = m_currentFileType & ExportHeader;
1000 bool isPublic = !isPrivate && !isQpa && !isRhi && !isSsg && !isSpi;
1004 if (!m_currentFileInSourceDir
1005 && m_currentFileString.find(m_commandLineArgs
->binaryDir()) != 0) {
1006 scannerDebug() <<
"Header file: " << headerFile
1007 <<
" is outside the sync directories. Skipping." <<
std::endl;
1008 m_headerCheckExceptions.push_back(m_currentFileString);
1015 if (m_currentFileString.find(m_commandLineArgs
->includeDir()) == 0) {
1016 m_moduleMapContents.insert({m_currentFileString, {}});
1018 if (m_commandLineArgs->isFramework() && isPublic)
1019 m_publicHeaders.insert(m_currentFilename);
1026 if (m_currentFilename.empty()) {
1027 std::cerr <<
"Header file name of " << m_currentFileString <<
"is empty" <<
std::endl;
1032 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
1034 originalStamp = FileStamp::clock::now();
1038 <<
"processHeader:start: " << headerFile
1039 <<
" m_currentFilename: " << m_currentFilename
1040 <<
" isPrivate: " << isPrivate
1041 <<
" isQpa: " << isQpa
1042 <<
" isRhi: " << isRhi
1043 <<
" isSsg: " << isSsg
1044 <<
" isSpi: " << isSpi
1045 <<
" isExport: " << isExport
1062 if (!utils::createDirectories(outputDir,
"Unable to create output directory"))
1065 bool headerFileExists =
std::filesystem::exists(headerFile);
1067 std::string aliasedFilepath = headerFile.generic_string();
1069 std::string aliasPath = outputDir +
'/' + m_currentFilename;
1074 if (m_commandLineArgs
->copy() && headerFileExists) {
1078 if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
1087 m_publicHeaders.insert(m_currentFilename);
1098 if (!headerFileExists) {
1099 scannerDebug() <<
"Header file: " << headerFile
1100 <<
" doesn't exist, but is added to syncqt scanning. Skipping.";
1108 bool is3rdParty =
std::regex_match(
1111 ThirdPartyFolderRegex);
1114 if (!
std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
1119 || isSpi || !m_currentFileInSourceDir || isGenerated) {
1137 ParsingResult parsingResult;
1138 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
1139 && isPublic && !isGenerated;
1141 scannerDebug() <<
"parseHeader failed: " << headerFile <<
std::endl;
1147 && !parsingResult.versionScriptContent.empty()) {
1148 m_versionScriptContents.insert(m_versionScriptContents.end(),
1149 parsingResult.versionScriptContent.begin(),
1150 parsingResult.versionScriptContent.end());
1155 bool willBeInModuleMasterHeader =
false;
1157 if (m_currentFilename.find(
'_') == std::string::npos
1158 && parsingResult.masterInclude) {
1159 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
1160 willBeInModuleMasterHeader =
true;
1165 <<
"processHeader:end: " << headerFile
1166 <<
" is3rdParty: " << is3rdParty
1167 <<
" isGenerated: " << isGenerated
1168 <<
" m_currentFileInSourceDir: " << m_currentFileInSourceDir
1169 <<
" willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
1171 }
else if (m_currentFilename ==
"qconfig.h") {
1176 m_moduleMapContents.insert({outputDir +
"/" + m_currentFilename, {}});
1184 static const std::regex VersionScriptSymbolRegex(
1185 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1188 static const std::regex VersionScriptNamespaceRegex(
1189 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1192 static const std::regex TrailingColonRegex(
"([\\w]+):$");
1194 switch (m_versionScriptGeneratorState) {
1196 scannerDebug() <<
"line ignored: " << buffer <<
std::endl;
1197 m_versionScriptGeneratorState = Active;
1202 m_versionScriptGeneratorState = Ignore;
1213 if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
1214 symbol = match[1].str();
1215 else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex))
1216 symbol = match[1].str();
1218 if (std::regex_match(symbol, match, TrailingColonRegex))
1219 symbol = match[1].str();
1222 if (!symbol.empty() && symbol[symbol.size() - 1] !=
';') {
1223 std::string relPath = m_currentFileInSourceDir
1229 std::string versionStringRecord =
" *";
1230 size_t startPos = 0;
1232 while (endPos != std::string::npos) {
1233 endPos = symbol.find(
"::", startPos);
1234 size_t length = endPos != std::string::npos ? (endPos - startPos)
1235 : (symbol.size() - startPos);
1237 std::string symbolPart = symbol.substr(startPos, length);
1238 versionStringRecord +=
std::to_string(symbolPart.size());
1239 versionStringRecord += symbolPart;
1241 startPos = endPos + 2;
1243 versionStringRecord +=
"*;";
1245 versionStringRecord +=
1247 versionStringRecord +=
" # ";
1248 versionStringRecord += relPath;
1249 versionStringRecord +=
":";
1250 versionStringRecord +=
std::to_string(m_currentFileLineNumber);
1251 versionStringRecord +=
"\n";
1252 result.versionScriptContent.push_back(versionStringRecord);
1261 ParsingResult &result,
1262 unsigned int skipChecks)
1267 static const std::regex MacroRegex(
"^\\s*#.*");
1300 static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)");
1301 static const std::regex SkipHeaderCheckRegex(
"^#\\s*pragma qt_sync_skip_header_check$");
1302 static const std::regex StopProcessingRegex(
"^#\\s*pragma qt_sync_stop_processing$");
1303 static const std::regex SuspendProcessingRegex(
"^#\\s*pragma qt_sync_suspend_processing$");
1304 static const std::regex ResumeProcessingRegex(
"^#\\s*pragma qt_sync_resume_processing$");
1305 static const std::regex ExplixitClassPragmaRegex(
"^#\\s*pragma qt_class\\(([^\\)]+)\\)$");
1306 static const std::regex DeprecatesPragmaRegex(
"^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$");
1307 static const std::regex NoMasterIncludePragmaRegex(
"^#\\s*pragma qt_no_master_include$");
1311 static const std::string_view WeMeantItString(
"We mean it.");
1314 static const std::regex BeginNamespaceRegex(
"^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1315 static const std::regex EndNamespaceRegex(
"^QT_END_NAMESPACE(_[A-Z_]+)?$");
1321 static const std::regex IncludeRegex(
"^#\\s*include\\s*[<\"](.+)[>\"]");
1324 static const std::regex NamespaceRegex(
"\\s*namespace ([^ ]*)\\s+");
1328 static const std::regex DeclareIteratorRegex(
"^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
1333 static const std::regex RequireConfigRegex(
"^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
1341 static const std::regex ElfVersionTagRegex(
".*ELFVERSION:(stop|ignore-next|ignore).*");
1343 std::ifstream input(headerFile, std::ifstream::in);
1344 if (!input.is_open()) {
1345 std::cerr <<
"Unable to open " << headerFile <<
std::endl;
1349 bool hasQtBeginNamespace =
false;
1350 std::string qtBeginNamespace;
1351 std::string qtEndNamespace;
1352 bool hasWeMeantIt =
false;
1353 bool isSuspended =
false;
1354 bool isMultiLineComment =
false;
1355 std::size_t bracesDepth = 0;
1356 std::size_t namespaceCount = 0;
1357 std::string namespaceString;
1363 std::string tmpLine;
1364 std::size_t linesProcessed = 0;
1367 const auto error = [&] () ->
decltype(
auto) {
1368 return std::cerr << ErrorMessagePreamble << m_currentFileString
1369 <<
":" << m_currentFileLineNumber <<
" ";
1373 while (
std::getline(input, tmpLine)) {
1374 ++m_currentFileLineNumber;
1375 line.append(tmpLine);
1376 if (line.empty() || line.at(line.size() - 1) ==
'\\') {
1380 buffer.reserve(line.size());
1385 for (
std::size_t i = 0; i < line.size(); ++i) {
1386 if (line[i] ==
'\r')
1388 if (bracesDepth == namespaceCount) {
1389 if (line[i] ==
'/') {
1390 if ((i + 1) < line.size()) {
1391 if (line[i + 1] ==
'*') {
1392 isMultiLineComment =
true;
1394 }
else if (line[i + 1] ==
'/') {
1395 if (!(skipChecks & WeMeantItChecks)
1396 && line.find(WeMeantItString) != std::string::npos) {
1397 hasWeMeantIt =
true;
1400 if (m_versionScriptGeneratorState != Stopped
1401 &&
std::regex_match(line, match, ElfVersionTagRegex)) {
1402 if (match[1].str() ==
"ignore")
1403 m_versionScriptGeneratorState = Ignore;
1404 else if (match[1].str() ==
"ignore-next")
1405 m_versionScriptGeneratorState = IgnoreNext;
1406 else if (match[1].str() ==
"stop")
1407 m_versionScriptGeneratorState = Stopped;
1412 }
else if (line[i] ==
'*' && (i + 1) < line.size() && line[i + 1] ==
'/') {
1414 isMultiLineComment =
false;
1419 if (isMultiLineComment) {
1420 if (!(skipChecks & WeMeantItChecks) &&
1421 line.find(WeMeantItString) != std::string::npos) {
1422 hasWeMeantIt =
true;
1428 if (line[i] ==
'{') {
1429 if (
std::regex_match(buffer, match, NamespaceRegex)) {
1431 namespaceString +=
"::";
1432 namespaceString += match[1].str();
1436 }
else if (line[i] ==
'}') {
1437 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1438 namespaceString.resize(namespaceString.rfind(
"::"));
1442 }
else if (bracesDepth == namespaceCount) {
1448 scannerDebug() << m_currentFilename <<
": " << buffer <<
std::endl;
1450 if (m_currentFileType & PrivateHeader) {
1460 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1461 || (m_currentFileType & SsgHeader) || (m_currentFileType & SpiHeader);
1464 if (
std::regex_match(buffer, MacroRegex)) {
1465 if (
std::regex_match(buffer, SkipHeaderCheckRegex)) {
1468 }
else if (
std::regex_match(buffer, StopProcessingRegex)) {
1469 if (skipChecks == AllChecks)
1470 m_headerCheckExceptions.push_back(m_currentFileString);
1472 }
else if (
std::regex_match(buffer, SuspendProcessingRegex)) {
1474 }
else if (
std::regex_match(buffer, ResumeProcessingRegex)) {
1475 isSuspended =
false;
1476 }
else if (
std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
1479 SymbolDescriptor::Pragma);
1483 }
else if (
std::regex_match(buffer, NoMasterIncludePragmaRegex)) {
1484 result.masterInclude =
false;
1485 }
else if (
std::regex_match(buffer, match, DeprecatesPragmaRegex)) {
1486 m_deprecatedHeaders[match[1].str()] =
1487 m_commandLineArgs->moduleName() +
'/' + m_currentFilename;
1488 }
else if (
std::regex_match(buffer, OnceRegex)) {
1491 error() <<
"\"#pragma once\" is not allowed in installed header files: "
1492 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1495 }
else if (
std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
1497 std::string includedHeader = match[1].str();
1501 .generic_string()
)) {
1503 error() <<
"includes private header " << includedHeader <<
std::endl;
1505 for (
const auto &module : m_commandLineArgs->knownModules()) {
1506 std::string suggestedHeader =
"Qt" + module +
'/' + includedHeader;
1507 const std::string suggestedHeaderReversePath =
"/../" + suggestedHeader;
1508 if (std::filesystem::exists(m_commandLineArgs->includeDir()
1509 + suggestedHeaderReversePath)
1510 || std::filesystem::exists(m_commandLineArgs->installIncludeDir()
1511 +
'/' + suggestedHeader)) {
1512 faults |= IncludeChecks;
1513 std::cerr << m_warningMessagePreamble << m_currentFileString
1514 <<
":" << m_currentFileLineNumber
1515 <<
" includes " << includedHeader
1516 <<
" when it should include "
1517 << suggestedHeader << std::endl;
1534 if (namespaceCount == 0
1535 ||
std::regex_match(namespaceString,
1538 SymbolDescriptor::Declaration
);
1541 }
else if (
std::regex_match(buffer, match, DeclareIteratorRegex)) {
1542 std::string iteratorSymbol = match[1].str() +
"Iterator";
1544 SymbolDescriptor::Declaration
);
1546 m_currentFilename
, SymbolDescriptor::Declaration
);
1548 }
else if (
std::regex_match(buffer, match, RequireConfigRegex)) {
1549 result.requireConfig = match[1].str();
1557 if (
std::regex_match(buffer, match, BeginNamespaceRegex)) {
1558 qtBeginNamespace = match[1].str();
1559 hasQtBeginNamespace =
true;
1560 }
else if (
std::regex_match(buffer, match, EndNamespaceRegex)) {
1561 qtEndNamespace = match[1].str();
1569 if (hasQtBeginNamespace) {
1570 if (qtBeginNamespace != qtEndNamespace) {
1572 std::cerr << m_warningMessagePreamble << m_currentFileString
1573 <<
" the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1574 <<
" doesn't match the end namespace macro QT_END_NAMESPACE"
1575 << qtEndNamespace << std::endl;
1579 std::cerr << m_warningMessagePreamble << m_currentFileString
1580 <<
" does not include QT_BEGIN_NAMESPACE" << std::endl;
1586 std::cerr << m_warningMessagePreamble << m_currentFileString
1587 <<
" does not have the \"We mean it.\" warning"
1591 scannerDebug() <<
"linesTotal: " << m_currentFileLineNumber
1592 <<
" linesProcessed: " << linesProcessed << std::endl;
1594 if (skipChecks == AllChecks)
1595 m_headerCheckExceptions.push_back(m_currentFileString);
1598 return !(faults & m_criticalChecks);
1773 static std::regex cIdentifierSymbolsRegex(
"[^a-zA-Z0-9_]");
1774 const std::string guard_base =
"DEPRECATED_HEADER_" + m_commandLineArgs
->moduleName();
1776 for (
auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) {
1777 const std::string &descriptor = it->first;
1778 const std::string &replacement = it->second;
1780 const auto separatorPos = descriptor.find(
',');
1781 std::string headerPath = descriptor.substr(0, separatorPos);
1782 std::string versionDisclaimer;
1783 if (separatorPos != std::string::npos) {
1784 std::string version = descriptor.substr(separatorPos + 1);
1785 versionDisclaimer =
" and will be removed in Qt " + version;
1787 if (!parsed
.ok() || parsed.count != 2) {
1788 std::cerr << ErrorMessagePreamble
1789 <<
"Invalid version format specified for the deprecated header file "
1790 << headerPath <<
": '" << version
1791 <<
"'. Expected format: 'major.minor'.\n";
1795 const int major = parsed.parts[0];
1796 const int minor = parsed.parts[1];
1798 if (QT_VERSION_MAJOR > major
1799 || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) {
1800 std::cerr << WarningMessagePreamble << headerPath
1801 <<
" is marked as deprecated and will not be generated in Qt "
1803 <<
". The respective qt_deprecates pragma needs to be removed.\n";
1808 const auto moduleSeparatorPos = headerPath.find(
'/');
1809 std::string headerName = moduleSeparatorPos != std::string::npos
1810 ? headerPath.substr(moduleSeparatorPos + 1)
1812 const std::string moduleName = moduleSeparatorPos != std::string::npos
1813 ? headerPath.substr(0, moduleSeparatorPos)
1814 : m_commandLineArgs->moduleName();
1816 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs
->moduleName();
1818 std::string qualifiedHeaderName =
1819 std::regex_replace(headerName, cIdentifierSymbolsRegex,
"_");
1820 std::string guard = guard_base +
"_" + qualifiedHeaderName;
1821 std::string warningText =
"Header <" + moduleName +
"/" + headerName +
"> is deprecated"
1822 + versionDisclaimer +
". Please include <" + replacement +
"> instead.";
1823 std::stringstream buffer;
1824 buffer <<
"#ifndef " << guard <<
"\n"
1825 <<
"#define " << guard <<
"\n"
1826 <<
"#if defined(__GNUC__)\n"
1827 <<
"# warning " << warningText <<
"\n"
1828 <<
"#elif defined(_MSC_VER)\n"
1829 <<
"# pragma message (\"" << warningText <<
"\")\n"
1831 <<
"#include <" << replacement <<
">\n"
1834 const std::string outputDir = isCrossModuleDeprecation
1840 if (isCrossModuleDeprecation) {
1841 const std::string stagingDir = outputDir +
"/.syncqt_staging/";
1844 m_producedHeaders.insert(headerName);