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,
1011 static const std::regex ThirdPartyFolderRegex(
"(^|.+/)3rdparty/.+");
1014 static const std::regex ConfigHeaderRegex(
"^(q|.+-)config(_p)?\\.h");
1018 bool isPrivate = m_currentFileType & PrivateHeader;
1019 bool isQpa = m_currentFileType & QpaHeader;
1020 bool isRhi = m_currentFileType & RhiHeader;
1021 bool isSsg = m_currentFileType & SsgHeader;
1022 bool isSpi = m_currentFileType & SpiHeader;
1023 bool isExport = m_currentFileType & ExportHeader;
1024 bool isPublic = !isPrivate && !isQpa && !isRhi && !isSsg && !isSpi;
1028 if (!m_currentFileInSourceDir
1029 && m_currentFileString.find(m_commandLineArgs
->binaryDir()) != 0) {
1030 scannerDebug() <<
"Header file: " << headerFile
1031 <<
" is outside the sync directories. Skipping." <<
std::endl;
1032 m_headerCheckExceptions.push_back(m_currentFileString);
1039 if (m_currentFileString.find(m_commandLineArgs
->includeDir()) == 0) {
1040 m_moduleMapContents.insert({m_currentFileString, {}});
1042 if (m_commandLineArgs->isFramework() && isPublic)
1043 m_publicHeaders.insert(m_currentFilename);
1050 if (m_currentFilename.empty()) {
1051 std::cerr <<
"Header file name of " << m_currentFileString <<
"is empty" <<
std::endl;
1056 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
1058 originalStamp = FileStamp::clock::now();
1062 <<
"processHeader:start: " << headerFile
1063 <<
" m_currentFilename: " << m_currentFilename
1064 <<
" isPrivate: " << isPrivate
1065 <<
" isQpa: " << isQpa
1066 <<
" isRhi: " << isRhi
1067 <<
" isSsg: " << isSsg
1068 <<
" isSpi: " << isSpi
1069 <<
" isExport: " << isExport
1075 if (!utils::createDirectories(outputDir,
"Unable to create output directory"))
1078 bool headerFileExists =
std::filesystem::exists(headerFile);
1080 std::string aliasedFilepath = headerFile.generic_string();
1082 std::string aliasPath = outputDir +
'/' + m_currentFilename;
1087 if (m_commandLineArgs
->copy() && headerFileExists) {
1091 if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
1100 m_publicHeaders.insert(m_currentFilename);
1111 if (!headerFileExists) {
1112 scannerDebug() <<
"Header file: " << headerFile
1113 <<
" doesn't exist, but is added to syncqt scanning. Skipping.";
1121 bool is3rdParty =
std::regex_match(
1124 ThirdPartyFolderRegex);
1127 if (!
std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
1132 || isSpi || !m_currentFileInSourceDir || isGenerated) {
1150 ParsingResult parsingResult;
1151 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
1152 && isPublic && !isGenerated;
1154 scannerDebug() <<
"parseHeader failed: " << headerFile <<
std::endl;
1160 && !parsingResult.versionScriptContent.empty()) {
1161 m_versionScriptContents.insert(m_versionScriptContents.end(),
1162 parsingResult.versionScriptContent.begin(),
1163 parsingResult.versionScriptContent.end());
1168 bool willBeInModuleMasterHeader =
false;
1170 if (m_currentFilename.find(
'_') == std::string::npos
1171 && parsingResult.masterInclude) {
1172 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
1173 willBeInModuleMasterHeader =
true;
1178 <<
"processHeader:end: " << headerFile
1179 <<
" is3rdParty: " << is3rdParty
1180 <<
" isGenerated: " << isGenerated
1181 <<
" m_currentFileInSourceDir: " << m_currentFileInSourceDir
1182 <<
" willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
1184 }
else if (m_currentFilename ==
"qconfig.h") {
1189 m_moduleMapContents.insert({outputDir +
"/" + m_currentFilename, {}});
1197 static const std::regex VersionScriptSymbolRegex(
1198 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1201 static const std::regex VersionScriptNamespaceRegex(
1202 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1205 static const std::regex TrailingColonRegex(
"([\\w]+):$");
1207 switch (m_versionScriptGeneratorState) {
1209 scannerDebug() <<
"line ignored: " << buffer <<
std::endl;
1210 m_versionScriptGeneratorState = Active;
1215 m_versionScriptGeneratorState = Ignore;
1226 if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
1227 symbol = match[1].str();
1228 else if (
std::regex_match(buffer, match, VersionScriptNamespaceRegex))
1229 symbol = match[1].str();
1231 if (
std::regex_match(symbol, match, TrailingColonRegex))
1232 symbol = match[1].str();
1235 if (!symbol.empty() && symbol[symbol.size() - 1] !=
';') {
1236 std::string relPath = m_currentFileInSourceDir
1242 std::string versionStringRecord =
" *";
1243 size_t startPos = 0;
1245 while (endPos != std::string::npos) {
1246 endPos = symbol.find(
"::", startPos);
1247 size_t length = endPos != std::string::npos ? (endPos - startPos)
1248 : (symbol.size() - startPos);
1250 std::string symbolPart = symbol.substr(startPos, length);
1251 versionStringRecord +=
std::to_string(symbolPart.size());
1252 versionStringRecord += symbolPart;
1254 startPos = endPos + 2;
1256 versionStringRecord +=
"*;";
1258 versionStringRecord +=
1260 versionStringRecord +=
" # ";
1261 versionStringRecord += relPath;
1262 versionStringRecord +=
":";
1263 versionStringRecord +=
std::to_string(m_currentFileLineNumber);
1264 versionStringRecord +=
"\n";
1265 result.versionScriptContent.push_back(versionStringRecord);
1274 ParsingResult &result,
1275 unsigned int skipChecks)
1280 static const std::regex MacroRegex(
"^\\s*#.*");
1313 static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)");
1314 static const std::regex SkipHeaderCheckRegex(
"^#\\s*pragma qt_sync_skip_header_check$");
1315 static const std::regex StopProcessingRegex(
"^#\\s*pragma qt_sync_stop_processing$");
1316 static const std::regex SuspendProcessingRegex(
"^#\\s*pragma qt_sync_suspend_processing$");
1317 static const std::regex ResumeProcessingRegex(
"^#\\s*pragma qt_sync_resume_processing$");
1318 static const std::regex ExplixitClassPragmaRegex(
"^#\\s*pragma qt_class\\(([^\\)]+)\\)$");
1319 static const std::regex DeprecatesPragmaRegex(
"^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$");
1320 static const std::regex NoMasterIncludePragmaRegex(
"^#\\s*pragma qt_no_master_include$");
1324 static const std::string_view WeMeantItString(
"We mean it.");
1327 static const std::regex BeginNamespaceRegex(
"^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1328 static const std::regex EndNamespaceRegex(
"^QT_END_NAMESPACE(_[A-Z_]+)?$");
1334 static const std::regex IncludeRegex(
"^#\\s*include\\s*[<\"](.+)[>\"]");
1337 static const std::regex NamespaceRegex(
"\\s*namespace ([^ ]*)\\s+");
1341 static const std::regex DeclareIteratorRegex(
"^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
1346 static const std::regex RequireConfigRegex(
"^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
1354 static const std::regex ElfVersionTagRegex(
".*ELFVERSION:(stop|ignore-next|ignore).*");
1356 std::ifstream input(headerFile, std::ifstream::in);
1357 if (!input.is_open()) {
1358 std::cerr <<
"Unable to open " << headerFile <<
std::endl;
1362 bool hasQtBeginNamespace =
false;
1363 std::string qtBeginNamespace;
1364 std::string qtEndNamespace;
1365 bool hasWeMeantIt =
false;
1366 bool isSuspended =
false;
1367 bool isMultiLineComment =
false;
1368 std::size_t bracesDepth = 0;
1369 std::size_t namespaceCount = 0;
1370 std::string namespaceString;
1376 std::string tmpLine;
1377 std::size_t linesProcessed = 0;
1380 const auto error = [&] () ->
decltype(
auto) {
1381 return std::cerr << ErrorMessagePreamble << m_currentFileString
1382 <<
":" << m_currentFileLineNumber <<
" ";
1386 while (
std::getline(input, tmpLine)) {
1387 ++m_currentFileLineNumber;
1388 line.append(tmpLine);
1389 if (line.empty() || line.at(line.size() - 1) ==
'\\') {
1393 buffer.reserve(line.size());
1398 for (
std::size_t i = 0; i < line.size(); ++i) {
1399 if (line[i] ==
'\r')
1401 if (bracesDepth == namespaceCount) {
1402 if (line[i] ==
'/') {
1403 if ((i + 1) < line.size()) {
1404 if (line[i + 1] ==
'*') {
1405 isMultiLineComment =
true;
1407 }
else if (line[i + 1] ==
'/') {
1408 if (!(skipChecks & WeMeantItChecks)
1409 && line.find(WeMeantItString) != std::string::npos) {
1410 hasWeMeantIt =
true;
1413 if (m_versionScriptGeneratorState != Stopped
1414 &&
std::regex_match(line, match, ElfVersionTagRegex)) {
1415 if (match[1].str() ==
"ignore")
1416 m_versionScriptGeneratorState = Ignore;
1417 else if (match[1].str() ==
"ignore-next")
1418 m_versionScriptGeneratorState = IgnoreNext;
1419 else if (match[1].str() ==
"stop")
1420 m_versionScriptGeneratorState = Stopped;
1425 }
else if (line[i] ==
'*' && (i + 1) < line.size() && line[i + 1] ==
'/') {
1427 isMultiLineComment =
false;
1432 if (isMultiLineComment) {
1433 if (!(skipChecks & WeMeantItChecks) &&
1434 line.find(WeMeantItString) != std::string::npos) {
1435 hasWeMeantIt =
true;
1441 if (line[i] ==
'{') {
1442 if (
std::regex_match(buffer, match, NamespaceRegex)) {
1444 namespaceString +=
"::";
1445 namespaceString += match[1].str();
1449 }
else if (line[i] ==
'}') {
1450 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1451 namespaceString.resize(namespaceString.rfind(
"::"));
1455 }
else if (bracesDepth == namespaceCount) {
1461 scannerDebug() << m_currentFilename <<
": " << buffer <<
std::endl;
1463 if (m_currentFileType & PrivateHeader) {
1473 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1474 || (m_currentFileType & SsgHeader) || (m_currentFileType & SpiHeader);
1477 if (
std::regex_match(buffer, MacroRegex)) {
1478 if (
std::regex_match(buffer, SkipHeaderCheckRegex)) {
1481 }
else if (
std::regex_match(buffer, StopProcessingRegex)) {
1482 if (skipChecks == AllChecks)
1483 m_headerCheckExceptions.push_back(m_currentFileString);
1485 }
else if (
std::regex_match(buffer, SuspendProcessingRegex)) {
1487 }
else if (
std::regex_match(buffer, ResumeProcessingRegex)) {
1488 isSuspended =
false;
1489 }
else if (
std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
1492 SymbolDescriptor::Pragma);
1496 }
else if (
std::regex_match(buffer, NoMasterIncludePragmaRegex)) {
1497 result.masterInclude =
false;
1498 }
else if (
std::regex_match(buffer, match, DeprecatesPragmaRegex)) {
1499 m_deprecatedHeaders[match[1].str()] = currentFileIncludePath();
1500 }
else if (
std::regex_match(buffer, OnceRegex)) {
1503 error() <<
"\"#pragma once\" is not allowed in installed header files: "
1504 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1507 }
else if (
std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
1509 std::string includedHeader = match[1].str();
1513 .generic_string()
)) {
1515 error() <<
"includes private header " << includedHeader <<
std::endl;
1517 for (
const auto &module : m_commandLineArgs->knownModules()) {
1518 std::string suggestedHeader =
"Qt" + module +
'/' + includedHeader;
1519 const std::string suggestedHeaderReversePath =
"/../" + suggestedHeader;
1520 if (std::filesystem::exists(m_commandLineArgs->includeDir()
1521 + suggestedHeaderReversePath)
1522 || std::filesystem::exists(m_commandLineArgs->installIncludeDir()
1523 +
'/' + suggestedHeader)) {
1524 faults |= IncludeChecks;
1525 std::cerr << m_warningMessagePreamble << m_currentFileString
1526 <<
":" << m_currentFileLineNumber
1527 <<
" includes " << includedHeader
1528 <<
" when it should include "
1529 << suggestedHeader << std::endl;
1546 if (namespaceCount == 0
1547 ||
std::regex_match(namespaceString,
1550 SymbolDescriptor::Declaration
);
1553 }
else if (
std::regex_match(buffer, match, DeclareIteratorRegex)) {
1554 std::string iteratorSymbol = match[1].str() +
"Iterator";
1556 SymbolDescriptor::Declaration
);
1558 m_currentFilename
, SymbolDescriptor::Declaration
);
1560 }
else if (
std::regex_match(buffer, match, RequireConfigRegex)) {
1561 result.requireConfig = match[1].str();
1569 if (
std::regex_match(buffer, match, BeginNamespaceRegex)) {
1570 qtBeginNamespace = match[1].str();
1571 hasQtBeginNamespace =
true;
1572 }
else if (
std::regex_match(buffer, match, EndNamespaceRegex)) {
1573 qtEndNamespace = match[1].str();
1581 if (hasQtBeginNamespace) {
1582 if (qtBeginNamespace != qtEndNamespace) {
1584 std::cerr << m_warningMessagePreamble << m_currentFileString
1585 <<
" the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1586 <<
" doesn't match the end namespace macro QT_END_NAMESPACE"
1587 << qtEndNamespace << std::endl;
1591 std::cerr << m_warningMessagePreamble << m_currentFileString
1592 <<
" does not include QT_BEGIN_NAMESPACE" << std::endl;
1598 std::cerr << m_warningMessagePreamble << m_currentFileString
1599 <<
" does not have the \"We mean it.\" warning"
1603 scannerDebug() <<
"linesTotal: " << m_currentFileLineNumber
1604 <<
" linesProcessed: " << linesProcessed << std::endl;
1606 if (skipChecks == AllChecks)
1607 m_headerCheckExceptions.push_back(m_currentFileString);
1610 return !(faults & m_criticalChecks);
1792 static std::regex cIdentifierSymbolsRegex(
"[^a-zA-Z0-9_]");
1793 const std::string guard_base =
"DEPRECATED_HEADER_" + m_commandLineArgs
->moduleName();
1795 for (
auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) {
1796 const std::string &descriptor = it->first;
1797 const std::string &replacement = it->second;
1799 const auto separatorPos = descriptor.find(
',');
1800 std::string headerPath = descriptor.substr(0, separatorPos);
1801 std::string versionDisclaimer;
1802 if (separatorPos != std::string::npos) {
1803 std::string version = descriptor.substr(separatorPos + 1);
1804 versionDisclaimer =
" and will be removed in Qt " + version;
1806 if (!parsed
.ok() || parsed.count != 2) {
1807 std::cerr << ErrorMessagePreamble
1808 <<
"Invalid version format specified for the deprecated header file "
1809 << headerPath <<
": '" << version
1810 <<
"'. Expected format: 'major.minor'.\n";
1814 const int major = parsed.parts[0];
1815 const int minor = parsed.parts[1];
1817 if (QT_VERSION_MAJOR > major
1818 || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) {
1819 std::cerr << WarningMessagePreamble << headerPath
1820 <<
" is marked as deprecated and will not be generated in Qt "
1822 <<
". The respective qt_deprecates pragma needs to be removed.\n";
1827 const auto moduleSeparatorPos = headerPath.find(
'/');
1828 std::string headerName = moduleSeparatorPos != std::string::npos
1829 ? headerPath.substr(moduleSeparatorPos + 1)
1831 const std::string moduleName = moduleSeparatorPos != std::string::npos
1832 ? headerPath.substr(0, moduleSeparatorPos)
1833 : m_commandLineArgs->moduleName();
1836 std::cerr << ErrorMessagePreamble <<
"Unable to deprecate " << headerPath
1837 <<
": only public header files can be deprecated.\n";
1842 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs
->moduleName();
1844 std::string qualifiedHeaderName =
1845 std::regex_replace(headerName, cIdentifierSymbolsRegex,
"_");
1846 std::string guard = guard_base +
"_" + qualifiedHeaderName;
1847 std::string warningText =
"Header <" + moduleName +
"/" + headerName +
"> is deprecated"
1848 + versionDisclaimer +
". Please include <" + replacement +
"> instead.";
1849 std::stringstream buffer;
1850 buffer <<
"#ifndef " << guard <<
"\n"
1851 <<
"#define " << guard <<
"\n"
1852 <<
"#if defined(__GNUC__)\n"
1853 <<
"# warning " << warningText <<
"\n"
1854 <<
"#elif defined(_MSC_VER)\n"
1855 <<
"# pragma message (\"" << warningText <<
"\")\n"
1857 <<
"#include <" << replacement <<
">\n"
1860 const std::string outputDir = isCrossModuleDeprecation
1866 if (isCrossModuleDeprecation) {
1867 const std::string stagingDir = outputDir +
"/.syncqt_staging/";
1870 m_producedHeaders.insert(headerName);