diff options
Diffstat (limited to 'src/tools/syncqt/main.cpp')
-rw-r--r-- | src/tools/syncqt/main.cpp | 336 |
1 files changed, 249 insertions, 87 deletions
diff --git a/src/tools/syncqt/main.cpp b/src/tools/syncqt/main.cpp index dbb3d30aaa..ab646e6cd1 100644 --- a/src/tools/syncqt/main.cpp +++ b/src/tools/syncqt/main.cpp @@ -7,8 +7,7 @@ * - Header file that contains the module version information, and named as <module>Vesion * - LD version script if applicable * - Aliases or copies of the header files sorted by the generic Qt-types: public/private/qpa - * and stored in the corresponding directories. Also copies the aliases to the framework-specific - * directories. + * and stored in the corresponding directories. * Also the tool executes conformity checks on each header file if applicable, to make sure they * follow rules that are relevant for their header type. * The tool can be run in two modes: with either '-all' or '-headers' options specified. Depending @@ -28,6 +27,7 @@ #include <regex> #include <map> #include <set> +#include <stdexcept> #include <array> enum ErrorCodes { @@ -43,9 +43,10 @@ enum HeaderChecks { PrivateHeaderChecks = 2, /* Checks if the public header includes a private header */ IncludeChecks = 4, /* Checks if the real header file but not an alias is included */ WeMeantItChecks = 8, /* Checks if private header files contains 'We meant it' disclaimer */ - CriticalChecks = PrivateHeaderChecks, /* Checks that lead to the fatal error of the sync - process */ - AllChecks = NamespaceChecks | PrivateHeaderChecks | IncludeChecks | WeMeantItChecks, + PragmaOnceChecks = 16, + /* Checks that lead to the fatal error of the sync process: */ + CriticalChecks = PrivateHeaderChecks | PragmaOnceChecks, + AllChecks = NamespaceChecks | CriticalChecks | IncludeChecks | WeMeantItChecks, }; constexpr int LinkerScriptCommentAlignment = 55; @@ -90,6 +91,33 @@ std::string asciiToUpper(std::string s) return s; } +bool parseVersion(const std::string &version, int &major, int &minor) +{ + const size_t separatorPos = version.find('.'); + if (separatorPos == std::string::npos || separatorPos == (version.size() - 1) + || separatorPos == 0) + return false; + + try { + size_t pos = 0; + major = std::stoi(version.substr(0, separatorPos), &pos); + if (pos != separatorPos) + return false; + + const size_t nextPart = separatorPos + 1; + pos = 0; + minor = std::stoi(version.substr(nextPart), &pos); + if (pos != (version.size() - nextPart)) + return false; + } catch (const std::invalid_argument &) { + return false; + } catch (const std::out_of_range &) { + return false; + } + + return true; +} + class DummyOutputStream : public std::ostream { struct : public std::streambuf @@ -108,12 +136,46 @@ void printInternalError() << std::endl; } +void printFilesystemError(const std::filesystem::filesystem_error &fserr, std::string_view errorMsg) +{ + std::cerr << errorMsg << ": " << fserr.path1() << ".\n" + << fserr.what() << "(" << fserr.code().value() << ")" << std::endl; +} + std::filesystem::path normilizedPath(const std::string &path) { - return std::filesystem::path(std::filesystem::weakly_canonical(path).generic_string()); + try { + auto result = std::filesystem::path(std::filesystem::weakly_canonical(path).generic_string()); + return result; + } catch (const std::filesystem::filesystem_error &fserr) { + printFilesystemError(fserr, "Unable to normalize path"); + throw; + } } + +bool createDirectories(const std::string &path, std::string_view errorMsg, bool *exists = nullptr) +{ + bool result = true; + try { + if (!std::filesystem::exists(path)) { + if (exists) + *exists = false; + std::filesystem::create_directories(path); + } else { + if (exists) + *exists = true; + } + } catch (const std::filesystem::filesystem_error &fserr) { + result = false; + std::cerr << errorMsg << ": " << path << ".\n" + << fserr.code().message() << "(" << fserr.code().value() << "):" << fserr.what() + << std::endl; + } + return result; } +} // namespace utils + using FileStamp = std::filesystem::file_time_type; class CommandLineOptions @@ -145,12 +207,12 @@ public: const std::string &privateIncludeDir() const { return m_privateIncludeDir; } - const std::string &frameworkIncludeDir() const { return m_frameworkIncludeDir; } - const std::string &qpaIncludeDir() const { return m_qpaIncludeDir; } const std::string &rhiIncludeDir() const { return m_rhiIncludeDir; } + const std::string &ssgIncludeDir() const { return m_ssgIncludeDir; } + const std::string &stagingDir() const { return m_stagingDir; } const std::string &versionScriptFile() const { return m_versionScriptFile; } @@ -161,6 +223,8 @@ public: const std::regex &rhiHeadersRegex() const { return m_rhiHeadersRegex; } + const std::regex &ssgHeadersRegex() const { return m_ssgHeadersRegex; } + const std::regex &privateHeadersRegex() const { return m_privateHeadersRegex; } const std::regex &publicNamespaceRegex() const { return m_publicNamespaceRegex; } @@ -171,8 +235,6 @@ public: bool scanAllMode() const { return m_scanAllMode; } - bool isFramework() const { return m_isFramework; } - bool isInternal() const { return m_isInternal; } bool isNonQtModule() const { return m_isNonQtModule; } @@ -192,10 +254,9 @@ public: void printHelp() const { std::cout << "Usage: syncqt -sourceDir <dir> -binaryDir <dir> -module <module name>" - " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir> -rhiIncludeDir <dir>" + " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir> -rhiIncludeDir <dir> -ssgIncludeDir <dir>" " -stagingDir <dir> <-headers <header list>|-all> [-debug]" " [-versionScript <path>] [-qpaHeadersFilter <regex>] [-rhiHeadersFilter <regex>]" - " [-framework [-frameworkIncludeDir <dir>]]" " [-knownModules <module1> <module2>... <moduleN>]" " [-nonQt] [-internal] [-copy]\n" "" @@ -218,6 +279,8 @@ public: " generated QPA header files.\n" " -rhiIncludeDir Module include directory for the \n" " generated RHI header files.\n" + " -ssgIncludeDir Module include directory for the \n" + " generated SSG header files.\n" " -stagingDir Temporary staging directory to collect\n" " artifacts that need to be installed.\n" " -knownModules list of known modules. syncqt uses the\n" @@ -233,16 +296,14 @@ public: " the list of 'headers'.\n" " -rhiHeadersFilter Regex that filters rhi header files from.\n" " the list of 'headers'.\n" + " -ssgHeadersFilter Regex that filters ssg files from.\n" + " the list of 'headers'.\n" " -publicNamespaceFilter Symbols that are in the specified\n" " namespace.\n" " are treated as public symbols.\n" " -versionScript Generate linker version script by\n" " provided path.\n" " -debug Enable debug output.\n" - " -framework Indicates that module is framework.\n" - " -frameworkIncludeDir The directory to store the framework\n" - " header files.\n" - " E.g. QtCore.framework/Versions/A/Headers\n" " -copy Copy header files instead of creating\n" " aliases.\n" " -minimal Do not create CaMeL case headers for the\n" @@ -271,6 +332,7 @@ private: { std::string qpaHeadersFilter; std::string rhiHeadersFilter; + std::string ssgHeadersFilter; std::string privateHeadersFilter; std::string publicNamespaceFilter; static std::unordered_map<std::string, CommandLineOption<std::string>> stringArgumentMap = { @@ -280,13 +342,14 @@ private: { "-privateHeadersFilter", { &privateHeadersFilter, true } }, { "-qpaHeadersFilter", { &qpaHeadersFilter, true } }, { "-rhiHeadersFilter", { &rhiHeadersFilter, true } }, + { "-ssgHeadersFilter", { &ssgHeadersFilter, true } }, { "-includeDir", { &m_includeDir } }, { "-privateIncludeDir", { &m_privateIncludeDir } }, { "-qpaIncludeDir", { &m_qpaIncludeDir } }, { "-rhiIncludeDir", { &m_rhiIncludeDir } }, + { "-ssgIncludeDir", { &m_ssgIncludeDir } }, { "-stagingDir", { &m_stagingDir, true } }, { "-versionScript", { &m_versionScriptFile, true } }, - { "-frameworkIncludeDir", { &m_frameworkIncludeDir, true } }, { "-publicNamespaceFilter", { &publicNamespaceFilter, true } }, }; @@ -299,7 +362,7 @@ private: static const std::unordered_map<std::string, CommandLineOption<bool>> boolArgumentMap = { { "-nonQt", { &m_isNonQtModule, true } }, { "-debug", { &m_debug, true } }, - { "-help", { &m_printHelpOnly, true } }, { "-framework", { &m_isFramework, true } }, + { "-help", { &m_printHelpOnly, true } }, { "-internal", { &m_isInternal, true } }, { "-all", { &m_scanAllMode, true } }, { "-copy", { &m_copy, true } }, { "-minimal", { &m_minimal, true } }, { "-showonly", { &m_showOnly, true } }, { "-showOnly", { &m_showOnly, true } }, @@ -384,6 +447,7 @@ private: if (!parseArgument(argFromFile)) return false; } + ifs.close(); continue; } @@ -400,6 +464,9 @@ private: if (!rhiHeadersFilter.empty()) m_rhiHeadersRegex = std::regex(rhiHeadersFilter); + if (!ssgHeadersFilter.empty()) + m_ssgHeadersRegex = std::regex(ssgHeadersFilter); + if (!privateHeadersFilter.empty()) m_privateHeadersRegex = std::regex(privateHeadersFilter); @@ -432,10 +499,10 @@ private: // Convert all paths from command line to a generic one. void normilizePaths() { - static std::array<std::string *, 9> paths = { + static std::array<std::string *, 8> paths = { &m_sourceDir, &m_binaryDir, &m_includeDir, &m_privateIncludeDir, &m_qpaIncludeDir, &m_rhiIncludeDir, &m_stagingDir, - &m_versionScriptFile, &m_frameworkIncludeDir + &m_versionScriptFile, }; for (auto path : paths) { if (!path->empty()) @@ -450,15 +517,14 @@ private: std::string m_privateIncludeDir; std::string m_qpaIncludeDir; std::string m_rhiIncludeDir; + std::string m_ssgIncludeDir; std::string m_stagingDir; std::string m_versionScriptFile; - std::string m_frameworkIncludeDir; std::set<std::string> m_knownModules; std::set<std::string> m_headers; std::set<std::string> m_generatedHeaders; bool m_scanAllMode = false; bool m_copy = false; - bool m_isFramework = false; bool m_isNonQtModule = false; bool m_isInternal = false; bool m_printHelpOnly = false; @@ -468,6 +534,7 @@ private: bool m_warningsAreErrors = false; std::regex m_qpaHeadersRegex; std::regex m_rhiHeadersRegex; + std::regex m_ssgHeadersRegex; std::regex m_privateHeadersRegex; std::regex m_publicNamespaceRegex; @@ -539,7 +606,7 @@ class SyncScanner size_t m_currentFileLineNumber = 0; bool m_currentFileInSourceDir = false; - enum FileType { PublicHeader = 0, PrivateHeader = 1, QpaHeader = 2, ExportHeader = 4, RhiHeader = 8 }; + enum FileType { PublicHeader = 0, PrivateHeader = 1, QpaHeader = 2, ExportHeader = 4, RhiHeader = 8, SsgHeader = 16 }; unsigned int m_currentFileType = PublicHeader; int m_criticalChecks = CriticalChecks; @@ -686,13 +753,6 @@ public: // process eaiser. if (!copyGeneratedHeadersToStagingDirectory(m_commandLineArgs->stagingDir())) error = SyncFailed; - // We also need to have a copy of the generated header files in framework include - // directories when building with '-framework'. - if (m_commandLineArgs->isFramework()) { - if (!copyGeneratedHeadersToStagingDirectory( - m_commandLineArgs->frameworkIncludeDir(), true)) - error = SyncFailed; - } } return error; } @@ -703,15 +763,37 @@ public: bool skipCleanup = false) { bool result = true; - if (!std::filesystem::exists(outputDirectory)) { - std::filesystem::create_directories(outputDirectory); - } else if (!skipCleanup) { - for (const auto &entry : - std::filesystem::recursive_directory_iterator(outputDirectory)) { - if (m_producedHeaders.find(entry.path().filename().generic_string()) - == m_producedHeaders.end()) { - std::filesystem::remove(entry.path()); + bool outDirExists = false; + if (!utils::createDirectories(outputDirectory, "Unable to create staging directory", + &outDirExists)) + return false; + + if (outDirExists && !skipCleanup) { + try { + for (const auto &entry : + std::filesystem::recursive_directory_iterator(outputDirectory)) { + if (m_producedHeaders.find(entry.path().filename().generic_string()) + == m_producedHeaders.end()) { + // Check if header file came from another module as result of the + // cross-module deprecation before removing it. + std::string firstLine; + { + std::ifstream input(entry.path(), std::ifstream::in); + if (input.is_open()) { + std::getline(input, firstLine); + input.close(); + } + } + if (firstLine.find("#ifndef DEPRECATED_HEADER_" + + m_commandLineArgs->moduleName()) + == 0 + || firstLine.find("#ifndef DEPRECATED_HEADER_") != 0) + std::filesystem::remove(entry.path()); + } } + } catch (const std::filesystem::filesystem_error &fserr) { + utils::printFilesystemError(fserr, "Unable to clean the staging directory"); + return false; } } @@ -745,6 +827,9 @@ public: if (isHeaderRhi(m_currentFilename)) m_currentFileType = RhiHeader | PrivateHeader; + if (isHeaderSsg(m_currentFilename)) + m_currentFileType = SsgHeader | PrivateHeader; + if (std::regex_match(m_currentFilename, ExportsHeaderRegex)) m_currentFileType |= ExportHeader; } @@ -752,7 +837,7 @@ public: [[nodiscard]] bool processHeader(const std::filesystem::path &headerFile) { // This regex filters any paths that contain the '3rdparty' directory. - static const std::regex ThirdPartyFolderRegex(".+/3rdparty/.+"); + static const std::regex ThirdPartyFolderRegex("(^|.+/)3rdparty/.+"); // This regex filters '-config.h' and '-config_p.h' header files. static const std::regex ConfigHeaderRegex("^(q|.+-)config(_p)?\\.h"); @@ -783,6 +868,7 @@ public: bool isPrivate = m_currentFileType & PrivateHeader; bool isQpa = m_currentFileType & QpaHeader; bool isRhi = m_currentFileType & RhiHeader; + bool isSsg = m_currentFileType & SsgHeader; bool isExport = m_currentFileType & ExportHeader; scannerDebug() << "processHeader:start: " << headerFile @@ -790,6 +876,7 @@ public: << " isPrivate: " << isPrivate << " isQpa: " << isQpa << " isRhi: " << isRhi + << " isSsg: " << isSsg << std::endl; // Chose the directory where to generate the header aliases or to copy header file if @@ -799,20 +886,17 @@ public: outputDir = m_commandLineArgs->qpaIncludeDir(); else if (isRhi) outputDir = m_commandLineArgs->rhiIncludeDir(); + else if (isSsg) + outputDir = m_commandLineArgs->ssgIncludeDir(); else if (isPrivate) outputDir = m_commandLineArgs->privateIncludeDir(); - if (!std::filesystem::exists(outputDir)) - std::filesystem::create_directories(outputDir); + if (!utils::createDirectories(outputDir, "Unable to create output directory")) + return false; bool headerFileExists = std::filesystem::exists(headerFile); - std::filesystem::path headerFileRootName = - std::filesystem::weakly_canonical(headerFile, ec).root_name(); - std::string aliasedFilepath = !ec && headerFileRootName == m_outputRootName - ? std::filesystem::relative(headerFile, outputDir).generic_string() - : headerFile.generic_string(); - ec.clear(); + std::string aliasedFilepath = headerFile.generic_string(); std::string aliasPath = outputDir + '/' + m_currentFilename; @@ -844,13 +928,20 @@ public: } bool isGenerated = isHeaderGenerated(m_currentFileString); - bool is3rdParty = std::regex_match(m_currentFileString, ThirdPartyFolderRegex); + + // Make sure that we detect the '3rdparty' directory inside the source directory only, + // since full path to the Qt sources might contain '/3rdparty/' too. + bool is3rdParty = std::regex_match( + std::filesystem::relative(headerFile, m_commandLineArgs->sourceDir()) + .generic_string(), + ThirdPartyFolderRegex); + // No processing of generated Qt config header files. if (!std::regex_match(m_currentFilename, ConfigHeaderRegex)) { unsigned int skipChecks = m_commandLineArgs->scanAllMode() ? AllChecks : NoChecks; // Collect checks that should skipped for the header file. - if (m_commandLineArgs->isNonQtModule() || is3rdParty || isQpa || isRhi + if (m_commandLineArgs->isNonQtModule() || is3rdParty || isQpa || isRhi || isSsg || !m_currentFileInSourceDir || isGenerated) { skipChecks = AllChecks; } else { @@ -871,7 +962,7 @@ public: ParsingResult parsingResult; parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty - && !isQpa && !isRhi && !isPrivate && !isGenerated; + && !isQpa && !isRhi && !isSsg && !isPrivate && !isGenerated; if (!parseHeader(headerFile, parsingResult, skipChecks)) { scannerDebug() << "parseHeader failed: " << headerFile << std::endl; return false; @@ -888,7 +979,7 @@ public: // Add the '#if QT_CONFIG(<feature>)' check for header files that supposed to be // included into the module master header only if corresponding feature is enabled. bool willBeInModuleMasterHeader = false; - if (!isQpa && !isRhi && !isPrivate) { + if (!isQpa && !isRhi && !isSsg && !isPrivate) { if (m_currentFilename.find('_') == std::string::npos && parsingResult.masterInclude) { m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig; @@ -1000,6 +1091,9 @@ public: static const std::regex MacroRegex("^\\s*#.*"); // The regex's bellow check line for known pragmas: + // + // - 'once' is not allowed in installed headers, so error out. + // // - 'qt_sync_skip_header_check' avoid any header checks. // // - 'qt_sync_stop_processing' stops the header proccesing from a moment when pragma is @@ -1015,13 +1109,19 @@ public: // - 'qt_class(<symbol>)' manually declares the 'symbol' that should be used to generate // the CaMeL case header alias. // - // - 'qt_deprecates(<deprecated header file>)' indicates that this header file replaces - // the 'deprecated header file'. syncqt will create the deprecated header file' with - // the special deprecation content. See the 'generateDeprecatedHeaders' function - // for details. + // - 'qt_deprecates([module/]<deprecated header file>[,<major.minor>])' indicates that + // this header file replaces the 'deprecated header file'. syncqt will create the + // deprecated header file' with the special deprecation content. Pragma optionally + // accepts the Qt version where file should be removed. If the current Qt version is + // higher than the deprecation version, syncqt displays deprecation warning and skips + // generating the deprecated header. If the module is specified and is different from + // the one this header file belongs to, syncqt attempts to generate header files + // for the specified module. Cross-module deprecation only works within the same repo. + // See the 'generateDeprecatedHeaders' function for details. // // - 'qt_no_master_include' indicates that syncqt should avoid including this header // files into the module master header file. + static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)"); static const std::regex SkipHeaderCheckRegex("^#\\s*pragma qt_sync_skip_header_check$"); static const std::regex StopProcessingRegex("^#\\s*pragma qt_sync_stop_processing$"); static const std::regex SuspendProcessingRegex("^#\\s*pragma qt_sync_suspend_processing$"); @@ -1088,6 +1188,11 @@ public: std::size_t linesProcessed = 0; int faults = NoChecks; + const auto error = [&] () -> decltype(auto) { + return std::cerr << ErrorMessagePreamble << m_currentFileString + << ":" << m_currentFileLineNumber << " "; + }; + // Read file line by line while (std::getline(input, tmpLine)) { ++m_currentFileLineNumber; @@ -1176,7 +1281,8 @@ public: ++linesProcessed; bool skipSymbols = - (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader); + (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader) + || (m_currentFileType & SsgHeader); // Parse pragmas if (std::regex_match(buffer, MacroRegex)) { @@ -1203,6 +1309,13 @@ public: } else if (std::regex_match(buffer, match, DeprecatesPragmaRegex)) { m_deprecatedHeaders[match[1].str()] = m_commandLineArgs->moduleName() + '/' + m_currentFilename; + } else if (std::regex_match(buffer, OnceRegex)) { + if (!(skipChecks & PragmaOnceChecks)) { + faults |= PragmaOnceChecks; + error() << "\"#pragma once\" is not allowed in installed header files: " + "https://lists.qt-project.org/pipermail/development/2022-October/043121.html" + << std::endl; + } } else if (std::regex_match(buffer, match, IncludeRegex) && !isSuspended) { if (!(skipChecks & IncludeChecks)) { std::string includedHeader = match[1].str(); @@ -1211,9 +1324,7 @@ public: .filename() .generic_string())) { faults |= PrivateHeaderChecks; - std::cerr << ErrorMessagePreamble << m_currentFileString - << ":" << m_currentFileLineNumber - << " includes private header " << includedHeader << std::endl; + error() << "includes private header " << includedHeader << std::endl; } for (const auto &module : m_commandLineArgs->knownModules()) { std::string suggestedHeader = "Qt" + module + '/' + includedHeader; @@ -1272,6 +1383,7 @@ public: } } } + input.close(); // Error out if namespace checks are failed. if (!(skipChecks & NamespaceChecks)) { @@ -1316,13 +1428,12 @@ public: // - <class|stuct> StructName // - template <> class ClassName // - class ClassName : [public|protected|private] BaseClassName - // - class ClassName [final|Q_DECL_FINAL|sealed] + // - class ClassName [QT_TEXT_STREAM_FINAL|Q_DECL_FINAL|final|sealed] // And possible combinations of the above variants. static const std::regex ClassRegex( - "^ *(template *<.*> *)?(class|struct) +([^ <>]* " - "+)?((?!Q_DECL_FINAL|final|sealed)[^<\\s\\:]+) ?(<[^>\\:]*> " - "?)?\\s*(?:Q_DECL_FINAL|final|sealed)?\\s*((,|:)\\s*(public|protected|private)? " - "*.*)? *$"); + "^ *(template *<.*> *)?(class|struct +)([^<>:]*\\s+)?" // Preceding part + "((?!Q[A-Z_0-9]*_FINAL|final|sealed)Q[a-zA-Z0-9_]+)" // Actual symbol + "(\\s+Q[A-Z_0-9]*_FINAL|\\s+final|\\s+sealed)?\\s*(:|$).*"); // Trailing part // This regex checks if line contains function pointer typedef declaration like: // - typedef void (* QFunctionPointerType)(int, char); @@ -1333,10 +1444,6 @@ public: // - typedef AnySymbol<char> QAnySymbolType; static const std::regex TypedefRegex("^ *typedef\\s+(.*)\\s+(Q\\w+); *$"); - // This regex checks if symbols is the Qt public symbol. Assume that Qt public symbols start - // with the capital 'Q'. - static const std::regex QtClassRegex("^Q\\w+$"); - std::smatch match; if (std::regex_match(line, match, FunctionPointerRegex)) { symbol = match[1].str(); @@ -1344,8 +1451,6 @@ public: symbol = match[2].str(); } else if (std::regex_match(line, match, ClassRegex)) { symbol = match[4].str(); - if (!std::regex_match(symbol, QtClassRegex)) - symbol.clear(); } else { return false; } @@ -1362,6 +1467,11 @@ public: return std::regex_match(headerFileName, m_commandLineArgs->rhiHeadersRegex()); } + [[nodiscard]] bool isHeaderSsg(const std::string &headerFileName) + { + return std::regex_match(headerFileName, m_commandLineArgs->ssgHeadersRegex()); + } + [[nodiscard]] bool isHeaderPrivate(const std::string &headerFile) { return std::regex_match(headerFile, m_commandLineArgs->privateHeadersRegex()); @@ -1449,13 +1559,53 @@ public: { static std::regex cIdentifierSymbolsRegex("[^a-zA-Z0-9_]"); static std::string guard_base = "DEPRECATED_HEADER_" + m_commandLineArgs->moduleName(); + bool result = true; for (auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) { - std::string &replacement = it->second; + const std::string &descriptor = it->first; + const std::string &replacement = it->second; + + const auto separatorPos = descriptor.find(','); + std::string headerPath = descriptor.substr(0, separatorPos); + std::string versionDisclaimer; + if (separatorPos != std::string::npos) { + std::string version = descriptor.substr(separatorPos + 1); + versionDisclaimer = " and will be removed in Qt " + version; + int minor = 0; + int major = 0; + if (!utils::parseVersion(version, major, minor)) { + std::cerr << ErrorMessagePreamble + << "Invalid version format specified for the deprecated header file " + << headerPath << ": '" << version + << "'. Expected format: 'major.minor'.\n"; + result = false; + continue; + } + + if (QT_VERSION_MAJOR > major + || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) { + std::cerr << WarningMessagePreamble << headerPath + << " is marked as deprecated and will not be generated in Qt " + << QT_VERSION_STR + << ". The respective qt_deprecates pragma needs to be removed.\n"; + continue; + } + } + + const auto moduleSeparatorPos = headerPath.find('/'); + std::string headerName = moduleSeparatorPos != std::string::npos + ? headerPath.substr(moduleSeparatorPos + 1) + : headerPath; + const std::string moduleName = moduleSeparatorPos != std::string::npos + ? headerPath.substr(0, moduleSeparatorPos) + : m_commandLineArgs->moduleName(); + + bool isCrossModuleDeprecation = moduleName != m_commandLineArgs->moduleName(); + std::string qualifiedHeaderName = - std::regex_replace(it->first, cIdentifierSymbolsRegex, "_"); + std::regex_replace(headerName, cIdentifierSymbolsRegex, "_"); std::string guard = guard_base + "_" + qualifiedHeaderName; - std::string warningText = "Header <" + m_commandLineArgs->moduleName() + "/" + it->first - + "> is deprecated. Please include <" + replacement + "> instead."; + std::string warningText = "Header <" + moduleName + "/" + headerName + "> is deprecated" + + versionDisclaimer + ". Please include <" + replacement + "> instead."; std::stringstream buffer; buffer << "#ifndef " << guard << "\n" << "#define " << guard << "\n" @@ -1465,15 +1615,21 @@ public: << "# pragma message (\"" << warningText << "\")\n" << "#endif\n" << "#include <" << replacement << ">\n" - << "#if 0\n" - // TODO: Looks like qt_no_master_include is useless since deprecated headers are - // generated by syncqt but are never scanned. - << "#pragma qt_no_master_include\n" - << "#endif\n" << "#endif\n"; - writeIfDifferent(m_commandLineArgs->includeDir() + '/' + it->first, buffer.str()); + + const std::string outputDir = isCrossModuleDeprecation + ? m_commandLineArgs->includeDir() + "/../" + moduleName + : m_commandLineArgs->includeDir(); + writeIfDifferent(outputDir + '/' + headerName, buffer.str()); + + // Add header file to staging installation directory for cross-module deprecation case. + if (isCrossModuleDeprecation) { + const std::string stagingDir = outputDir + "/.syncqt_staging/"; + writeIfDifferent(stagingDir + headerName, buffer.str()); + } + m_producedHeaders.insert(headerName); } - return true; + return result; } [[nodiscard]] bool generateHeaderCheckExceptions() @@ -1495,7 +1651,7 @@ public: return writeIfDifferent(m_commandLineArgs->versionScriptFile(), buffer.str()); } - bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst); + bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) noexcept; void updateSymbolDescriptor(const std::string &symbol, const std::string &file, SymbolDescriptor::SourceType type); }; @@ -1521,7 +1677,8 @@ SyncScanner::makeHeaderAbsolute(const std::string &filename) const return utils::normilizedPath(filename); } -bool SyncScanner::updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) +bool SyncScanner::updateOrCopy(const std::filesystem::path &src, + const std::filesystem::path &dst) noexcept { if (m_commandLineArgs->showOnly()) return true; @@ -1568,7 +1725,7 @@ bool SyncScanner::generateQtCamelCaseFileIfContentChanged(const std::string &out std::string buffer = "#include \""; buffer += aliasedFilePath; - buffer += "\"\n"; + buffer += "\" // IWYU pragma: export\n"; return writeIfDifferent(outputFilePath, buffer); } @@ -1595,7 +1752,7 @@ bool SyncScanner::generateAliasedHeaderFileIfTimestampChanged(const std::string std::cerr << "Unable to write header file alias: " << outputFilePath << std::endl; return false; } - ofs << "#include \"" << aliasedFilePath << "\"\n"; + ofs << "#include \"" << aliasedFilePath << "\" // IWYU pragma: export\n"; ofs.close(); return true; } @@ -1610,8 +1767,9 @@ bool SyncScanner::writeIfDifferent(const std::string &outputFile, const std::str std::filesystem::path outputFilePath(outputFile); std::string outputDirectory = outputFilePath.parent_path().string(); - if (!std::filesystem::exists(outputDirectory)) - std::filesystem::create_directories(outputDirectory); + + if (!utils::createDirectories(outputDirectory, "Unable to create output directory")) + return false; auto expectedSize = buffer.size(); #ifdef _WINDOWS @@ -1625,6 +1783,10 @@ bool SyncScanner::writeIfDifferent(const std::string &outputFile, const std::str memset(rdBuffer, 0, bufferSize); std::ifstream ifs(outputFile, std::fstream::in); + if (!ifs.is_open()) { + std::cerr << "Unable to open " << outputFile << " for comparison." << std::endl; + return false; + } std::streamsize currentPos = 0; std::size_t bytesRead = 0; |