From bec7aa4aac1b7741fd5bcb9a5ee41709e0c0ffe4 Mon Sep 17 00:00:00 2001 From: malte_langkabel Date: Mon, 24 Jul 2017 09:20:08 +0200 Subject: [PATCH] logic: add include path validation to project settings --- src/lib/utility/file/FileManager.cpp | 17 ++ src/lib/utility/file/FileManager.h | 1 + .../interprocess/SharedUUIDManager.cpp | 126 -------------- src/lib/utility/utility.h | 3 +- src/lib_cxx/CMakeLists.txt | 4 + src/lib_cxx/project/IncludeDirective.cpp | 34 ++++ src/lib_cxx/project/IncludeDirective.h | 27 +++ src/lib_cxx/project/IncludeValidation.cpp | 160 ++++++++++++++++++ src/lib_cxx/project/IncludeValidation.h | 24 +++ .../QtProjectWizzardContentPaths.cpp | 141 +++++++++++++-- .../QtProjectWizzardContentPaths.h | 12 +- 11 files changed, 411 insertions(+), 138 deletions(-) delete mode 100644 src/lib/utility/interprocess/SharedUUIDManager.cpp create mode 100644 src/lib_cxx/project/IncludeDirective.cpp create mode 100644 src/lib_cxx/project/IncludeDirective.h create mode 100644 src/lib_cxx/project/IncludeValidation.cpp create mode 100644 src/lib_cxx/project/IncludeValidation.h diff --git a/src/lib/utility/file/FileManager.cpp b/src/lib/utility/file/FileManager.cpp index 393ed850..b55c3591 100644 --- a/src/lib/utility/file/FileManager.cpp +++ b/src/lib/utility/file/FileManager.cpp @@ -56,6 +56,23 @@ std::set FileManager::getAllSourceFilePaths() const return m_allSourceFilePaths; } +std::set FileManager::getAllSourceFilePathsRelative(const FilePath& baseDirectory) const +{ + std::set absolutePaths; + for (const FilePath& path: getAllSourceFilePaths()) + { + if (baseDirectory.exists()) + { + absolutePaths.insert(path.relativeTo(baseDirectory)); + } + else + { + absolutePaths.insert(path); + } + } + return absolutePaths; +} + std::vector FileManager::makeCanonical(const std::vector& filePaths) { std::vector ret; diff --git a/src/lib/utility/file/FileManager.h b/src/lib/utility/file/FileManager.h index e47d5107..b027eda7 100644 --- a/src/lib/utility/file/FileManager.h +++ b/src/lib/utility/file/FileManager.h @@ -28,6 +28,7 @@ public: // returns a list of paths to all files that reside in the non-excluded source paths std::set getAllSourceFilePaths() const; + std::set getAllSourceFilePathsRelative(const FilePath& baseDirectory) const; private: std::vector makeCanonical(const std::vector& filePaths); diff --git a/src/lib/utility/interprocess/SharedUUIDManager.cpp b/src/lib/utility/interprocess/SharedUUIDManager.cpp deleted file mode 100644 index 5302b2b4..00000000 --- a/src/lib/utility/interprocess/SharedUUIDManager.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "SharedUUIDManager.h" - -#include - -#include "utility/file/FileSystem.h" -#include "utility/ConfigManager.h" -#include "utility/text/TextAccess.h" - -#include "utility/UserPaths.h" - -const std::string SharedUUIDManager::m_fileName("sharedUUIDs.xml"); -const std::string SharedUUIDManager::m_instanceUUIDsKey("instances"); -std::shared_ptr SharedUUIDManager::m_instance(NULL); - -std::shared_ptr SharedUUIDManager::getInstance() -{ - if (m_instance == NULL) - { - // m_instance = std::make_shared(); - SharedUUIDManager* sharedUUIDManager = new SharedUUIDManager(); - m_instance = std::shared_ptr(sharedUUIDManager); - m_instance->setFilePath(UserPaths::getUserDataPath().str()); - m_instance->saveInstanceUUID(); - } - - return m_instance; -} - -SharedUUIDManager::~SharedUUIDManager() -{ -} - -void SharedUUIDManager::setFilePath(const std::string& filePath) -{ - m_filePath = filePath; - FileSystem::createDirectory(FilePath(m_filePath)); - - refreshUUIDs(); -} - -std::string SharedUUIDManager::getInstanceUUID() const -{ - return UUIDUtility::UUIDtoString(m_instanceUUID); -} - -std::string SharedUUIDManager::getNewUUID() -{ - std::string uuidString = UUIDUtility::getUUIDString(); - - std::vector uuids; - m_uuids->getValues(UUIDUtility::UUIDtoString(m_instanceUUID), uuids); - - uuids.push_back(uuidString); - - m_uuids->setValues(UUIDUtility::UUIDtoString(m_instanceUUID), uuids); - - m_uuids->save(m_filePath + m_fileName); - - return uuidString; -} - -std::vector SharedUUIDManager::getUUIDsForInstance(const std::string& instanceUUID) -{ - std::vector uuids; - m_uuids->getValues(instanceUUID, uuids); - - return uuids; -} - -void SharedUUIDManager::removeUUIDsForInstance(const std::string& instanceUUID) -{ - m_uuids->removeValues(instanceUUID); - - m_uuids->save(m_filePath + m_fileName); -} - -std::vector SharedUUIDManager::getStoredInstanceUUIDs() const -{ - std::vector instanceUUIDs; - m_uuids->getValues(m_instanceUUIDsKey, instanceUUIDs); - - return instanceUUIDs; -} - -void SharedUUIDManager::removeInstanceUUID(const std::string& instanceUUID) -{ - std::vector instanceUUIDs; - m_uuids->getValues(m_instanceUUIDsKey, instanceUUIDs); - - // instanceUUIDs - for (std::vector::iterator it = instanceUUIDs.begin(); it != instanceUUIDs.end(); it++) - { - if (*it == instanceUUID) - { - instanceUUIDs.erase(it); - break; - } - } - - m_uuids->setValues(m_instanceUUIDsKey, instanceUUIDs); - m_uuids->save(m_filePath + m_fileName); -} - -SharedUUIDManager::SharedUUIDManager() - : m_filePath("user/") - , m_instanceUUID(UUIDUtility::getUUID()) - , m_uuids(NULL) -{ -} - -void SharedUUIDManager::saveInstanceUUID() -{ - std::vector instanceUUIDs; - m_uuids->getValues(m_instanceUUIDsKey, instanceUUIDs); - - instanceUUIDs.push_back(getInstanceUUID()); - m_uuids->setValues(m_instanceUUIDsKey, instanceUUIDs); -} - -void SharedUUIDManager::refreshUUIDs() -{ - if (FilePath(m_filePath + m_fileName).exists()) - { - m_uuids = ConfigManager::createAndLoad(TextAccess::createFromFile(FilePath(m_filePath + m_fileName))); - } -} diff --git a/src/lib/utility/utility.h b/src/lib/utility/utility.h index 0644f386..27dd3275 100644 --- a/src/lib/utility/utility.h +++ b/src/lib/utility/utility.h @@ -166,7 +166,8 @@ std::vector utility::unique(const std::vector& a) } } - std::vector r(i, T()); + std::vector r; + r.reserve(i); for (const std::pair& p : unique) { r[p.second] = p.first; diff --git a/src/lib_cxx/CMakeLists.txt b/src/lib_cxx/CMakeLists.txt index 826e433f..d583e4e1 100644 --- a/src/lib_cxx/CMakeLists.txt +++ b/src/lib_cxx/CMakeLists.txt @@ -76,6 +76,10 @@ add_files( data/storage/StorageTransformationAnonymousTypedef.cpp data/storage/StorageTransformationAnonymousTypedef.h + project/IncludeDirective.cpp + project/IncludeDirective.h + project/IncludeValidation.cpp + project/IncludeValidation.h project/SourceGroupCxx.cpp project/SourceGroupCxx.h project/SourceGroupFactoryModuleCpp.cpp diff --git a/src/lib_cxx/project/IncludeDirective.cpp b/src/lib_cxx/project/IncludeDirective.cpp new file mode 100644 index 00000000..c136a3f8 --- /dev/null +++ b/src/lib_cxx/project/IncludeDirective.cpp @@ -0,0 +1,34 @@ +#include "project/IncludeDirective.h" + +IncludeDirective::IncludeDirective( + const FilePath& includedFilePath, + const FilePath& includingFilePath, + unsigned int lineNumber, + bool usesBrackets +) + : m_includedFilePath(includedFilePath) + , m_includingFilePath(includingFilePath) + , m_lineNumber(lineNumber) + , m_usesBrackets(usesBrackets) +{ +} + +FilePath IncludeDirective::getIncludedFile() const +{ + return m_includedFilePath; +} + +FilePath IncludeDirective::getIncludingFile() const +{ + return m_includingFilePath; +} + +std::string IncludeDirective::getDirective() const +{ + return std::string("#include ") + (m_usesBrackets ? "<" : "\"") + m_includedFilePath.str() + (m_usesBrackets ? ">" : "\""); +} + +unsigned int IncludeDirective::getLineNumber() const +{ + return m_lineNumber; +} diff --git a/src/lib_cxx/project/IncludeDirective.h b/src/lib_cxx/project/IncludeDirective.h new file mode 100644 index 00000000..9f949bcc --- /dev/null +++ b/src/lib_cxx/project/IncludeDirective.h @@ -0,0 +1,27 @@ +#ifndef INCLUDE_DIRECTIVE_H +#define INCLUDE_DIRECTIVE_H + +#include "utility/file/FilePath.h" + +class IncludeDirective +{ +public: + IncludeDirective( + const FilePath& includedFilePath, + const FilePath& includingFilePath, + unsigned int lineNumber, + bool usesBrackets); + + FilePath getIncludedFile() const; + FilePath getIncludingFile() const; + std::string getDirective() const; + unsigned int getLineNumber() const; + +private: + const FilePath m_includedFilePath; + const FilePath m_includingFilePath; + const unsigned int m_lineNumber; + bool m_usesBrackets; +}; + +#endif // INCLUDE_DIRECTIVE_H diff --git a/src/lib_cxx/project/IncludeValidation.cpp b/src/lib_cxx/project/IncludeValidation.cpp new file mode 100644 index 00000000..575f0839 --- /dev/null +++ b/src/lib_cxx/project/IncludeValidation.cpp @@ -0,0 +1,160 @@ +#include "project/IncludeValidation.h" + +#include "project/IncludeDirective.h" +#include "utility/file/FilePath.h" +#include "utility/text/TextAccess.h" +#include "utility/utility.h" +#include "utility/utilityString.h" + +std::vector IncludeValidation::getUnresolvedIncludeDirectives( + const std::vector& sourceFilePaths, + const std::vector& indexedPaths, + const std::vector& headerSearchDirectories, + size_t quantileCount, std::function progress +) +{ + struct IncludeDirectiveComparator + { + bool operator()(const IncludeDirective& a, const IncludeDirective& b) + { + return a.getIncludedFile() < b.getIncludedFile(); + } + }; + + std::set processedFilePaths; + std::set unresolvedIncludeDirectives; + + quantileCount = std::min(quantileCount, sourceFilePaths.size()); + + std::vector> quantiles; + for (size_t i = 0; i < quantileCount; i++) + { + quantiles.push_back(std::vector()); + } + for (size_t i = 0; i < sourceFilePaths.size(); i++) + { + quantiles[i%quantileCount].push_back(sourceFilePaths[i]); + } + + for (size_t i = 0; i < quantiles.size(); i++) + { + const std::vector& quantile = quantiles[i]; + + progress(float(i) / quantiles.size()); + + std::set unprocessedFilePaths(quantile.begin(), quantile.end()); + + while (!unprocessedFilePaths.empty()) + { + std::transform(unprocessedFilePaths.begin(), unprocessedFilePaths.end(), std::inserter(processedFilePaths, processedFilePaths.begin()), [](const FilePath& p){ return p.str(); }); + std::set tempUnprocessedFilePaths; + + for (const FilePath& filePath: unprocessedFilePaths) + { + for (const IncludeDirective& includeDirective: getIncludeDirectives(filePath)) + { + const FilePath resolvedIncludePath = resolveIncludeDirective(includeDirective, headerSearchDirectories); + if (resolvedIncludePath.empty()) + { + unresolvedIncludeDirectives.insert(includeDirective); + } + else if (processedFilePaths.find(resolvedIncludePath.str()) == processedFilePaths.end()) + { + for (const FilePath& indexedPath: indexedPaths) + { + if (indexedPath.contains(resolvedIncludePath)) + { + tempUnprocessedFilePaths.insert(resolvedIncludePath); + break; + } + } + } + } + } + + unprocessedFilePaths = tempUnprocessedFilePaths; + } + } + + std::vector ret; + + for (const IncludeDirective& directive: unresolvedIncludeDirectives) + { + ret.push_back(directive); + } + + progress(1.0f); + + return ret; +} + +std::vector IncludeValidation::getIncludeDirectives(const FilePath& filePath) +{ + std::vector includeDirectives; + + if (filePath.exists()) + { + std::shared_ptr textAccess = TextAccess::createFromFile(filePath); + const std::vector lines = textAccess->getAllLines(); + for (size_t i = 0; i < lines.size(); i++) + { + const std::string lineTrimmedToHash = utility::trim(lines[i]); + if (utility::isPrefix("#", lineTrimmedToHash)) + { + const std::string lineTrimmedToInclude = utility::trim(lineTrimmedToHash.substr(1)); + if (utility::isPrefix("include", lineTrimmedToInclude)) + { + std::string includeString = utility::substrBetween(lineTrimmedToInclude, "<", ">"); + bool usesBrackets = true; + if (includeString.empty()) + { + includeString = utility::substrBetween(lineTrimmedToInclude, "\"", "\""); + usesBrackets = false; + } + + if (!includeString.empty()) + { + // lines are 1 based + includeDirectives.push_back(IncludeDirective(FilePath(includeString), filePath, i + 1, usesBrackets)); + } + } + } + } + } + + return includeDirectives; +} + +FilePath IncludeValidation::resolveIncludeDirective(const IncludeDirective& includeDirective, const std::vector& headerSearchDirectories) +{ + { + // check for an absolute include path + if (includeDirective.getIncludedFile().exists()) + { + return includeDirective.getIncludedFile(); + } + } + + { + // check for an include path relative to the including path + FilePath resolvedIncludePath = includeDirective.getIncludingFile().parentDirectory().concat(includeDirective.getIncludedFile()); + if (resolvedIncludePath.exists()) + { + return resolvedIncludePath; + } + } + + { + // check for an include path relative to the header search directories + for (const FilePath& headerSearchDirectory: headerSearchDirectories) + { + FilePath resolvedIncludePath = headerSearchDirectory.concat(includeDirective.getIncludedFile()); + if (resolvedIncludePath.exists()) + { + return resolvedIncludePath; + } + } + } + + return FilePath(); +} diff --git a/src/lib_cxx/project/IncludeValidation.h b/src/lib_cxx/project/IncludeValidation.h new file mode 100644 index 00000000..611c0824 --- /dev/null +++ b/src/lib_cxx/project/IncludeValidation.h @@ -0,0 +1,24 @@ +#ifndef INCLUDE_VALIDATION_H +#define INCLUDE_VALIDATION_H + +#include +#include +#include + +class FilePath; +class IncludeDirective; + +class IncludeValidation +{ +public: + static std::vector getUnresolvedIncludeDirectives( + const std::vector& sourceFilePaths, + const std::vector& indexedPaths, + const std::vector& headerSearchDirectories, + size_t quantileCount, std::function progress); +private: + static std::vector getIncludeDirectives(const FilePath& filePath); + static FilePath resolveIncludeDirective(const IncludeDirective& includeDirective, const std::vector& headerSearchDirectories); +}; + +#endif // INCLUDE_VALIDATION_H diff --git a/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.cpp b/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.cpp index 18999f7d..4f7e35b0 100644 --- a/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.cpp +++ b/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.cpp @@ -6,15 +6,21 @@ #include #include +#include "component/view/DialogView.h" +#include "project/IncludeDirective.h" +#include "project/IncludeValidation.h" #include "qt/element/QtDirectoryListBox.h" +#include "qt/view/QtDialogView.h" #include "qt/window/QtSelectPathsDialog.h" #include "settings/ApplicationSettings.h" #include "settings/SourceGroupSettingsCxx.h" #include "settings/SourceGroupSettingsJava.h" #include "utility/CompilationDatabase.h" #include "utility/file/FileManager.h" +#include "utility/ScopedFunctor.h" #include "utility/utility.h" #include "utility/utilityPathDetection.h" +#include "Application.h" QtProjectWizzardContentPaths::QtProjectWizzardContentPaths(std::shared_ptr settings, QtProjectWizzardWindow* window) : QtProjectWizzardContent(window) @@ -177,18 +183,11 @@ std::vector QtProjectWizzardContentPathsSource::getFileNames() cons m_settings->getSourceExtensions() ); - const FilePath projectPath = m_settings->getProjectFileLocation(); + const std::set filePaths = fileManager.getAllSourceFilePathsRelative(m_settings->getProjectFileLocation()); std::vector list; - for (FilePath path: fileManager.getAllSourceFilePaths()) - { - if (projectPath.exists()) - { - path = path.relativeTo(projectPath); - } - - list.push_back(path.str()); - } + list.resize(filePaths.size()); + std::transform(filePaths.begin(), filePaths.end(), list.begin(), [](const FilePath& p){ return p.str(); }); return list; } @@ -328,6 +327,7 @@ QtProjectWizzardContentPathsHeaderSearch::QtProjectWizzardContentPathsHeaderSear std::shared_ptr settings, QtProjectWizzardWindow* window, bool isCDB ) : QtProjectWizzardContentPaths(settings, window) + , m_showValidationResultFunctor(std::bind(&QtProjectWizzardContentPathsHeaderSearch::showValidationResult, this, std::placeholders::_1)) { setTitleString(isCDB ? "Additional Include Paths" : "Include Paths"); setHelpString( @@ -345,6 +345,18 @@ QtProjectWizzardContentPathsHeaderSearch::QtProjectWizzardContentPathsHeaderSear ); } +void QtProjectWizzardContentPathsHeaderSearch::populate(QGridLayout* layout, int& row) +{ + QtProjectWizzardContentPaths::populate(layout, row); + + QPushButton* button = new QPushButton("validate include directives"); + button->setObjectName("windowButton"); + connect(button, SIGNAL(clicked()), this, SLOT(validateButtonClicked())); + + layout->addWidget(button, row, QtProjectWizzardWindow::BACK_COL, Qt::AlignRight | Qt::AlignTop); + row++; +} + void QtProjectWizzardContentPathsHeaderSearch::load() { std::shared_ptr cxxSettings = std::dynamic_pointer_cast(m_settings); @@ -368,6 +380,115 @@ bool QtProjectWizzardContentPathsHeaderSearch::isScrollAble() const return true; } +void QtProjectWizzardContentPathsHeaderSearch::validateButtonClicked() +{ + // TODO: regard Force Includes here, too! + m_window->saveContent(); + + std::thread([&]() + { + std::vector unresolvedIncludes; + { + std::shared_ptr dialogView = Application::getInstance()->getDialogView(); + std::dynamic_pointer_cast(dialogView)->setParentWindow(m_window); + ScopedFunctor dialogParentResetter([&dialogView](){ + std::dynamic_pointer_cast(dialogView)->setParentWindow(nullptr); + }); + + std::vector sourceFilePaths; + std::vector indexedFilePaths; + std::vector headerSearchPaths; + + { + dialogView->showUnknownProgressDialog("Processing", "Gathering Source Files"); + ScopedFunctor dialogHider([&dialogView](){ + dialogView->hideUnknownProgressDialog(); + }); + + FileManager fileManager; + fileManager.update( + m_settings->getSourcePathsExpandedAndAbsolute(), + m_settings->getExcludePathsExpandedAndAbsolute(), + m_settings->getSourceExtensions() + ); + sourceFilePaths = utility::toVector(fileManager.getAllSourceFilePaths()); + + headerSearchPaths = ApplicationSettings::getInstance()->getHeaderSearchPathsExpanded(); + + if (std::shared_ptr cxxSettings = std::dynamic_pointer_cast(m_settings)) + { + indexedFilePaths = cxxSettings->getSourcePaths(); + utility::append(headerSearchPaths, cxxSettings->getHeaderSearchPathsExpandedAndAbsolute()); + } + } + { + ScopedFunctor dialogHider([&dialogView](){ + dialogView->hideProgressDialog(); + }); + + unresolvedIncludes = IncludeValidation::getUnresolvedIncludeDirectives( + sourceFilePaths, + indexedFilePaths, + headerSearchPaths, + log2(sourceFilePaths.size()), + [&](const float progress) + { + Application::getInstance()->getDialogView()->showProgressDialog( + "Processing", std::to_string(int(progress * sourceFilePaths.size())) + " Files", int(progress * 100.0f) + ); + } + ); + } + } + m_showValidationResultFunctor(unresolvedIncludes); + }).detach(); +} + +void QtProjectWizzardContentPathsHeaderSearch::showValidationResult(const std::vector& unresolvedIncludes) +{ + if (unresolvedIncludes.empty()) + { + QMessageBox msgBox; + msgBox.setText("

All include directives throughout the indexed files have been resolved.

"); + msgBox.exec(); + } + else + { + std::string detailedText = ""; + for (const IncludeDirective& unresolvedInclude: unresolvedIncludes) + { + detailedText += unresolvedInclude.getIncludingFile().str() + "[" + std::to_string(unresolvedInclude.getLineNumber()) + "]: " + unresolvedInclude.getDirective() + "\n"; + } + + QMessageBox msgBox; + msgBox.setText(QString::fromStdString( + "

The indexed files contain " + std::to_string(unresolvedIncludes.size()) + " include directive" + (unresolvedIncludes.size() == 1 ? "" : "s") + " that could " + "not be resolved correctly. Please check the details and add the respective header search paths.

" + "

Note: This is only a quick pass that does not regard block commenting or conditional preprocessor directives. This means that " + "some of the unresolved includes may actually not be required by the indexer.

")); + msgBox.setDetailedText(QString::fromStdString(detailedText)); + msgBox.exec(); + } + + + //if (!m_filesDialog) + //{ + // m_filesDialog = std::make_shared( + // getFileNamesTitle(), QString::number(fileNames.size()) + " " + getFileNamesDescription()); + // m_filesDialog->setup(); + + // m_filesDialog->setText(utility::join(fileNames, "\n")); + // m_filesDialog->setCloseVisible(false); + // m_filesDialog->setReadOnly(true); + + // connect(m_filesDialog.get(), SIGNAL(finished()), this, SLOT(closedFilesDialog())); + // connect(m_filesDialog.get(), SIGNAL(canceled()), this, SLOT(closedFilesDialog())); + //} + + //m_filesDialog->showWindow(); + //m_filesDialog->raise(); +} + QtProjectWizzardContentPathsHeaderSearchGlobal::QtProjectWizzardContentPathsHeaderSearchGlobal( QtProjectWizzardWindow* window ) diff --git a/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.h b/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.h index 02ad6e23..51158307 100644 --- a/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.h +++ b/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.h @@ -4,6 +4,7 @@ #include "qt/window/project_wizzard/QtProjectWizzardContent.h" #include "utility/path_detector/CombinedPathDetector.h" +class IncludeDirective; class QCheckBox; class QComboBox; class QPushButton; @@ -99,15 +100,24 @@ public: class QtProjectWizzardContentPathsHeaderSearch : public QtProjectWizzardContentPaths { + Q_OBJECT + public: QtProjectWizzardContentPathsHeaderSearch( std::shared_ptr settings, QtProjectWizzardWindow* window, bool isCDB = false); // QtProjectWizzardContent implementation + virtual void populate(QGridLayout* layout, int& row); virtual void load() override; virtual void save() override; - virtual bool isScrollAble() const override; + +private slots: + void validateButtonClicked(); + +private: + void showValidationResult(const std::vector& unresolvedIncludes); + QtThreadedFunctor> m_showValidationResultFunctor; }; class QtProjectWizzardContentPathsHeaderSearchGlobal