669 m_warningMessagePreamble = ErrorMessagePreamble;
672 m_versionScriptGeneratorState =
679 for (
auto const &entry :
680 std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) {
682 const bool isRegularFile = entry.is_regular_file();
683 const bool isHeaderFlag = isHeader(entry);
684 const bool isDocFileHeuristicFlag =
685 isDocFileHeuristic(entry.path().generic_string());
686 const bool shouldProcessHeader =
687 isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag;
688 const std::string filePath = entry.path().generic_string();
690 if (shouldProcessHeader) {
691 scannerDebug() <<
"Processing header: " << filePath << std::endl;
692 if (!processHeader(makeHeaderAbsolute(filePath)))
696 <<
"Skipping processing header: " << filePath
697 <<
" isRegularFile: " << isRegularFile
698 <<
" isHeaderFlag: " << isHeaderFlag
699 <<
" isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
706 std::set<std::string> rspHeaders;
707 const auto &headers = m_commandLineArgs->headers();
708 for (
auto it = headers.begin(); it != headers.end(); ++it) {
709 const auto &header = *it;
710 scannerDebug() <<
"Processing header: " << header <<
std::endl;
715 for (
const auto &header : rspHeaders) {
716 scannerDebug() <<
"Processing header: " << header << std::endl;
717 if (!processHeader(makeHeaderAbsolute(header)))
727 for (
auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
728 const std::string &filename = it->second.file();
729 if (!filename.empty()) {
732 m_producedHeaders.insert(it->first);
742 std::string versionHeaderFilename(moduleNameLower +
"version.h");
744 std::string versionFile = m_commandLineArgs
->includeDir() +
'/' + versionHeaderFilename;
747 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
749 originalStamp = FileStamp::clock::now();
752 if (!generateAliasedHeaderFileIfTimestampChanged(
753 m_commandLineArgs->includeDir() +
'/' + versionHeaderCamel,
754 versionFile, originalStamp)) {
757 m_masterHeaderContents[versionHeaderFilename] = {};
758 m_producedHeaders.insert(versionHeaderFilename);
759 m_producedHeaders.insert(versionHeaderCamel);
877 static const std::regex ThirdPartyFolderRegex(
"(^|.+/)3rdparty/.+");
880 static const std::regex ConfigHeaderRegex(
"^(q|.+-)config(_p)?\\.h");
885 if (!m_currentFileInSourceDir
886 && m_currentFileString.find(m_commandLineArgs
->binaryDir()) != 0) {
887 scannerDebug() <<
"Header file: " << headerFile
888 <<
" is outside the sync directories. Skipping." <<
std::endl;
889 m_headerCheckExceptions.push_back(m_currentFileString);
894 if (m_currentFilename.empty()) {
895 std::cerr <<
"Header file name of " << m_currentFileString <<
"is empty" <<
std::endl;
900 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
902 originalStamp = FileStamp::clock::now();
905 bool isPrivate = m_currentFileType & PrivateHeader;
906 bool isQpa = m_currentFileType & QpaHeader;
907 bool isRhi = m_currentFileType & RhiHeader;
908 bool isSsg = m_currentFileType & SsgHeader;
909 bool isSpi = m_currentFileType & SpiHeader;
910 bool isExport = m_currentFileType & ExportHeader;
912 <<
"processHeader:start: " << headerFile
913 <<
" m_currentFilename: " << m_currentFilename
914 <<
" isPrivate: " << isPrivate
915 <<
" isQpa: " << isQpa
916 <<
" isRhi: " << isRhi
917 <<
" isSsg: " << isSsg
918 <<
" isSpi: " << isSpi
919 <<
" isExport: " << isExport
936 if (!utils::createDirectories(outputDir,
"Unable to create output directory"))
939 bool headerFileExists =
std::filesystem::exists(headerFile);
941 std::string aliasedFilepath = headerFile.generic_string();
943 std::string aliasPath = outputDir +
'/' + m_currentFilename;
948 if (m_commandLineArgs
->copy() && headerFileExists) {
952 if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
966 if (!headerFileExists) {
967 scannerDebug() <<
"Header file: " << headerFile
968 <<
" doesn't exist, but is added to syncqt scanning. Skipping.";
976 bool is3rdParty =
std::regex_match(
979 ThirdPartyFolderRegex);
982 if (!
std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
987 || isSpi || !m_currentFileInSourceDir || isGenerated) {
1005 ParsingResult parsingResult;
1006 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
1007 && !isQpa && !isRhi && !isSsg && !isSpi && !isPrivate && !isGenerated;
1009 scannerDebug() <<
"parseHeader failed: " << headerFile <<
std::endl;
1015 && !parsingResult.versionScriptContent.empty()) {
1016 m_versionScriptContents.insert(m_versionScriptContents.end(),
1017 parsingResult.versionScriptContent.begin(),
1018 parsingResult.versionScriptContent.end());
1023 bool willBeInModuleMasterHeader =
false;
1024 if (!isQpa && !isRhi && !isSsg && !isSpi && !isPrivate) {
1025 if (m_currentFilename.find(
'_') == std::string::npos
1026 && parsingResult.masterInclude) {
1027 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
1028 willBeInModuleMasterHeader =
true;
1033 <<
"processHeader:end: " << headerFile
1034 <<
" is3rdParty: " << is3rdParty
1035 <<
" isGenerated: " << isGenerated
1036 <<
" m_currentFileInSourceDir: " << m_currentFileInSourceDir
1037 <<
" willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
1039 }
else if (m_currentFilename ==
"qconfig.h") {
1050 static const std::regex VersionScriptSymbolRegex(
1051 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
1054 static const std::regex VersionScriptNamespaceRegex(
1055 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
1058 static const std::regex TrailingColonRegex(
"([\\w]+):$");
1060 switch (m_versionScriptGeneratorState) {
1062 scannerDebug() <<
"line ignored: " << buffer <<
std::endl;
1063 m_versionScriptGeneratorState = Active;
1068 m_versionScriptGeneratorState = Ignore;
1079 if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
1080 symbol = match[1].str();
1081 else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex))
1082 symbol = match[1].str();
1084 if (std::regex_match(symbol, match, TrailingColonRegex))
1085 symbol = match[1].str();
1088 if (!symbol.empty() && symbol[symbol.size() - 1] !=
';') {
1089 std::string relPath = m_currentFileInSourceDir
1095 std::string versionStringRecord =
" *";
1096 size_t startPos = 0;
1098 while (endPos != std::string::npos) {
1099 endPos = symbol.find(
"::", startPos);
1100 size_t length = endPos != std::string::npos ? (endPos - startPos)
1101 : (symbol.size() - startPos);
1103 std::string symbolPart = symbol.substr(startPos, length);
1104 versionStringRecord +=
std::to_string(symbolPart.size());
1105 versionStringRecord += symbolPart;
1107 startPos = endPos + 2;
1109 versionStringRecord +=
"*;";
1111 versionStringRecord +=
1113 versionStringRecord +=
" # ";
1114 versionStringRecord += relPath;
1115 versionStringRecord +=
":";
1116 versionStringRecord +=
std::to_string(m_currentFileLineNumber);
1117 versionStringRecord +=
"\n";
1118 result.versionScriptContent.push_back(versionStringRecord);
1127 ParsingResult &result,
1128 unsigned int skipChecks)
1133 static const std::regex MacroRegex(
"^\\s*#.*");
1166 static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)");
1167 static const std::regex SkipHeaderCheckRegex(
"^#\\s*pragma qt_sync_skip_header_check$");
1168 static const std::regex StopProcessingRegex(
"^#\\s*pragma qt_sync_stop_processing$");
1169 static const std::regex SuspendProcessingRegex(
"^#\\s*pragma qt_sync_suspend_processing$");
1170 static const std::regex ResumeProcessingRegex(
"^#\\s*pragma qt_sync_resume_processing$");
1171 static const std::regex ExplixitClassPragmaRegex(
"^#\\s*pragma qt_class\\(([^\\)]+)\\)$");
1172 static const std::regex DeprecatesPragmaRegex(
"^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$");
1173 static const std::regex NoMasterIncludePragmaRegex(
"^#\\s*pragma qt_no_master_include$");
1177 static const std::string_view WeMeantItString(
"We mean it.");
1180 static const std::regex BeginNamespaceRegex(
"^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1181 static const std::regex EndNamespaceRegex(
"^QT_END_NAMESPACE(_[A-Z_]+)?$");
1187 static const std::regex IncludeRegex(
"^#\\s*include\\s*[<\"](.+)[>\"]");
1190 static const std::regex NamespaceRegex(
"\\s*namespace ([^ ]*)\\s+");
1194 static const std::regex DeclareIteratorRegex(
"^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
1199 static const std::regex RequireConfigRegex(
"^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
1207 static const std::regex ElfVersionTagRegex(
".*ELFVERSION:(stop|ignore-next|ignore).*");
1209 std::ifstream input(headerFile, std::ifstream::in);
1210 if (!input.is_open()) {
1211 std::cerr <<
"Unable to open " << headerFile <<
std::endl;
1215 bool hasQtBeginNamespace =
false;
1216 std::string qtBeginNamespace;
1217 std::string qtEndNamespace;
1218 bool hasWeMeantIt =
false;
1219 bool isSuspended =
false;
1220 bool isMultiLineComment =
false;
1221 std::size_t bracesDepth = 0;
1222 std::size_t namespaceCount = 0;
1223 std::string namespaceString;
1229 std::string tmpLine;
1230 std::size_t linesProcessed = 0;
1233 const auto error = [&] () ->
decltype(
auto) {
1234 return std::cerr << ErrorMessagePreamble << m_currentFileString
1235 <<
":" << m_currentFileLineNumber <<
" ";
1239 while (
std::getline(input, tmpLine)) {
1240 ++m_currentFileLineNumber;
1241 line.append(tmpLine);
1242 if (line.empty() || line.at(line.size() - 1) ==
'\\') {
1246 buffer.reserve(line.size());
1251 for (
std::size_t i = 0; i < line.size(); ++i) {
1252 if (line[i] ==
'\r')
1254 if (bracesDepth == namespaceCount) {
1255 if (line[i] ==
'/') {
1256 if ((i + 1) < line.size()) {
1257 if (line[i + 1] ==
'*') {
1258 isMultiLineComment =
true;
1260 }
else if (line[i + 1] ==
'/') {
1261 if (!(skipChecks & WeMeantItChecks)
1262 && line.find(WeMeantItString) != std::string::npos) {
1263 hasWeMeantIt =
true;
1266 if (m_versionScriptGeneratorState != Stopped
1267 &&
std::regex_match(line, match, ElfVersionTagRegex)) {
1268 if (match[1].str() ==
"ignore")
1269 m_versionScriptGeneratorState = Ignore;
1270 else if (match[1].str() ==
"ignore-next")
1271 m_versionScriptGeneratorState = IgnoreNext;
1272 else if (match[1].str() ==
"stop")
1273 m_versionScriptGeneratorState = Stopped;
1278 }
else if (line[i] ==
'*' && (i + 1) < line.size() && line[i + 1] ==
'/') {
1280 isMultiLineComment =
false;
1285 if (isMultiLineComment) {
1286 if (!(skipChecks & WeMeantItChecks) &&
1287 line.find(WeMeantItString) != std::string::npos) {
1288 hasWeMeantIt =
true;
1294 if (line[i] ==
'{') {
1295 if (
std::regex_match(buffer, match, NamespaceRegex)) {
1297 namespaceString +=
"::";
1298 namespaceString += match[1].str();
1302 }
else if (line[i] ==
'}') {
1303 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1304 namespaceString.resize(namespaceString.rfind(
"::"));
1308 }
else if (bracesDepth == namespaceCount) {
1314 scannerDebug() << m_currentFilename <<
": " << buffer <<
std::endl;
1316 if (m_currentFileType & PrivateHeader) {
1326 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
1327 || (m_currentFileType & SsgHeader) || (m_currentFileType & SpiHeader);
1330 if (
std::regex_match(buffer, MacroRegex)) {
1331 if (
std::regex_match(buffer, SkipHeaderCheckRegex)) {
1334 }
else if (
std::regex_match(buffer, StopProcessingRegex)) {
1335 if (skipChecks == AllChecks)
1336 m_headerCheckExceptions.push_back(m_currentFileString);
1338 }
else if (
std::regex_match(buffer, SuspendProcessingRegex)) {
1340 }
else if (
std::regex_match(buffer, ResumeProcessingRegex)) {
1341 isSuspended =
false;
1342 }
else if (
std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
1345 SymbolDescriptor::Pragma);
1349 }
else if (
std::regex_match(buffer, NoMasterIncludePragmaRegex)) {
1350 result.masterInclude =
false;
1351 }
else if (
std::regex_match(buffer, match, DeprecatesPragmaRegex)) {
1352 m_deprecatedHeaders[match[1].str()] =
1353 m_commandLineArgs->moduleName() +
'/' + m_currentFilename;
1354 }
else if (
std::regex_match(buffer, OnceRegex)) {
1357 error() <<
"\"#pragma once\" is not allowed in installed header files: "
1358 "https://lists.qt-project.org/pipermail/development/2022-October/043121.html"
1361 }
else if (
std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
1363 std::string includedHeader = match[1].str();
1367 .generic_string()
)) {
1369 error() <<
"includes private header " << includedHeader <<
std::endl;
1371 for (
const auto &module : m_commandLineArgs->knownModules()) {
1372 std::string suggestedHeader =
"Qt" + module +
'/' + includedHeader;
1373 const std::string suggestedHeaderReversePath =
"/../" + suggestedHeader;
1374 if (std::filesystem::exists(m_commandLineArgs->includeDir()
1375 + suggestedHeaderReversePath)
1376 || std::filesystem::exists(m_commandLineArgs->installIncludeDir()
1377 +
'/' + suggestedHeader)) {
1378 faults |= IncludeChecks;
1379 std::cerr << m_warningMessagePreamble << m_currentFileString
1380 <<
":" << m_currentFileLineNumber
1381 <<
" includes " << includedHeader
1382 <<
" when it should include "
1383 << suggestedHeader << std::endl;
1400 if (namespaceCount == 0
1401 ||
std::regex_match(namespaceString,
1404 SymbolDescriptor::Declaration
);
1407 }
else if (
std::regex_match(buffer, match, DeclareIteratorRegex)) {
1408 std::string iteratorSymbol = match[1].str() +
"Iterator";
1410 SymbolDescriptor::Declaration
);
1412 m_currentFilename
, SymbolDescriptor::Declaration
);
1414 }
else if (
std::regex_match(buffer, match, RequireConfigRegex)) {
1415 result.requireConfig = match[1].str();
1423 if (
std::regex_match(buffer, match, BeginNamespaceRegex)) {
1424 qtBeginNamespace = match[1].str();
1425 hasQtBeginNamespace =
true;
1426 }
else if (
std::regex_match(buffer, match, EndNamespaceRegex)) {
1427 qtEndNamespace = match[1].str();
1435 if (hasQtBeginNamespace) {
1436 if (qtBeginNamespace != qtEndNamespace) {
1438 std::cerr << m_warningMessagePreamble << m_currentFileString
1439 <<
" the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1440 <<
" doesn't match the end namespace macro QT_END_NAMESPACE"
1441 << qtEndNamespace << std::endl;
1445 std::cerr << m_warningMessagePreamble << m_currentFileString
1446 <<
" does not include QT_BEGIN_NAMESPACE" << std::endl;
1452 std::cerr << m_warningMessagePreamble << m_currentFileString
1453 <<
" does not have the \"We mean it.\" warning"
1457 scannerDebug() <<
"linesTotal: " << m_currentFileLineNumber
1458 <<
" linesProcessed: " << linesProcessed << std::endl;
1460 if (skipChecks == AllChecks)
1461 m_headerCheckExceptions.push_back(m_currentFileString);
1464 return !(faults & m_criticalChecks);
1612 static std::regex cIdentifierSymbolsRegex(
"[^a-zA-Z0-9_]");
1613 const std::string guard_base =
"DEPRECATED_HEADER_" + m_commandLineArgs
->moduleName();
1615 for (
auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) {
1616 const std::string &descriptor = it->first;
1617 const std::string &replacement = it->second;
1619 const auto separatorPos = descriptor.find(
',');
1620 std::string headerPath = descriptor.substr(0, separatorPos);
1621 std::string versionDisclaimer;
1622 if (separatorPos != std::string::npos) {
1623 std::string version = descriptor.substr(separatorPos + 1);
1624 versionDisclaimer =
" and will be removed in Qt " + version;
1628 std::cerr << ErrorMessagePreamble
1629 <<
"Invalid version format specified for the deprecated header file "
1630 << headerPath <<
": '" << version
1631 <<
"'. Expected format: 'major.minor'.\n";
1636 if (QT_VERSION_MAJOR > major
1637 || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) {
1638 std::cerr << WarningMessagePreamble << headerPath
1639 <<
" is marked as deprecated and will not be generated in Qt "
1641 <<
". The respective qt_deprecates pragma needs to be removed.\n";
1646 const auto moduleSeparatorPos = headerPath.find(
'/');
1647 std::string headerName = moduleSeparatorPos != std::string::npos
1648 ? headerPath.substr(moduleSeparatorPos + 1)
1650 const std::string moduleName = moduleSeparatorPos != std::string::npos
1651 ? headerPath.substr(0, moduleSeparatorPos)
1652 : m_commandLineArgs->moduleName();
1654 bool isCrossModuleDeprecation = moduleName != m_commandLineArgs
->moduleName();
1656 std::string qualifiedHeaderName =
1657 std::regex_replace(headerName, cIdentifierSymbolsRegex,
"_");
1658 std::string guard = guard_base +
"_" + qualifiedHeaderName;
1659 std::string warningText =
"Header <" + moduleName +
"/" + headerName +
"> is deprecated"
1660 + versionDisclaimer +
". Please include <" + replacement +
"> instead.";
1661 std::stringstream buffer;
1662 buffer <<
"#ifndef " << guard <<
"\n"
1663 <<
"#define " << guard <<
"\n"
1664 <<
"#if defined(__GNUC__)\n"
1665 <<
"# warning " << warningText <<
"\n"
1666 <<
"#elif defined(_MSC_VER)\n"
1667 <<
"# pragma message (\"" << warningText <<
"\")\n"
1669 <<
"#include <" << replacement <<
">\n"
1672 const std::string outputDir = isCrossModuleDeprecation
1678 if (isCrossModuleDeprecation) {
1679 const std::string stagingDir = outputDir +
"/.syncqt_staging/";
1682 m_producedHeaders.insert(headerName);