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.cpp553
1 files changed, 409 insertions, 144 deletions
diff --git a/src/tools/syncqt/main.cpp b/src/tools/syncqt/main.cpp
index b4c37ec626..5df7b03fd5 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
@@ -19,6 +18,7 @@
#include <iostream>
#include <fstream>
#include <string>
+#include <string_view>
#include <cstring>
#include <sstream>
#include <filesystem>
@@ -27,6 +27,7 @@
#include <regex>
#include <map>
#include <set>
+#include <stdexcept>
#include <array>
enum ErrorCodes {
@@ -42,15 +43,19 @@ 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;
static const std::regex GlobalHeaderRegex("^q(.*)global\\.h$");
+constexpr std::string_view ErrorMessagePreamble = "ERROR: ";
+constexpr std::string_view WarningMessagePreamble = "WARNING: ";
+
// This comparator is used to sort include records in master header.
// It's used to put q.*global.h file to the top of the list and sort all other files alphabetically.
bool MasterHeaderIncludeComparator(const std::string &a, const std::string &b)
@@ -86,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
@@ -104,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::absolute(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
@@ -141,10 +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; }
@@ -153,6 +221,10 @@ public:
const std::regex &qpaHeadersRegex() const { return m_qpaHeadersRegex; }
+ 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; }
@@ -163,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; }
@@ -184,10 +254,9 @@ public:
void printHelp() const
{
std::cout << "Usage: syncqt -sourceDir <dir> -binaryDir <dir> -module <module name>"
- " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir>"
+ " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir> -rhiIncludeDir <dir> -ssgIncludeDir <dir>"
" -stagingDir <dir> <-headers <header list>|-all> [-debug]"
- " [-versionScript <path>] [-qpaHeadersFilter <regex>]"
- " [-framework [-frameworkIncludeDir <dir>]]"
+ " [-versionScript <path>] [-qpaHeadersFilter <regex>] [-rhiHeadersFilter <regex>]"
" [-knownModules <module1> <module2>... <moduleN>]"
" [-nonQt] [-internal] [-copy]\n"
""
@@ -208,6 +277,10 @@ public:
" generated private header files.\n"
" -qpaIncludeDir Module include directory for the \n"
" 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"
@@ -221,16 +294,16 @@ public:
" from the list of 'headers'.\n"
" -qpaHeadersFilter Regex that filters qpa header files from.\n"
" 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"
@@ -258,34 +331,38 @@ private:
[[nodiscard]] bool parseArguments(int argc, char *argv[])
{
std::string qpaHeadersFilter;
+ std::string rhiHeadersFilter;
+ std::string ssgHeadersFilter;
std::string privateHeadersFilter;
std::string publicNamespaceFilter;
- std::set<std::string> generatedHeaders;
static std::unordered_map<std::string, CommandLineOption<std::string>> stringArgumentMap = {
{ "-module", { &m_moduleName } },
{ "-sourceDir", { &m_sourceDir } },
{ "-binaryDir", { &m_binaryDir } },
{ "-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 } },
};
static const std::unordered_map<std::string, CommandLineOption<std::set<std::string>>>
listArgumentMap = {
{ "-headers", { &m_headers, true } },
- { "-generatedHeaders", { &generatedHeaders, true } },
+ { "-generatedHeaders", { &m_generatedHeaders, true } },
{ "-knownModules", { &m_knownModules, true } },
};
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 } },
@@ -294,12 +371,9 @@ private:
std::string *currentValue = nullptr;
std::set<std::string> *currentListValue = nullptr;
- for (int i = 1; i < argc; ++i) {
- std::string arg(argv[i]);
- if (arg.size() == 0)
- continue;
- if (arg.size() > 0 && arg[0] == '-') {
+ auto parseArgument = [&currentValue, &currentListValue](const std::string &arg) -> bool {
+ if (arg[0] == '-') {
currentValue = nullptr;
currentListValue = nullptr;
{
@@ -310,7 +384,7 @@ private:
return false;
}
currentValue = it->second.value;
- continue;
+ return true;
}
}
@@ -322,7 +396,7 @@ private:
return false;
}
*(it->second.value) = true;
- continue;
+ return true;
}
}
@@ -334,22 +408,51 @@ private:
return false;
}
currentListValue = it->second.value;
- continue;
+ currentListValue->insert(""); // Indicate that argument is provided
+ return true;
}
}
std::cerr << "Unknown argument: " << arg << std::endl;
return false;
+ }
+
+ if (currentValue != nullptr) {
+ *currentValue = arg;
+ currentValue = nullptr;
+ } else if (currentListValue != nullptr) {
+ currentListValue->insert(arg);
} else {
- if (currentValue != nullptr) {
- *currentValue = arg;
- currentValue = nullptr;
- } else if (currentListValue != nullptr) {
- currentListValue->insert(arg);
- } else {
- std::cerr << "Unknown argument: " << arg << std::endl;
+ std::cerr << "Unknown argument: " << arg << std::endl;
+ return false;
+ }
+ return true;
+ };
+
+ for (int i = 1; i < argc; ++i) {
+ std::string arg(argv[i]);
+ if (arg.empty())
+ continue;
+
+ if (arg[0] == '@') {
+ std::ifstream ifs(arg.substr(1), std::ifstream::in);
+ if (!ifs.is_open()) {
+ std::cerr << "Unable to open rsp file: " << arg[0] << std::endl;
+ return false;
}
+ std::string argFromFile;
+ while (std::getline(ifs, argFromFile)) {
+ if (argFromFile.empty())
+ continue;
+ if (!parseArgument(argFromFile))
+ return false;
+ }
+ ifs.close();
+ continue;
}
+
+ if (!parseArgument(arg))
+ return false;
}
if (m_printHelpOnly)
@@ -358,6 +461,12 @@ private:
if (!qpaHeadersFilter.empty())
m_qpaHeadersRegex = std::regex(qpaHeadersFilter);
+ if (!rhiHeadersFilter.empty())
+ m_rhiHeadersRegex = std::regex(rhiHeadersFilter);
+
+ if (!ssgHeadersFilter.empty())
+ m_ssgHeadersRegex = std::regex(ssgHeadersFilter);
+
if (!privateHeadersFilter.empty())
m_privateHeadersRegex = std::regex(privateHeadersFilter);
@@ -365,32 +474,18 @@ private:
m_publicNamespaceRegex = std::regex(publicNamespaceFilter);
if (m_headers.empty() && !m_scanAllMode) {
- std::cerr << "You need to specify either -headers or -all option.";
+ std::cerr << "You need to specify either -headers or -all option." << std::endl;
return false;
}
if (!m_headers.empty() && m_scanAllMode) {
std::cerr << "Both -headers and -all are specified. Need to choose only one"
- "operational mode.";
+ "operational mode." << std::endl;
return false;
}
- for (auto header : generatedHeaders) {
- if (header.size() == 0)
- continue;
- if (header[0] == '@') {
- std::ifstream ifs(header.substr(1), std::ifstream::in);
- if (ifs.is_open()) {
- std::string headerFromFile;
- while (std::getline(ifs, headerFromFile)) {
- if (!headerFromFile.empty())
- m_generatedHeaders.insert(headerFromFile);
- }
- }
- } else {
- m_generatedHeaders.insert(header);
- }
- }
+ for (const auto &argument : listArgumentMap)
+ argument.second.value->erase("");
bool ret = true;
ret &= checkRequiredArguments(stringArgumentMap);
@@ -406,7 +501,8 @@ private:
{
static std::array<std::string *, 8> paths = {
&m_sourceDir, &m_binaryDir, &m_includeDir, &m_privateIncludeDir,
- &m_qpaIncludeDir, &m_stagingDir, &m_versionScriptFile, &m_frameworkIncludeDir
+ &m_qpaIncludeDir, &m_rhiIncludeDir, &m_stagingDir,
+ &m_versionScriptFile,
};
for (auto path : paths) {
if (!path->empty())
@@ -420,15 +516,15 @@ private:
std::string m_includeDir;
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;
@@ -437,6 +533,8 @@ private:
bool m_showOnly = false;
bool m_warningsAreErrors = false;
std::regex m_qpaHeadersRegex;
+ std::regex m_rhiHeadersRegex;
+ std::regex m_ssgHeadersRegex;
std::regex m_privateHeadersRegex;
std::regex m_publicNamespaceRegex;
@@ -501,20 +599,26 @@ class SyncScanner
enum { Active, Stopped, IgnoreNext, Ignore } m_versionScriptGeneratorState = Active;
+ std::filesystem::path m_outputRootName;
std::filesystem::path m_currentFile;
std::string m_currentFilename;
std::string m_currentFileString;
size_t m_currentFileLineNumber = 0;
bool m_currentFileInSourceDir = false;
- enum FileType { PublicHeader = 0, PrivateHeader = 1, QpaHeader = 2, ExportHeader = 4 };
+ enum FileType { PublicHeader = 0, PrivateHeader = 1, QpaHeader = 2, ExportHeader = 4, RhiHeader = 8, SsgHeader = 16 };
unsigned int m_currentFileType = PublicHeader;
int m_criticalChecks = CriticalChecks;
+ std::string_view m_warningMessagePreamble;
public:
SyncScanner(CommandLineOptions *commandLineArgs)
- : m_commandLineArgs(commandLineArgs), m_masterHeaderContents(MasterHeaderIncludeComparator)
+ : m_commandLineArgs(commandLineArgs),
+ m_masterHeaderContents(MasterHeaderIncludeComparator),
+ m_outputRootName(
+ std::filesystem::weakly_canonical(m_commandLineArgs->includeDir()).root_name()),
+ m_warningMessagePreamble(WarningMessagePreamble)
{
}
@@ -526,7 +630,11 @@ public:
ErrorCodes sync()
{
- m_criticalChecks = m_commandLineArgs->warningsAreErrors() ? AllChecks : CriticalChecks;
+ if (m_commandLineArgs->warningsAreErrors()) {
+ m_criticalChecks = AllChecks;
+ m_warningMessagePreamble = ErrorMessagePreamble;
+ }
+
m_versionScriptGeneratorState =
m_commandLineArgs->versionScriptFile().empty() ? Stopped : Active;
auto error = NoError;
@@ -536,13 +644,26 @@ public:
if (m_commandLineArgs->scanAllMode()) {
for (auto const &entry :
std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) {
- if (entry.is_regular_file() && isHeader(entry)
- && !isDocFileHeuristic(entry.path().generic_string())) {
- const std::string filePath = entry.path().generic_string();
- const std::string fileName = entry.path().filename().generic_string();
- scannerDebug() << "Checking: " << filePath << std::endl;
+
+ const bool isRegularFile = entry.is_regular_file();
+ const bool isHeaderFlag = isHeader(entry);
+ const bool isDocFileHeuristicFlag =
+ isDocFileHeuristic(entry.path().generic_string());
+ const bool shouldProcessHeader =
+ isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag;
+ const std::string filePath = entry.path().generic_string();
+
+ if (shouldProcessHeader) {
+ scannerDebug() << "Processing header: " << filePath << std::endl;
if (!processHeader(makeHeaderAbsolute(filePath)))
error = SyncFailed;
+ } else {
+ scannerDebug()
+ << "Skipping processing header: " << filePath
+ << " isRegularFile: " << isRegularFile
+ << " isHeaderFlag: " << isHeaderFlag
+ << " isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
+ << std::endl;
}
}
} else {
@@ -552,20 +673,13 @@ public:
const auto &headers = m_commandLineArgs->headers();
for (auto it = headers.begin(); it != headers.end(); ++it) {
const auto &header = *it;
- if (header.size() > 0 && header[0] == '@') {
- std::ifstream ifs(header.substr(1), std::ifstream::in);
- if (ifs.is_open()) {
- std::string headerFromFile;
- while (std::getline(ifs, headerFromFile)) {
- if (!headerFromFile.empty())
- rspHeaders.insert(headerFromFile);
- }
- }
- } else if (!processHeader(makeHeaderAbsolute(header))) {
+ scannerDebug() << "Processing header: " << header << std::endl;
+ if (!processHeader(makeHeaderAbsolute(header))) {
error = SyncFailed;
}
}
for (const auto &header : rspHeaders) {
+ scannerDebug() << "Processing header: " << header << std::endl;
if (!processHeader(makeHeaderAbsolute(header)))
error = SyncFailed;
}
@@ -634,18 +748,11 @@ public:
error = SyncFailed;
}
- if (!m_commandLineArgs->scanAllMode()) {
+ if (!m_commandLineArgs->scanAllMode() && !m_commandLineArgs->stagingDir().empty()) {
// Copy the generated files to a spearate staging directory to make the installation
// 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;
}
@@ -656,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;
}
}
@@ -695,6 +824,12 @@ public:
if (isHeaderQpa(m_currentFilename))
m_currentFileType = QpaHeader | PrivateHeader;
+ 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;
}
@@ -702,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");
@@ -720,7 +855,7 @@ public:
// Check if a directory is passed as argument. That shouldn't happen, print error and exit.
if (m_currentFilename.empty()) {
- std::cerr << "Header file name of " << m_currentFileString << "is empty";
+ std::cerr << "Header file name of " << m_currentFileString << "is empty" << std::endl;
return false;
}
@@ -728,27 +863,41 @@ public:
FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
if (ec)
originalStamp = FileStamp::clock::now();
+ ec.clear();
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() << headerFile << " m_currentFilename: " << m_currentFilename
- << " isPrivate: " << isPrivate << " isQpa: " << isQpa << std::endl;
+ scannerDebug()
+ << "processHeader:start: " << headerFile
+ << " m_currentFilename: " << m_currentFilename
+ << " 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
// the '-copy' argument is passed.
std::string outputDir = m_commandLineArgs->includeDir();
if (isQpa)
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::string aliasedFilepath =
- std::filesystem::relative(headerFile, outputDir).generic_string();
+
+ std::string aliasedFilepath = headerFile.generic_string();
+
std::string aliasPath = outputDir + '/' + m_currentFilename;
// If the '-copy' argument is passed, we copy the original file to a corresponding output
@@ -779,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
+ if (m_commandLineArgs->isNonQtModule() || is3rdParty || isQpa || isRhi || isSsg
|| !m_currentFileInSourceDir || isGenerated) {
skipChecks = AllChecks;
} else {
@@ -806,13 +962,15 @@ public:
ParsingResult parsingResult;
parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
- && !isQpa && !isPrivate && !isGenerated;
- if (!parseHeader(headerFile, originalStamp, parsingResult, skipChecks))
+ && !isQpa && !isRhi && !isSsg && !isPrivate && !isGenerated;
+ if (!parseHeader(headerFile, parsingResult, skipChecks)) {
+ scannerDebug() << "parseHeader failed: " << headerFile << std::endl;
return false;
+ }
// Record the private header file inside the version script content.
if (isPrivate && !m_commandLineArgs->versionScriptFile().empty()
- && parsingResult.versionScriptContent.size() > 0) {
+ && !parsingResult.versionScriptContent.empty()) {
m_versionScriptContents.insert(m_versionScriptContents.end(),
parsingResult.versionScriptContent.begin(),
parsingResult.versionScriptContent.end());
@@ -820,12 +978,22 @@ 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.
- if (!isQpa && !isPrivate) {
+ bool willBeInModuleMasterHeader = false;
+ if (!isQpa && !isRhi && !isSsg && !isPrivate) {
if (m_currentFilename.find('_') == std::string::npos
&& parsingResult.masterInclude) {
m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
+ willBeInModuleMasterHeader = true;
}
}
+
+ scannerDebug()
+ << "processHeader:end: " << headerFile
+ << " is3rdParty: " << is3rdParty
+ << " isGenerated: " << isGenerated
+ << " m_currentFileInSourceDir: " << m_currentFileInSourceDir
+ << " willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
+ << std::endl;
} else if (m_currentFilename == "qconfig.h") {
// Hardcode generating of QtConfig alias
updateSymbolDescriptor("QtConfig", "qconfig.h", SyncScanner::SymbolDescriptor::Pragma);
@@ -849,6 +1017,7 @@ public:
switch (m_versionScriptGeneratorState) {
case Ignore:
+ scannerDebug() << "line ignored: " << buffer << std::endl;
m_versionScriptGeneratorState = Active;
return;
case Stopped:
@@ -860,6 +1029,9 @@ public:
break;
}
+ if (buffer.empty())
+ return;
+
std::smatch match;
std::string symbol;
if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
@@ -871,7 +1043,7 @@ public:
symbol = match[1].str();
// checkLineForSymbols(buffer, symbol);
- if (symbol.size() > 0 && symbol[symbol.size() - 1] != ';') {
+ if (!symbol.empty() && symbol[symbol.size() - 1] != ';') {
std::string relPath = m_currentFileInSourceDir
? std::filesystem::relative(m_currentFile, m_commandLineArgs->sourceDir())
.string()
@@ -910,7 +1082,7 @@ public:
// 'result' the function output value that stores the result of parsing.
// 'skipChecks' checks that are not applicable for the header file.
[[nodiscard]] bool parseHeader(const std::filesystem::path &headerFile,
- const FileStamp &timeStamp, ParsingResult &result,
+ ParsingResult &result,
unsigned int skipChecks)
{
if (m_commandLineArgs->showOnly())
@@ -919,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
@@ -934,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$");
@@ -951,7 +1132,7 @@ public:
// This regex checks if header contains 'We mean it' disclaimer. All private headers should
// contain them.
- static const std::regex WeMeantItRegex("\\s*// We mean it\\.");
+ static const std::string_view WeMeantItString("We mean it.");
// The regex's check if the content of header files is wrapped with the Qt namespace macros.
static const std::regex BeginNamespaceRegex("^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
@@ -978,8 +1159,8 @@ public:
// This regex looks for the ELFVERSION tag this is control key-word for the version script
// content processing.
// ELFVERSION tag accepts the following values:
- // - stop - stops the symbols lookup for a versino script starting from this line.
- // - ignore-next - ignores the line followed but the current one.
+ // - stop - stops the symbols lookup for a version script starting from this line.
+ // - ignore-next - ignores the line followed by the current one.
// - ignore - ignores the current line.
static const std::regex ElfVersionTagRegex(".*ELFVERSION:(stop|ignore-next|ignore).*");
@@ -1007,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;
@@ -1021,6 +1207,8 @@ public:
// - start-end of class/structures
// And avoid processing of the the data inside these blocks.
for (std::size_t i = 0; i < line.size(); ++i) {
+ if (line[i] == '\r')
+ continue;
if (bracesDepth == namespaceCount) {
if (line[i] == '/') {
if ((i + 1) < line.size()) {
@@ -1029,7 +1217,7 @@ public:
continue;
} else if (line[i + 1] == '/') { // Single line comment
if (!(skipChecks & WeMeantItChecks)
- && std::regex_match(line, WeMeantItRegex)) {
+ && line.find(WeMeantItString) != std::string::npos) {
hasWeMeantIt = true;
continue;
}
@@ -1053,7 +1241,8 @@ public:
}
if (isMultiLineComment) {
- if (!(skipChecks & WeMeantItChecks) && std::regex_match(line, WeMeantItRegex)) {
+ if (!(skipChecks & WeMeantItChecks) &&
+ line.find(WeMeantItString) != std::string::npos) {
hasWeMeantIt = true;
continue;
}
@@ -1080,18 +1269,20 @@ public:
}
line.clear();
- if (buffer.size() == 0)
- continue;
scannerDebug() << m_currentFilename << ": " << buffer << std::endl;
if (m_currentFileType & PrivateHeader) {
parseVersionScriptContent(buffer, result);
}
+ if (buffer.empty())
+ continue;
+
++linesProcessed;
bool skipSymbols =
- (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader);
+ (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader)
+ || (m_currentFileType & SsgHeader);
// Parse pragmas
if (std::regex_match(buffer, MacroRegex)) {
@@ -1118,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();
@@ -1126,16 +1324,14 @@ public:
.filename()
.generic_string())) {
faults |= PrivateHeaderChecks;
- std::cerr << "ERROR: " << 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;
if (std::filesystem::exists(m_commandLineArgs->includeDir() + "/../"
+ suggestedHeader)) {
faults |= IncludeChecks;
- std::cerr << "WARNING: " << m_currentFileString
+ std::cerr << m_warningMessagePreamble << m_currentFileString
<< ":" << m_currentFileLineNumber
<< " includes " << includedHeader
<< " when it should include "
@@ -1187,27 +1383,28 @@ public:
}
}
}
+ input.close();
// Error out if namespace checks are failed.
if (!(skipChecks & NamespaceChecks)) {
if (hasQtBeginNamespace) {
if (qtBeginNamespace != qtEndNamespace) {
faults |= NamespaceChecks;
- std::cerr << "WARNING: " << m_currentFileString
+ std::cerr << m_warningMessagePreamble << m_currentFileString
<< " the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
<< " doesn't match the end namespace macro QT_END_NAMESPACE"
<< qtEndNamespace << std::endl;
}
} else {
faults |= NamespaceChecks;
- std::cerr << "WARNING: " << m_currentFileString
+ std::cerr << m_warningMessagePreamble << m_currentFileString
<< " does not include QT_BEGIN_NAMESPACE" << std::endl;
}
}
if (!(skipChecks & WeMeantItChecks) && !hasWeMeantIt) {
faults |= WeMeantItChecks;
- std::cerr << "WARNING: " << m_currentFileString
+ std::cerr << m_warningMessagePreamble << m_currentFileString
<< " does not have the \"We mean it.\" warning"
<< std::endl;
}
@@ -1272,6 +1469,16 @@ public:
return std::regex_match(headerFileName, m_commandLineArgs->qpaHeadersRegex());
}
+ [[nodiscard]] bool isHeaderRhi(const std::string &headerFileName)
+ {
+ 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());
@@ -1359,13 +1566,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"
@@ -1375,15 +1622,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()
@@ -1405,7 +1658,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);
};
@@ -1431,7 +1684,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;
@@ -1478,7 +1732,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);
}
@@ -1505,7 +1759,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;
}
@@ -1520,15 +1774,26 @@ 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
+ // File on disk has \r\n instead of just \n
+ expectedSize += std::count(buffer.begin(), buffer.end(), '\n');
+#endif
if (std::filesystem::exists(outputFilePath)
- && buffer.size() == std::filesystem::file_size(outputFilePath)) {
+ && expectedSize == std::filesystem::file_size(outputFilePath)) {
char rdBuffer[bufferSize];
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;