summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAlexey Edelev <alexey.edelev@qt.io>2022-08-15 18:29:41 +0200
committerAlexey Edelev <alexey.edelev@qt.io>2022-09-27 13:12:11 +0200
commitb89d63515bb352cecfd87e709320a2db5b6a1906 (patch)
tree4f6e40d6e14991af1c9082ab7508a49a6360f25a /src
parent458ec4cb5442315c2c923ba78faf45fdd729a109 (diff)
Replace the syncqt.pl script with syncqt tool
syncqt.pl adds an extra dependency on perl when building Qt. Modern C++ provides the convenient cross-platform way to access a filesystem and to use regular expressions, so we may replace the perl script with C++ application. The syncqt executable is built at configure time and installed as QtCore tool. It's running at configure time to deliver the required header files for IDE to build a consistent code model and at the build time to keep tracking changes in header files and generate the missing aliases without reconfiguring. 'syncqt' only parses header files from a CMake build tree, so the resulting Qt installation only contains interfacing headers that belong to the platform that Qt is built for. 'sync.profile' files are not used as the 'source of truth' for sync qt procedure anymore, all the necessary information is taken from either CMake files at configure time or from the module header files while parsing them. syncqt.pl is still in place since it's required as fallback solution for a smooth transition to the new syncqt implementation for all qt repositories. This patchset only enables the C++ based syncqt for 'qtbase' repository. From the performance perspective C++ version works faster then perl script, also the configure time is reduced significally on subsequent reconfigurations - up x2 times faster when re-configuring repository, but it also takes time to compile the tool itself the first time. Numbers for qtbase: syncqt.pl syncqt.cpp initial: 0m16,035s 0m20,413s reconfig: 0m6,819s 0m3,725s The syncing procedure can be run separately for each module using <ModuleName>_sync_headers targets. The 'sync_headers' target can be used to sync all the modules at once. Task-number: QTBUG-87480 Task-number: QTBUG-103196 Change-Id: I8c938bcaf88a8713b39bbfd66d9e7ef12b2c3523 Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/3rdparty/harfbuzz-ng/CMakeLists.txt2
-rw-r--r--src/3rdparty/zlib/CMakeLists.txt2
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/corelib/CMakeLists.txt13
-rw-r--r--src/corelib/global/qtconfigmacros.h5
-rw-r--r--src/corelib/global/qtversionchecks.h5
-rw-r--r--src/entrypoint/CMakeLists.txt2
-rw-r--r--src/gui/CMakeLists.txt1
-rw-r--r--src/plugins/platforms/eglfs/CMakeLists.txt2
-rw-r--r--src/tools/bootstrap/CMakeLists.txt4
-rw-r--r--src/tools/syncqt/CMakeLists.txt30
-rw-r--r--src/tools/syncqt/main.cpp1567
12 files changed, 1628 insertions, 7 deletions
diff --git a/src/3rdparty/harfbuzz-ng/CMakeLists.txt b/src/3rdparty/harfbuzz-ng/CMakeLists.txt
index d52a5b7c5b..b03ad1279c 100644
--- a/src/3rdparty/harfbuzz-ng/CMakeLists.txt
+++ b/src/3rdparty/harfbuzz-ng/CMakeLists.txt
@@ -70,6 +70,8 @@ qt_internal_add_3rdparty_library(BundledHarfbuzz
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/harfbuzz>
)
+qt_internal_add_sync_header_dependencies(BundledHarfbuzz Core)
+
# GHS compiler doesn't support the __restrict keyword
if(INTEGRITY)
target_compile_definitions(BundledHarfbuzz PRIVATE __restrict=)
diff --git a/src/3rdparty/zlib/CMakeLists.txt b/src/3rdparty/zlib/CMakeLists.txt
index 701dab7dd3..ee9ece80fc 100644
--- a/src/3rdparty/zlib/CMakeLists.txt
+++ b/src/3rdparty/zlib/CMakeLists.txt
@@ -34,6 +34,8 @@ qt_internal_add_3rdparty_library(BundledZLIB
$<TARGET_PROPERTY:Core,INCLUDE_DIRECTORIES>
)
+qt_internal_add_sync_header_dependencies(BundledZLIB Core)
+
qt_disable_warnings(BundledZLIB)
qt_set_symbol_visibility_hidden(BundledZLIB)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index df235433a2..0a2c955ffe 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -15,6 +15,8 @@ if(QT_FEATURE_gui)
qt_feature_evaluate_features("${CMAKE_CURRENT_SOURCE_DIR}/gui/configure.cmake")
endif()
+add_subdirectory(tools/syncqt)
+
function(find_or_build_bootstrap_names)
if (QT_WILL_BUILD_TOOLS)
add_subdirectory(tools/bootstrap) # bootstrap library
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index cd698a57ef..1b41c659dc 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -377,10 +377,15 @@ endif()
# additional json files.
qt6_extract_metatypes(Core ${core_metatype_args})
-set_property(TARGET Core APPEND PROPERTY
- PUBLIC_HEADER "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig.h")
-set_property(TARGET Core APPEND PROPERTY
- PRIVATE_HEADER "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig_p.h")
+target_sources(Core PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig.h"
+ "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig_p.h"
+)
+set_source_files_properties(
+ "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig.h"
+ "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig_p.h"
+ PROPERTIES GENERATED TRUE
+)
# Find ELF interpreter and define a macro for that:
if ((LINUX OR HURD) AND NOT CMAKE_CROSSCOMPILING AND BUILD_SHARED_LIBS)
diff --git a/src/corelib/global/qtconfigmacros.h b/src/corelib/global/qtconfigmacros.h
index 0ff6e2955a..51f6e651bc 100644
--- a/src/corelib/global/qtconfigmacros.h
+++ b/src/corelib/global/qtconfigmacros.h
@@ -5,7 +5,10 @@
#define QTCONFIGMACROS_H
#ifdef QT_BOOTSTRAPPED
-#include <QtCore/qconfig-bootstrapped.h>
+// qconfig-bootstrapped.h is not supposed to be a part of the synced header files. So we find it by
+// the include path specified for Bootstrap library in the source tree instead of the build tree as
+// it's done for regular header files.
+#include "qconfig-bootstrapped.h"
#else
#include <QtCore/qconfig.h>
#include <QtCore/qtcore-config.h>
diff --git a/src/corelib/global/qtversionchecks.h b/src/corelib/global/qtversionchecks.h
index d6fad1ed6c..8f3bd8b371 100644
--- a/src/corelib/global/qtversionchecks.h
+++ b/src/corelib/global/qtversionchecks.h
@@ -10,7 +10,10 @@
#endif
#ifdef QT_BOOTSTRAPPED
-#include <QtCore/qconfig-bootstrapped.h>
+// qconfig-bootstrapped.h is not supposed to be a part of the synced header files. So we find it by
+// the include path specified for Bootstrap library in the source tree instead of the build tree as
+// it's done for regular header files.
+#include "qconfig-bootstrapped.h"
#else
#include <QtCore/qconfig.h>
#include <QtCore/qtcore-config.h>
diff --git a/src/entrypoint/CMakeLists.txt b/src/entrypoint/CMakeLists.txt
index 81a68aff04..845ce419ea 100644
--- a/src/entrypoint/CMakeLists.txt
+++ b/src/entrypoint/CMakeLists.txt
@@ -109,6 +109,8 @@ if(WIN32)
target_compile_definitions(EntryPointPrivate INTERFACE QT_NEEDS_QMAIN)
qt_internal_extend_target(EntryPointImplementation DEFINES QT_NEEDS_QMAIN)
endif()
+
+ qt_internal_add_sync_header_dependencies(EntryPointImplementation Core)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index f6c2b2979a..b611724835 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -887,7 +887,6 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_filesystemmodel
qt_internal_extend_target(Gui CONDITION QT_FEATURE_vulkan
SOURCES
rhi/qrhivulkan.cpp rhi/qrhivulkan_p.h
- rhi/qrhivulkan_p_p.h
rhi/qrhivulkanext_p.h
vulkan/qbasicvulkanplatforminstance.cpp vulkan/qbasicvulkanplatforminstance_p.h
vulkan/qplatformvulkaninstance.cpp vulkan/qplatformvulkaninstance.h
diff --git a/src/plugins/platforms/eglfs/CMakeLists.txt b/src/plugins/platforms/eglfs/CMakeLists.txt
index 8972e7fa26..108900518c 100644
--- a/src/plugins/platforms/eglfs/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/CMakeLists.txt
@@ -46,6 +46,8 @@ qt_internal_add_module(EglFSDeviceIntegrationPrivate
Qt::FbSupportPrivate
Qt::GuiPrivate
EGL::EGL # special case
+ HEADER_SYNC_SOURCE_DIRECTORY
+ "${CMAKE_CURRENT_SOURCE_DIR}/api"
)
#### Keys ignored in scope 2:.:.:eglfsdeviceintegration.pro:<TRUE>:
diff --git a/src/tools/bootstrap/CMakeLists.txt b/src/tools/bootstrap/CMakeLists.txt
index 8964e27fda..66d16781aa 100644
--- a/src/tools/bootstrap/CMakeLists.txt
+++ b/src/tools/bootstrap/CMakeLists.txt
@@ -11,6 +11,9 @@
# The bootstrap library has a few manual tweaks compared to other
# libraries.
qt_add_library(Bootstrap STATIC)
+
+qt_internal_add_sync_header_dependencies(Bootstrap Core)
+
# special case end
qt_internal_extend_target(Bootstrap
SOURCES
@@ -104,6 +107,7 @@ qt_internal_extend_target(Bootstrap
../../3rdparty/tinycbor/src
PUBLIC_INCLUDE_DIRECTORIES # special case
$<TARGET_PROPERTY:Core,INCLUDE_DIRECTORIES> # special case
+ ../../corelib/global
PUBLIC_LIBRARIES # special case
Qt::Platform # special case
)
diff --git a/src/tools/syncqt/CMakeLists.txt b/src/tools/syncqt/CMakeLists.txt
new file mode 100644
index 0000000000..0152c53450
--- /dev/null
+++ b/src/tools/syncqt/CMakeLists.txt
@@ -0,0 +1,30 @@
+# The tool should be optimized for maximum performance when working.
+qt_internal_get_optimize_full_flags(optimize_full_flags)
+
+set(compile_definitions
+ QT_VERSION_STR="${PROJECT_VERSION}"
+ QT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
+ QT_VERSION_MINOR=${PROJECT_VERSION_MINOR}
+ QT_VERSION_PATCH=${PROJECT_VERSION_PATCH}
+ QT_NAMESPACE="${QT_NAMESPACE}"
+)
+
+if(CMAKE_OSX_ARCHITECTURES)
+ set(osx_architectures "-DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}")
+endif()
+qt_get_tool_target_name(target_name syncqt)
+qt_internal_add_configure_time_tool(${target_name}
+ DEFINES ${compile_definitions}
+ COMPILE_OPTIONS ${optimize_full_flags}
+ TOOLS_TARGET Core
+ INSTALL_DIRECTORY "${INSTALL_LIBEXECDIR}"
+ CMAKE_FLAGS
+ -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=TRUE
+ -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD}
+ # std::filesystem API is only available in macOS 10.15+
+ -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.15
+ "${osx_architectures}"
+ SOURCES
+ "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp"
+ CONFIG RelWithDebInfo
+)
diff --git a/src/tools/syncqt/main.cpp b/src/tools/syncqt/main.cpp
new file mode 100644
index 0000000000..03a80ef973
--- /dev/null
+++ b/src/tools/syncqt/main.cpp
@@ -0,0 +1,1567 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*
+ * The tool generates deployment artifacts for the Qt builds such as:
+ * - CaMeL case header files named by public C++ symbols located in public module header files
+ * - 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.
+ * 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
+ * on the selected mode, the tool either scans the filesystem to find header files or use the
+ * pre-defined list of header files.
+ */
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <sstream>
+#include <filesystem>
+#include <unordered_map>
+#include <vector>
+#include <regex>
+#include <map>
+#include <set>
+#include <array>
+
+enum ErrorCodes {
+ NoError = 0,
+ InvalidArguments,
+ SyncFailed,
+};
+
+// Enum contains the list of checks that can be executed on header files.
+enum HeaderChecks {
+ NoChecks = 0,
+ NamespaceChecks = 1, /* Checks if header file is wrapped with QT_<BEGIN|END>_NAMESPACE macros */
+ 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,
+};
+
+constexpr int LinkerScriptCommentAlignment = 55;
+
+static const std::regex GlobalHeaderRegex("^q(.*)global\\.h$");
+
+// 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)
+{
+ std::smatch amatch;
+ std::smatch bmatch;
+
+ if (std::regex_match(a, amatch, GlobalHeaderRegex)) {
+ if (std::regex_match(b, bmatch, GlobalHeaderRegex)) {
+ return amatch[1].str().empty()
+ || (!bmatch[1].str().empty() && amatch[1].str() < bmatch[1].str());
+ }
+ return true;
+ } else if (std::regex_match(b, bmatch, GlobalHeaderRegex)) {
+ return false;
+ }
+
+ return a < b;
+};
+
+namespace utils {
+std::string asciiToLower(std::string s)
+{
+ std::transform(s.begin(), s.end(), s.begin(),
+ [](unsigned char c) { return (c >= 'A' && c <= 'Z') ? c | 0x20 : c; });
+ return s;
+}
+
+std::string asciiToUpper(std::string s)
+{
+ std::transform(s.begin(), s.end(), s.begin(),
+ [](unsigned char c) { return (c >= 'a' && c <= 'z') ? c & 0xdf : c; });
+ return s;
+}
+
+class DummyOutputStream : public std::ostream
+{
+ struct : public std::streambuf
+ {
+ int overflow(int c) override { return c; }
+ } buff;
+
+public:
+ DummyOutputStream() : std::ostream(&buff) { }
+} DummyOutput;
+
+void printInternalError()
+{
+ std::cerr << "Internal error. Please create bugreport at https://bugreports.qt.io "
+ "using 'Build tools: Other component.'"
+ << std::endl;
+}
+
+std::filesystem::path normilizedPath(const std::string &path)
+{
+ return std::filesystem::path(std::filesystem::absolute(path).generic_string());
+}
+}
+
+using FileStamp = std::filesystem::file_time_type;
+
+class CommandLineOptions
+{
+ template<typename T>
+ struct CommandLineOption
+ {
+ CommandLineOption(T *_value, bool _isOptional = false)
+ : value(_value), isOptional(_isOptional)
+ {
+ }
+
+ T *value;
+ bool isOptional;
+ };
+
+public:
+ CommandLineOptions(int argc, char *argv[]) : m_isValid(parseArguments(argc, argv)) { }
+
+ bool isValid() const { return m_isValid; }
+
+ const std::string &moduleName() const { return m_moduleName; }
+
+ const std::string &sourceDir() const { return m_sourceDir; }
+
+ const std::string &binaryDir() const { return m_binaryDir; }
+
+ const std::string &includeDir() const { return m_includeDir; }
+
+ 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 &stagingDir() const { return m_stagingDir; }
+
+ const std::string &versionScriptFile() const { return m_versionScriptFile; }
+
+ const std::set<std::string> &knownModules() const { return m_knownModules; }
+
+ const std::regex &qpaHeadersRegex() const { return m_qpaHeadersRegex; }
+
+ const std::regex &privateHeadersRegex() const { return m_privateHeadersRegex; }
+
+ const std::regex &publicNamespaceRegex() const { return m_publicNamespaceRegex; }
+
+ const std::set<std::string> &headers() const { return m_headers; }
+
+ const std::set<std::string> &generatedHeaders() const { return m_generatedHeaders; }
+
+ bool scanAllMode() const { return m_scanAllMode; }
+
+ bool isFramework() const { return m_isFramework; }
+
+ bool isInternal() const { return m_isInternal; }
+
+ bool isNonQtModule() const { return m_isNonQtModule; }
+
+ bool printHelpOnly() const { return m_printHelpOnly; }
+
+ bool debug() const { return m_debug; }
+
+ bool copy() const { return m_copy; }
+
+ bool minimal() const { return m_minimal; }
+
+ bool showOnly() const { return m_showOnly; }
+
+ void printHelp() const
+ {
+ std::cout << "Usage: syncqt -sourceDir <dir> -binaryDir <dir> -module <module name>"
+ " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir>"
+ " -stagingDir <dir> <-headers <header list>|-all> [-debug]"
+ " [-versionScript <path>] [-qpaHeadersFilter <regex>]"
+ " [-framework [-frameworkIncludeDir <dir>]]"
+ " [-knownModules <module1> <module2>... <moduleN>]"
+ " [-nonQt] [-internal] [-copy]\n"
+ ""
+ "Mandatory arguments:\n"
+ " -module Module name.\n"
+ " -headers List of header files.\n"
+ " -all In 'all' mode syncqt scans source\n"
+ " directory for public qt headers and\n"
+ " artifacts not considering CMake source\n"
+ " tree. The main use cases are the \n"
+ " generating of documentation and creating\n"
+ " API review changes.\n"
+ " -sourceDir Module source directory.\n"
+ " -binaryDir Module build directory.\n"
+ " -includeDir Module include directory where the\n"
+ " generated header files will be located.\n"
+ " -privateIncludeDir Module include directory for the\n"
+ " generated private header files.\n"
+ " -qpaIncludeDir Module include directory for the \n"
+ " generated QPA 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"
+ " list to check the #include macros\n"
+ " consistency.\n"
+ "Optional arguments:\n"
+ " -internal Indicates that the module is internal.\n"
+ " -nonQt Indicates that the module is not a Qt\n"
+ " module.\n"
+ " -privateHeadersFilter Regex that filters private header files\n"
+ " from the list of 'headers'.\n"
+ " -qpaHeadersFilter Regex that filters qpa header 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"
+ " public C++ symbols.\n"
+ " -showonly Show actions, but not perform them.\n"
+ " -help Print this help.\n";
+ }
+
+private:
+ template<typename T>
+ [[nodiscard]] bool checkRequiredArguments(const std::unordered_map<std::string, T> &arguments)
+ {
+ bool ret = true;
+ for (const auto &argument : arguments) {
+ if (!argument.second.isOptional
+ && (!argument.second.value || argument.second.value->size()) == 0) {
+ std::cerr << "Missing argument: " << argument.first << std::endl;
+ ret = false;
+ }
+ }
+ return ret;
+ }
+
+ [[nodiscard]] bool parseArguments(int argc, char *argv[])
+ {
+ std::string qpaHeadersFilter;
+ 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 } },
+ { "-includeDir", { &m_includeDir } },
+ { "-privateIncludeDir", { &m_privateIncludeDir } },
+ { "-qpaIncludeDir", { &m_qpaIncludeDir } },
+ { "-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 } },
+ { "-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 } },
+ { "-internal", { &m_isInternal, true } }, { "-all", { &m_scanAllMode, true } },
+ { "-copy", { &m_copy, true } }, { "-minimal", { &m_minimal, true } },
+ { "-showonly", { &m_showOnly, true } }, { "-showOnly", { &m_showOnly, true } },
+ };
+
+ 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] == '-') {
+ currentValue = nullptr;
+ currentListValue = nullptr;
+ {
+ auto it = stringArgumentMap.find(arg);
+ if (it != stringArgumentMap.end()) {
+ if (it->second.value == nullptr) {
+ utils::printInternalError();
+ return false;
+ }
+ currentValue = it->second.value;
+ continue;
+ }
+ }
+
+ {
+ auto it = boolArgumentMap.find(arg);
+ if (it != boolArgumentMap.end()) {
+ if (it->second.value == nullptr) {
+ utils::printInternalError();
+ return false;
+ }
+ *(it->second.value) = true;
+ continue;
+ }
+ }
+
+ {
+ auto it = listArgumentMap.find(arg);
+ if (it != listArgumentMap.end()) {
+ if (it->second.value == nullptr) {
+ utils::printInternalError();
+ return false;
+ }
+ currentListValue = it->second.value;
+ continue;
+ }
+ }
+
+ std::cerr << "Unknown argument: " << arg << std::endl;
+ return false;
+ } else {
+ if (currentValue != nullptr) {
+ *currentValue = arg;
+ currentValue = nullptr;
+ } else if (currentListValue != nullptr) {
+ currentListValue->insert(arg);
+ } else {
+ std::cerr << "Unknown argument: " << arg << std::endl;
+ }
+ }
+ }
+
+ if (!qpaHeadersFilter.empty())
+ m_qpaHeadersRegex = std::regex(qpaHeadersFilter);
+
+ if (!privateHeadersFilter.empty())
+ m_privateHeadersRegex = std::regex(privateHeadersFilter);
+
+ if (!publicNamespaceFilter.empty())
+ m_publicNamespaceRegex = std::regex(publicNamespaceFilter);
+
+ if (m_headers.empty() && !m_scanAllMode) {
+ std::cerr << "You need to specify either -headers or -all option.";
+ return false;
+ }
+
+ if (!m_headers.empty() && m_scanAllMode) {
+ std::cerr << "Both -headers and -all are specified. Need to choose only one"
+ "operational mode.";
+ 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);
+ }
+ }
+
+ bool ret = true;
+ ret &= checkRequiredArguments(stringArgumentMap);
+ ret &= checkRequiredArguments(listArgumentMap);
+
+ normilizePaths();
+
+ return ret;
+ }
+
+ // Convert all paths from command line to a generic one.
+ void normilizePaths()
+ {
+ static std::array<std::string *, 8> paths = {
+ &m_sourceDir, &m_binaryDir, &m_includeDir, &m_privateIncludeDir,
+ &m_qpaIncludeDir, &m_stagingDir, &m_versionScriptFile, &m_frameworkIncludeDir
+ };
+ for (auto path : paths) {
+ if (!path->empty())
+ *path = utils::normilizedPath(*path).generic_string();
+ }
+ }
+
+ std::string m_moduleName;
+ std::string m_sourceDir;
+ std::string m_binaryDir;
+ std::string m_includeDir;
+ std::string m_privateIncludeDir;
+ std::string m_qpaIncludeDir;
+ 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;
+ bool m_debug = false;
+ bool m_minimal = false;
+ bool m_showOnly = false;
+ std::regex m_qpaHeadersRegex;
+ std::regex m_privateHeadersRegex;
+ std::regex m_publicNamespaceRegex;
+
+ bool m_isValid;
+};
+
+class SyncScanner
+{
+ class SymbolDescriptor
+ {
+ public:
+ // Where the symbol comes from
+ enum SourceType {
+ Pragma = 0, // pragma qt_class is mentioned a header file
+ Declaration, // The symbol declaration inside a header file
+ MaxSourceType
+ };
+
+ void update(const std::string &file, SourceType type)
+ {
+ if (type < m_type) {
+ m_file = file;
+ m_type = type;
+ }
+ }
+
+ // The file that contains a symbol.
+ const std::string &file() const { return m_file; }
+
+ private:
+ SourceType m_type = MaxSourceType;
+ std::string m_file;
+ };
+ using SymbolContainer = std::unordered_map<std::string, SymbolDescriptor>;
+
+ struct ParsingResult
+ {
+ std::vector<std::string> versionScriptContent;
+ std::string requireConfig;
+ bool masterInclude = true;
+ };
+
+ CommandLineOptions *m_commandLineArgs = nullptr;
+
+ std::map<std::string /* header file name */, std::string /* header feature guard name */,
+ decltype(MasterHeaderIncludeComparator) *>
+ m_masterHeaderContents;
+
+ std::unordered_map<std::string /* the deprecated header name*/,
+ std::string /* the replacement */>
+ m_deprecatedHeaders;
+ std::vector<std::string> m_versionScriptContents;
+ std::set<std::string> m_producedHeaders;
+ std::vector<std::string> m_headerCheckExceptions;
+ SymbolContainer m_symbols;
+ std::ostream &scannerDebug() const
+ {
+ if (m_commandLineArgs->debug())
+ return std::cout;
+ return utils::DummyOutput;
+ }
+
+ enum { Active, Stopped, IgnoreNext, Ignore } m_versionScriptGeneratorState = Active;
+
+ 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 };
+ unsigned int m_currentFileType = PublicHeader;
+
+public:
+ SyncScanner(CommandLineOptions *commandLineArgs)
+ : m_commandLineArgs(commandLineArgs), m_masterHeaderContents(MasterHeaderIncludeComparator)
+ {
+ }
+
+ // The function converts the relative path to a header files to the absolute. It also makes the
+ // path canonical(removes '..' and '.' parts of the path). The source directory passed in
+ // '-sourceDir' command line argument is used as base path for relative paths to create the
+ // absolute path.
+ [[nodiscard]] std::filesystem::path makeHeaderAbsolute(const std::string &filename) const;
+
+ ErrorCodes sync()
+ {
+ m_versionScriptGeneratorState =
+ m_commandLineArgs->versionScriptFile().empty() ? Stopped : Active;
+ auto error = NoError;
+
+ // In the scan all mode we ingore the list of header files that is specified in the
+ // '-headers' argument, and collect header files from the source directory tree.
+ 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;
+ if (!processHeader(makeHeaderAbsolute(filePath)))
+ error = SyncFailed;
+ }
+ }
+ } else {
+ // Since the list of header file is quite big syncqt supports response files to avoid
+ // the issues with long command lines.
+ std::set<std::string> rspHeaders;
+ 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))) {
+ error = SyncFailed;
+ }
+ }
+ for (const auto &header : rspHeaders) {
+ if (!processHeader(makeHeaderAbsolute(header)))
+ error = SyncFailed;
+ }
+ }
+
+ // No further processing in minimal mode.
+ if (m_commandLineArgs->minimal())
+ return error;
+
+ // Generate aliases for all unique symbols collected during the header files parsing.
+ for (auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
+ const std::string &filename = it->second.file();
+ if (!filename.empty()) {
+ if (generateQtCamelCaseFileIfContentChanged(
+ m_commandLineArgs->includeDir() + '/' + it->first, filename)) {
+ m_producedHeaders.insert(it->first);
+ } else {
+ error = SyncFailed;
+ }
+ }
+ }
+
+ // Generate the header file containing version information.
+ if (!m_commandLineArgs->isNonQtModule()) {
+ std::string moduleNameLower = utils::asciiToLower(m_commandLineArgs->moduleName());
+ std::string versionHeaderFilename(moduleNameLower + "version.h");
+ std::string versionHeaderCamel(m_commandLineArgs->moduleName() + "Version");
+ std::string versionFile = m_commandLineArgs->includeDir() + '/' + versionHeaderFilename;
+
+ std::error_code ec;
+ FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
+ if (ec)
+ originalStamp = FileStamp::clock::now();
+
+ if (generateVersionHeader(versionFile)) {
+ if (!generateAliasedHeaderFileIfTimestampChanged(
+ m_commandLineArgs->includeDir() + '/' + versionHeaderCamel,
+ versionHeaderFilename, originalStamp)) {
+ error = SyncFailed;
+ }
+ m_masterHeaderContents[versionHeaderFilename] = {};
+ m_producedHeaders.insert(versionHeaderFilename);
+ m_producedHeaders.insert(versionHeaderCamel);
+ } else {
+ error = SyncFailed;
+ }
+ }
+
+ if (!m_commandLineArgs->scanAllMode()) {
+ if (!m_commandLineArgs->isNonQtModule()) {
+ if (!generateDeprecatedHeaders())
+ error = SyncFailed;
+
+ if (!generateHeaderCheckExceptions())
+ error = SyncFailed;
+ }
+
+ if (!m_commandLineArgs->versionScriptFile().empty()) {
+ if (!generateLinkerVersionScript())
+ error = SyncFailed;
+ }
+ }
+
+ if (!m_commandLineArgs->isNonQtModule()) {
+ if (!generateMasterHeader())
+ error = SyncFailed;
+ }
+
+ if (!m_commandLineArgs->scanAllMode()) {
+ // 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;
+ }
+
+ // The function copies files, that were generated while the sync procedure to a staging
+ // directory. This is necessary to simplify the installation of the generated files.
+ [[nodiscard]] bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory,
+ 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());
+ }
+ }
+ }
+
+ for (const auto &header : m_producedHeaders) {
+ std::filesystem::path src(m_commandLineArgs->includeDir() + '/' + header);
+ std::filesystem::path dst(outputDirectory + '/' + header);
+ if (!m_commandLineArgs->showOnly())
+ result &= updateOrCopy(src, dst);
+ }
+ return result;
+ }
+
+ void resetCurrentFileInfoData(const std::filesystem::path &headerFile)
+ {
+ // This regex filters the generated '*exports.h' and '*exports_p.h' header files.
+ static const std::regex ExportsHeaderRegex("^q(.*)exports(_p)?\\.h$");
+
+ m_currentFile = headerFile;
+ m_currentFileLineNumber = 0;
+ m_currentFilename = m_currentFile.filename().generic_string();
+ m_currentFileType = PublicHeader;
+ m_currentFileString = m_currentFile.generic_string();
+ m_currentFileInSourceDir = m_currentFileString.find(m_commandLineArgs->sourceDir()) == 0;
+
+ if (isHeaderPrivate(m_currentFilename))
+ m_currentFileType = PrivateHeader;
+
+ if (isHeaderQpa(m_currentFilename))
+ m_currentFileType = QpaHeader | PrivateHeader;
+
+ if (std::regex_match(m_currentFilename, ExportsHeaderRegex))
+ m_currentFileType |= ExportHeader;
+ }
+
+ [[nodiscard]] bool processHeader(const std::filesystem::path &headerFile)
+ {
+ // This regex filters any paths that contain the '3rdparty' directory.
+ 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");
+
+ resetCurrentFileInfoData(headerFile);
+ // We assume that header files ouside of the module source or build directories do not
+ // belong to the module. Skip any processing.
+ if (!m_currentFileInSourceDir
+ && m_currentFileString.find(m_commandLineArgs->binaryDir()) != 0) {
+ scannerDebug() << "Header file: " << headerFile
+ << " is outside the sync directories. Skipping." << std::endl;
+ m_headerCheckExceptions.push_back(m_currentFileString);
+ return true;
+ }
+
+ // 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";
+ return false;
+ }
+
+ std::error_code ec;
+ FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
+ if (ec)
+ originalStamp = FileStamp::clock::now();
+
+ bool isPrivate = m_currentFileType & PrivateHeader;
+ bool isQpa = m_currentFileType & QpaHeader;
+ bool isExport = m_currentFileType & ExportHeader;
+ scannerDebug() << headerFile << " m_currentFilename: " << m_currentFilename
+ << " isPrivate: " << isPrivate << " isQpa: " << isQpa << 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 (isPrivate)
+ outputDir = m_commandLineArgs->privateIncludeDir();
+
+ if (!std::filesystem::exists(outputDir))
+ std::filesystem::create_directories(outputDir);
+
+ bool headerFileExists = std::filesystem::exists(headerFile);
+ std::string aliasedFilepath =
+ std::filesystem::relative(headerFile, outputDir).generic_string();
+ std::string aliasPath = outputDir + '/' + m_currentFilename;
+
+ // If the '-copy' argument is passed, we copy the original file to a corresponding output
+ // directory otherwise we only create a header file alias that contains relative path to
+ // the original header file in the module source or build tree.
+ if (m_commandLineArgs->copy() && headerFileExists) {
+ if (!updateOrCopy(headerFile, aliasPath))
+ return false;
+ } else {
+ if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath,
+ originalStamp))
+ return false;
+ }
+
+ // No further processing in minimal mode.
+ if (m_commandLineArgs->minimal())
+ return true;
+
+ // Stop processing if header files doesn't exist. This happens at configure time, since
+ // either header files are generated later than syncqt is running or header files only
+ // generated at build time. These files will be processed at build time, if CMake files
+ // contain the correct dependencies between the missing header files and the module
+ // 'sync_headers' targets.
+ if (!headerFileExists) {
+ scannerDebug() << "Header file: " << headerFile
+ << " doesn't exist, but is added to syncqt scanning. Skipping.";
+ return true;
+ }
+
+ bool isGenerated = isHeaderGenerated(m_currentFileString);
+ bool is3rdParty = std::regex_match(m_currentFileString, 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
+ || !m_currentFileInSourceDir || isGenerated) {
+ skipChecks = AllChecks;
+ } else {
+ if (std::regex_match(m_currentFilename, GlobalHeaderRegex) || isExport)
+ skipChecks |= NamespaceChecks;
+
+ if (isHeaderPCH(m_currentFilename))
+ skipChecks |= WeMeantItChecks;
+
+ if (isPrivate) {
+ skipChecks |= NamespaceChecks;
+ skipChecks |= PrivateHeaderChecks;
+ skipChecks |= IncludeChecks;
+ } else {
+ skipChecks |= WeMeantItChecks;
+ }
+ }
+
+ ParsingResult parsingResult;
+ parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
+ && !isQpa && !isPrivate && !isGenerated;
+ if (!parseHeader(headerFile, originalStamp, parsingResult, skipChecks))
+ return false;
+
+ // Record the private header file inside the version script content.
+ if (isPrivate && !m_commandLineArgs->versionScriptFile().empty()
+ && parsingResult.versionScriptContent.size() > 0) {
+ m_versionScriptContents.insert(m_versionScriptContents.end(),
+ parsingResult.versionScriptContent.begin(),
+ parsingResult.versionScriptContent.end());
+ }
+
+ // 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) {
+ if (m_currentFilename.find('_') == std::string::npos
+ && parsingResult.masterInclude) {
+ m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
+ }
+ }
+ } else if (m_currentFilename == "qconfig.h") {
+ // Hardcode generating of QtConfig alias
+ updateSymbolDescriptor("QtConfig", "qconfig.h", SyncScanner::SymbolDescriptor::Pragma);
+ }
+
+ return true;
+ }
+
+ void parseVersionScriptContent(const std::string buffer, ParsingResult &result)
+ {
+ // This regex looks for the symbols that needs to be placed into linker version script.
+ static const std::regex VersionScriptSymbolRegex(
+ "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
+
+ // This regex looks for the namespaces that needs to be placed into linker version script.
+ static const std::regex VersionScriptNamespaceRegex(
+ "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
+
+ // This regex filters the tailing colon from the symbol name.
+ static const std::regex TrailingColonRegex("([\\w]+):$");
+
+ switch (m_versionScriptGeneratorState) {
+ case Ignore:
+ m_versionScriptGeneratorState = Active;
+ return;
+ case Stopped:
+ return;
+ case IgnoreNext:
+ m_versionScriptGeneratorState = Ignore;
+ break;
+ case Active:
+ break;
+ }
+
+ std::smatch match;
+ std::string symbol;
+ if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty())
+ symbol = match[1].str();
+ else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex))
+ symbol = match[1].str();
+
+ if (std::regex_match(symbol, match, TrailingColonRegex))
+ symbol = match[1].str();
+
+ // checkLineForSymbols(buffer, symbol);
+ if (symbol.size() > 0 && symbol[symbol.size() - 1] != ';') {
+ std::string relPath = m_currentFileInSourceDir
+ ? std::filesystem::relative(m_currentFile, m_commandLineArgs->sourceDir())
+ .string()
+ : std::filesystem::relative(m_currentFile, m_commandLineArgs->binaryDir())
+ .string();
+
+ std::string versionStringRecord = " *";
+ size_t startPos = 0;
+ size_t endPos = 0;
+ while (endPos != std::string::npos) {
+ endPos = symbol.find("::", startPos);
+ size_t length = endPos != std::string::npos ? (endPos - startPos)
+ : (symbol.size() - startPos);
+ if (length > 0) {
+ std::string symbolPart = symbol.substr(startPos, length);
+ versionStringRecord += std::to_string(symbolPart.size());
+ versionStringRecord += symbolPart;
+ }
+ startPos = endPos + 2;
+ }
+ versionStringRecord += "*;";
+ if (versionStringRecord.size() < LinkerScriptCommentAlignment)
+ versionStringRecord +=
+ std::string(LinkerScriptCommentAlignment - versionStringRecord.size(), ' ');
+ versionStringRecord += " # ";
+ versionStringRecord += relPath;
+ versionStringRecord += ":";
+ versionStringRecord += std::to_string(m_currentFileLineNumber);
+ versionStringRecord += "\n";
+ result.versionScriptContent.push_back(versionStringRecord);
+ }
+ }
+
+ // The function parses 'headerFile' and collect artifacts that are used at generating step.
+ // 'timeStamp' is saved in internal structures to compare it when generating files.
+ // '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,
+ unsigned int skipChecks)
+ {
+ if (m_commandLineArgs->showOnly())
+ std::cout << headerFile << " [" << m_commandLineArgs->moduleName() << "]" << std::endl;
+ // This regex checks if line contains a macro.
+ static const std::regex MacroRegex("^\\s*#.*");
+
+ // The regex's bellow check line for known pragmas:
+ // - 'qt_sync_skip_header_check' avoid any header checks.
+ //
+ // - 'qt_sync_stop_processing' stops the header proccesing from a moment when pragma is
+ // found. Important note: All the parsing artifacts were found before this point are
+ // stored for further processing.
+ //
+ // - 'qt_sync_suspend_processing' pauses processing and skip lines inside a header until
+ // 'qt_sync_resume_processing' is found. 'qt_sync_stop_processing' stops processing if
+ // it's found before the 'qt_sync_resume_processing'.
+ //
+ // - 'qt_sync_resume_processing' resumes processing after 'qt_sync_suspend_processing'.
+ //
+ // - '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_no_master_include' indicates that syncqt should avoid including this header
+ // files into the module master header file.
+ 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$");
+ static const std::regex ResumeProcessingRegex("^#\\s*pragma qt_sync_resume_processing$");
+ static const std::regex ExplixitClassPragmaRegex("^#\\s*pragma qt_class\\(([^\\)]+)\\)$");
+ static const std::regex DeprecatesPragmaRegex("^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$");
+ static const std::regex NoMasterIncludePragmaRegex("^#\\s*pragma qt_no_master_include$");
+
+ // 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\\.");
+
+ // 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_]+)?$");
+ static const std::regex EndNamespaceRegex("^QT_END_NAMESPACE(_[A-Z_]+)?$");
+
+ // This regex checks if line contains the include macro of the following formats:
+ // - #include <file>
+ // - #include "file"
+ // - # include <file>
+ static const std::regex IncludeRegex("^#\\s*include\\s*[<\"](.+)[>\"]");
+
+ // This regex checks if line contains namespace definition.
+ static const std::regex NamespaceRegex("\\s*namespace ([^ ]*)\\s+");
+
+ // This regex checks if line contains the Qt iterator declaration, that need to have
+ // CaMel case header alias.
+ static const std::regex DeclareIteratorRegex("^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
+
+ // This regex checks if header file contains the QT_REQUIRE_CONFIG call.
+ // The macro argument is used to wrap an include of the header file inside the module master
+ // header file with the '#if QT_CONFIG(<feature>)' guard.
+ static const std::regex RequireConfigRegex("^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
+
+ // 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.
+ // - ignore - ignores the current line.
+ static const std::regex ElfVersionTagRegex(".*ELFVERSION:(stop|ignore-next|ignore).*");
+
+ std::ifstream input(headerFile, std::ifstream::in);
+ if (!input.is_open()) {
+ std::cerr << "Unable to open " << headerFile << std::endl;
+ return false;
+ }
+
+ bool hasQtBeginNamespace = false;
+ std::string qtBeginNamespace;
+ std::string qtEndNamespace;
+ bool hasWeMeantIt = false;
+ bool isSuspended = false;
+ bool isMultiLineComment = false;
+ std::size_t bracesDepth = 0;
+ std::size_t namespaceCount = 0;
+ std::string namespaceString;
+
+ std::smatch match;
+
+ std::string buffer;
+ std::string line;
+ std::string tmpLine;
+ std::size_t linesProcessed = 0;
+ int faults = NoChecks;
+
+ // Read file line by line
+ while (std::getline(input, tmpLine)) {
+ ++m_currentFileLineNumber;
+ line.append(tmpLine);
+ if (line.empty() || line.at(line.size() - 1) == '\\') {
+ continue;
+ }
+ buffer.clear();
+ buffer.reserve(line.size());
+ // Optimize processing by looking for a special sequences such as:
+ // - start-end of comments
+ // - 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 (bracesDepth == namespaceCount) {
+ if (line[i] == '/') {
+ if ((i + 1) < line.size()) {
+ if (line[i + 1] == '*') {
+ isMultiLineComment = true;
+ continue;
+ } else if (line[i + 1] == '/') { // Single line comment
+ if (!(skipChecks & WeMeantItChecks)
+ && std::regex_match(line, WeMeantItRegex)) {
+ hasWeMeantIt = true;
+ continue;
+ }
+ if (m_versionScriptGeneratorState != Stopped
+ && std::regex_match(line, match, ElfVersionTagRegex)) {
+ if (match[1].str() == "ignore")
+ m_versionScriptGeneratorState = Ignore;
+ else if (match[1].str() == "ignore-next")
+ m_versionScriptGeneratorState = IgnoreNext;
+ else if (match[1].str() == "stop")
+ m_versionScriptGeneratorState = Stopped;
+ }
+ break;
+ }
+ }
+ } else if (line[i] == '*' && (i + 1) < line.size() && line[i + 1] == '/') {
+ ++i;
+ isMultiLineComment = false;
+ continue;
+ }
+ }
+
+ if (isMultiLineComment) {
+ if (!(skipChecks & WeMeantItChecks) && std::regex_match(line, WeMeantItRegex)) {
+ hasWeMeantIt = true;
+ continue;
+ }
+ continue;
+ }
+
+ if (line[i] == '{') {
+ if (std::regex_match(buffer, match, NamespaceRegex)) {
+ ++namespaceCount;
+ namespaceString += "::";
+ namespaceString += match[1].str();
+ }
+ ++bracesDepth;
+ continue;
+ } else if (line[i] == '}') {
+ if (namespaceCount > 0 && bracesDepth == namespaceCount) {
+ namespaceString.resize(namespaceString.rfind("::"));
+ --namespaceCount;
+ }
+ --bracesDepth;
+ } else if (bracesDepth == namespaceCount) {
+ buffer += line[i];
+ }
+ }
+ line.clear();
+
+ if (buffer.size() == 0)
+ continue;
+ scannerDebug() << m_currentFilename << ": " << buffer << std::endl;
+
+ if (m_currentFileType & PrivateHeader) {
+ parseVersionScriptContent(buffer, result);
+ }
+
+ ++linesProcessed;
+
+ bool skipSymbols =
+ (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader);
+
+ // Parse pragmas
+ if (std::regex_match(buffer, MacroRegex)) {
+ if (std::regex_match(buffer, SkipHeaderCheckRegex)) {
+ skipChecks = AllChecks;
+ faults = NoChecks;
+ } else if (std::regex_match(buffer, StopProcessingRegex)) {
+ if (skipChecks == AllChecks)
+ m_headerCheckExceptions.push_back(m_currentFileString);
+ return true;
+ } else if (std::regex_match(buffer, SuspendProcessingRegex)) {
+ isSuspended = true;
+ } else if (std::regex_match(buffer, ResumeProcessingRegex)) {
+ isSuspended = false;
+ } else if (std::regex_match(buffer, match, ExplixitClassPragmaRegex)) {
+ if (!skipSymbols) {
+ updateSymbolDescriptor(match[1].str(), m_currentFilename,
+ SymbolDescriptor::Pragma);
+ } else {
+ // TODO: warn about skipping symbols that are defined explicitly
+ }
+ } else if (std::regex_match(buffer, NoMasterIncludePragmaRegex)) {
+ result.masterInclude = false;
+ } else if (std::regex_match(buffer, match, DeprecatesPragmaRegex)) {
+ m_deprecatedHeaders[match[1].str()] =
+ m_commandLineArgs->moduleName() + '/' + m_currentFilename;
+ } else if (std::regex_match(buffer, match, IncludeRegex) && !isSuspended) {
+ if (!(skipChecks & IncludeChecks)) {
+ std::string includedHeader = match[1].str();
+ if (!(skipChecks & PrivateHeaderChecks)
+ && isHeaderPrivate(std::filesystem::path(includedHeader)
+ .filename()
+ .generic_string())) {
+ faults |= PrivateHeaderChecks;
+ std::cerr << m_commandLineArgs->moduleName()
+ << ": ERROR: " << m_currentFilename
+ << " includes private header " << includedHeader << std::endl;
+ }
+ for (const auto &module : m_commandLineArgs->knownModules()) {
+ faults |= IncludeChecks;
+ std::string suggestedHeader = "Qt" + module + '/' + includedHeader;
+ if (std::filesystem::exists(m_commandLineArgs->includeDir() + "/../"
+ + suggestedHeader)) {
+ std::cerr << m_commandLineArgs->moduleName()
+ << ": WARNING: " << m_currentFilename << " includes "
+ << includedHeader << " when it should include "
+ << suggestedHeader << std::endl;
+ }
+ }
+ }
+ }
+ continue;
+ }
+
+ // Logic below this line is affected by the 'qt_sync_suspend_processing' and
+ // 'qt_sync_resume_processing' pragmas.
+ if (isSuspended)
+ continue;
+
+ // Look for the symbols in header file.
+ if (!skipSymbols) {
+ std::string symbol;
+ if (checkLineForSymbols(buffer, symbol)) {
+ if (namespaceCount == 0
+ || std::regex_match(namespaceString,
+ m_commandLineArgs->publicNamespaceRegex())) {
+ updateSymbolDescriptor(symbol, m_currentFilename,
+ SymbolDescriptor::Declaration);
+ }
+ continue;
+ } else if (std::regex_match(buffer, match, DeclareIteratorRegex)) {
+ std::string iteratorSymbol = match[1].str() + "Iterator";
+ updateSymbolDescriptor(std::string("Q") + iteratorSymbol, m_currentFilename,
+ SymbolDescriptor::Declaration);
+ updateSymbolDescriptor(std::string("QMutable") + iteratorSymbol,
+ m_currentFilename, SymbolDescriptor::Declaration);
+ continue;
+ } else if (std::regex_match(buffer, match, RequireConfigRegex)) {
+ result.requireConfig = match[1].str();
+ continue;
+ }
+ }
+
+ // Check for both QT_BEGIN_NAMESPACE and QT_END_NAMESPACE macros are present in the
+ // header file.
+ if (!(skipChecks & NamespaceChecks)) {
+ if (std::regex_match(buffer, match, BeginNamespaceRegex)) {
+ qtBeginNamespace = match[1].str();
+ hasQtBeginNamespace = true;
+ } else if (std::regex_match(buffer, match, EndNamespaceRegex)) {
+ qtEndNamespace = match[1].str();
+ }
+ }
+ }
+
+ // Error out if namespace checks are failed.
+ if (!(skipChecks & NamespaceChecks)) {
+ if (hasQtBeginNamespace) {
+ if (qtBeginNamespace != qtEndNamespace) {
+ faults |= NamespaceChecks;
+ std::cerr << m_commandLineArgs->moduleName()
+ << ":WARNING: " << m_currentFilename
+ << " 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 << m_commandLineArgs->moduleName() << ": WARNING: " << m_currentFilename
+ << " does not include QT_BEGIN_NAMESPACE" << std::endl;
+ }
+ }
+
+ if (!(skipChecks & WeMeantItChecks) && !hasWeMeantIt) {
+ faults |= WeMeantItChecks;
+ std::cerr << m_commandLineArgs->moduleName() << ": WARNING: " << m_currentFilename
+ << " does not have the \"We mean it.\" warning\n"
+ << std::endl;
+ }
+
+ scannerDebug() << "linesTotal: " << m_currentFileLineNumber
+ << " linesProcessed: " << linesProcessed << std::endl;
+
+ if (skipChecks == AllChecks)
+ m_headerCheckExceptions.push_back(m_currentFileString);
+
+ // Exit with an error if any of critical checks are present.
+ return !(faults & CriticalChecks);
+ }
+
+ // The function checks if line contains the symbol that needs to have a CaMeL-style alias.
+ [[nodiscard]] bool checkLineForSymbols(const std::string &line, std::string &symbol)
+ {
+ scannerDebug() << "checkLineForSymbols: " << line << std::endl;
+
+ // This regex checks if line contains class or structure declaration like:
+ // - <class|stuct> StructName
+ // - template <> class ClassName
+ // - class ClassName : [public|protected|private] BaseClassName
+ // - class ClassName [final|Q_DECL_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)? "
+ "*.*)? *$");
+
+ // This regex checks if line contains function pointer typedef declaration like:
+ // - typedef void (* QFunctionPointerType)(int, char);
+ static const std::regex FunctionPointerRegex(
+ "^ *typedef *.*\\(\\*(Q[^\\)]+)\\)\\(.*\\); *");
+
+ // This regex checks if line contains class or structure typedef declaration like:
+ // - 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();
+ } else if (std::regex_match(line, match, TypedefRegex)) {
+ 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;
+ }
+ return !symbol.empty();
+ }
+
+ [[nodiscard]] bool isHeaderQpa(const std::string &headerFileName)
+ {
+ return std::regex_match(headerFileName, m_commandLineArgs->qpaHeadersRegex());
+ }
+
+ [[nodiscard]] bool isHeaderPrivate(const std::string &headerFile)
+ {
+ return std::regex_match(headerFile, m_commandLineArgs->privateHeadersRegex());
+ }
+
+ [[nodiscard]] bool isHeaderPCH(const std::string &headerFilename)
+ {
+ static const std::string pchSuffix("_pch.h");
+ return headerFilename.find(pchSuffix, headerFilename.size() - pchSuffix.size())
+ != std::string::npos;
+ }
+
+ [[nodiscard]] bool isHeader(const std::filesystem::path &path)
+ {
+ return path.extension().string() == ".h";
+ }
+
+ [[nodiscard]] bool isDocFileHeuristic(const std::string &headerFilePath)
+ {
+ return headerFilePath.find("/doc/") != std::string::npos;
+ }
+
+ [[nodiscard]] bool isHeaderGenerated(const std::string &header)
+ {
+ return m_commandLineArgs->generatedHeaders().find(header)
+ != m_commandLineArgs->generatedHeaders().end();
+ }
+
+ [[nodiscard]] bool generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath,
+ const std::string &aliasedFilePath);
+
+ [[nodiscard]] bool generateAliasedHeaderFileIfTimestampChanged(
+ const std::string &outputFilePath, const std::string &aliasedFilePath,
+ const FileStamp &originalStamp = FileStamp::clock::now());
+
+ bool writeIfDifferent(const std::string &outputFile, const std::string &buffer);
+
+ [[nodiscard]] bool generateMasterHeader()
+ {
+ if (m_masterHeaderContents.empty())
+ return true;
+
+ std::string outputFile =
+ m_commandLineArgs->includeDir() + '/' + m_commandLineArgs->moduleName();
+
+ std::string moduleUpper = utils::asciiToUpper(m_commandLineArgs->moduleName());
+ std::stringstream buffer;
+ buffer << "#ifndef QT_" << moduleUpper << "_MODULE_H\n"
+ << "#define QT_" << moduleUpper << "_MODULE_H\n"
+ << "#include <" << m_commandLineArgs->moduleName() << "/"
+ << m_commandLineArgs->moduleName() << "Depends>\n";
+ for (const auto &headerContents : m_masterHeaderContents) {
+ if (!headerContents.second.empty()) {
+ buffer << "#if QT_CONFIG(" << headerContents.second << ")\n"
+ << "#include \"" << headerContents.first << "\"\n"
+ << "#endif\n";
+ } else {
+ buffer << "#include \"" << headerContents.first << "\"\n";
+ }
+ }
+ buffer << "#endif\n";
+
+ m_producedHeaders.insert(m_commandLineArgs->moduleName());
+ return writeIfDifferent(outputFile, buffer.str());
+ }
+
+ [[nodiscard]] bool generateVersionHeader(const std::string &outputFile)
+ {
+ std::string moduleNameUpper = utils::asciiToUpper( m_commandLineArgs->moduleName());
+
+ std::stringstream buffer;
+ buffer << "/* This file was generated by syncqt. */\n"
+ << "#ifndef QT_" << moduleNameUpper << "_VERSION_H\n"
+ << "#define QT_" << moduleNameUpper << "_VERSION_H\n\n"
+ << "#define " << moduleNameUpper << "_VERSION_STR \"" << QT_VERSION_STR << "\"\n\n"
+ << "#define " << moduleNameUpper << "_VERSION "
+ << "0x0" << QT_VERSION_MAJOR << "0" << QT_VERSION_MINOR << "0" << QT_VERSION_PATCH
+ << "\n\n"
+ << "#endif // QT_" << moduleNameUpper << "_VERSION_H\n";
+
+ return writeIfDifferent(outputFile, buffer.str());
+ }
+
+ [[nodiscard]] bool generateDeprecatedHeaders()
+ {
+ static std::regex cIdentifierSymbolsRegex("[^a-zA-Z0-9_]");
+ static std::string guard_base = "DEPRECATED_HEADER_" + m_commandLineArgs->moduleName();
+ for (auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) {
+ std::string &replacement = it->second;
+ std::string qualifiedHeaderName =
+ std::regex_replace(it->first, cIdentifierSymbolsRegex, "_");
+ std::string guard = guard_base + "_" + qualifiedHeaderName;
+ std::string warningText = "Header <" + m_commandLineArgs->moduleName() + "/" + it->first
+ + "> is deprecated. Please include <" + replacement + "> instead.";
+ std::stringstream buffer;
+ buffer << "#ifndef " << guard << "\n"
+ << "#define " << guard << "\n"
+ << "#if defined(__GNUC__)\n"
+ << "# warning " << warningText << "\n"
+ << "#elif defined(_MSC_VER)\n"
+ << "# 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());
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool generateHeaderCheckExceptions()
+ {
+ std::stringstream buffer;
+ for (const auto &header : m_headerCheckExceptions)
+ buffer << header << ";";
+ return writeIfDifferent(m_commandLineArgs->binaryDir() + '/'
+ + m_commandLineArgs->moduleName()
+ + "_header_check_exceptions",
+ buffer.str());
+ }
+
+ [[nodiscard]] bool generateLinkerVersionScript()
+ {
+ std::stringstream buffer;
+ for (const auto &content : m_versionScriptContents)
+ buffer << content;
+ return writeIfDifferent(m_commandLineArgs->versionScriptFile(), buffer.str());
+ }
+
+ bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst);
+ void updateSymbolDescriptor(const std::string &symbol, const std::string &file,
+ SymbolDescriptor::SourceType type);
+};
+
+// The function updates information about the symbol:
+// - The path and modification time of the file where the symbol was found.
+// - The source of finding
+// Also displays a short info about a symbol in show only mode.
+void SyncScanner::updateSymbolDescriptor(const std::string &symbol, const std::string &file,
+ SymbolDescriptor::SourceType type)
+{
+ if (m_commandLineArgs->showOnly())
+ std::cout << " SYMBOL: " << symbol << std::endl;
+ m_symbols[symbol].update(file, type);
+}
+
+[[nodiscard]] std::filesystem::path
+SyncScanner::makeHeaderAbsolute(const std::string &filename) const
+{
+ if (std::filesystem::path(filename).is_relative())
+ return utils::normilizedPath(m_commandLineArgs->sourceDir() + '/' + filename);
+
+ return utils::normilizedPath(filename);
+}
+
+bool SyncScanner::updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst)
+{
+ if (m_commandLineArgs->showOnly())
+ return true;
+
+ if (src == dst) {
+ std::cout << "Source and destination paths are same when copying " << src.string()
+ << ". Skipping." << std::endl;
+ return true;
+ }
+
+ std::error_code ec;
+ std::filesystem::copy(src, dst, std::filesystem::copy_options::update_existing, ec);
+ if (ec) {
+ ec.clear();
+ std::filesystem::remove(dst, ec);
+ if (ec) {
+ // On some file systems(e.g. vboxfs) the std::filesystem::copy doesn't support
+ // std::filesystem::copy_options::overwrite_existing remove file first and then copy.
+ std::cerr << "Unable to remove file: " << src << " to " << dst << " error: ("
+ << ec.value() << ")" << ec.message() << std::endl;
+ return false;
+ }
+
+ std::filesystem::copy(src, dst, std::filesystem::copy_options::overwrite_existing, ec);
+ if (ec) {
+ std::cerr << "Unable to copy file: " << src << " to " << dst << " error: ("
+ << ec.value() << ")" << ec.message() << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+// The function generates Qt CaMeL-case files.
+// CaMeL-case files can have timestamp that is the same as or newer than timestamp of file that
+// supposed to included there. It's not an issue when we generate regular aliases because the
+// content of aliases is always the same, but we only want to "touch" them when content of original
+// is changed.
+bool SyncScanner::generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath,
+ const std::string &aliasedFilePath)
+{
+ if (m_commandLineArgs->showOnly())
+ return true;
+
+ std::string buffer = "#include \"";
+ buffer += aliasedFilePath;
+ buffer += "\"\n";
+
+ return writeIfDifferent(outputFilePath, buffer);
+}
+
+// The function generates aliases for files in source tree. Since the content of these aliases is
+// always same, it's ok to check only timestamp and touch files in case if stamp of original is
+// newer than the timestamp of an alias.
+bool SyncScanner::generateAliasedHeaderFileIfTimestampChanged(const std::string &outputFilePath,
+ const std::string &aliasedFilePath,
+ const FileStamp &originalStamp)
+{
+ if (m_commandLineArgs->showOnly())
+ return true;
+
+ if (std::filesystem::exists({ outputFilePath })
+ && std::filesystem::last_write_time({ outputFilePath }) >= originalStamp) {
+ return true;
+ }
+ scannerDebug() << "Rewrite " << outputFilePath << std::endl;
+
+ std::ofstream ofs;
+ ofs.open(outputFilePath, std::ofstream::out | std::ofstream::trunc);
+ if (!ofs.is_open()) {
+ std::cerr << "Unable to write header file alias: " << outputFilePath << std::endl;
+ return false;
+ }
+ ofs << "#include \"" << aliasedFilePath << "\"\n";
+ ofs.close();
+ return true;
+}
+
+bool SyncScanner::writeIfDifferent(const std::string &outputFile, const std::string &buffer)
+{
+ if (m_commandLineArgs->showOnly())
+ return true;
+
+ static const std::streamsize bufferSize = 1025;
+ bool differs = false;
+ std::filesystem::path outputFilePath(outputFile);
+
+ std::string outputDirectory = outputFilePath.parent_path().string();
+ if (!std::filesystem::exists(outputDirectory))
+ std::filesystem::create_directories(outputDirectory);
+
+ if (std::filesystem::exists(outputFilePath)
+ && buffer.size() == std::filesystem::file_size(outputFilePath)) {
+ char rdBuffer[bufferSize];
+ memset(rdBuffer, 0, bufferSize);
+
+ std::ifstream ifs(outputFile, std::fstream::in);
+ std::streamsize currentPos = 0;
+
+ std::size_t bytesRead = 0;
+ do {
+ ifs.read(rdBuffer, bufferSize - 1); // Read by 1K
+ bytesRead = ifs.gcount();
+ if (buffer.compare(currentPos, bytesRead, rdBuffer) != 0) {
+ differs = true;
+ break;
+ }
+ currentPos += bytesRead;
+ memset(rdBuffer, 0, bufferSize);
+ } while (bytesRead > 0);
+
+ ifs.close();
+ } else {
+ differs = true;
+ }
+
+ scannerDebug() << "Update: " << outputFile << " " << differs << std::endl;
+ if (differs) {
+ std::ofstream ofs;
+ ofs.open(outputFilePath, std::fstream::out | std::ofstream::trunc);
+ if (!ofs.is_open()) {
+ std::cerr << "Unable to write header content to " << outputFilePath << std::endl;
+ return false;
+ }
+ ofs << buffer;
+
+ ofs.close();
+ }
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ CommandLineOptions options(argc, argv);
+ if (!options.isValid())
+ return InvalidArguments;
+
+ if (options.printHelpOnly()) {
+ options.printHelp();
+ return NoError;
+ }
+
+ SyncScanner scanner = SyncScanner(&options);
+ return scanner.sync();
+}