summaryrefslogtreecommitdiffstats
path: root/src/tools/syncqt/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/syncqt/main.cpp')
-rw-r--r--src/tools/syncqt/main.cpp336
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;