From dfaf01614d976e228d97daf979745bed89b7f91a Mon Sep 17 00:00:00 2001 From: Alexander Drozdov Date: Sun, 4 Dec 2016 03:36:12 +1000 Subject: [PATCH] CMake: make project file system tree scanner persistent Project source tree is a same for all build configurations so it is a good idea to keep it persistent between CMake runs, configurations switches and so on. It safes a lot of time for big projects. Move more operations to the scanner thread: - Nodes filtering: skip .user files on top level of the project, skip well-known extensions and octet-streams: In most cases these are not required to be shown in the project tree. - Nodes sorting Fix small memory leak: we have .user in the scanner result. After this node filtered out, but is not feed (old code at the BuildDirManager::generateProjectTree()). Now .user file skips during scan without memory allocation at all. Allow user manually rescan project tree by call Build -> Rescan project tree. It runs CMake and Tree Scanner together: in most cases only CMake run requires but time to time (VCS update) full rescan also required. Change-Id: I4a6e6c897202da557509291c79932dd7751860e5 Reviewed-by: Tobias Hunger --- .../cmakeprojectmanager/builddirmanager.cpp | 81 +------ .../cmakeprojectmanager/builddirmanager.h | 7 +- .../cmakebuildconfiguration.cpp | 4 +- .../cmakebuildconfiguration.h | 2 +- .../cmakeprojectmanager/cmakeproject.cpp | 72 ++++++- .../cmakeprojectmanager/cmakeproject.h | 8 + .../cmakeprojectconstants.h | 1 + .../cmakeprojectmanager.cpp | 24 ++- .../cmakeprojectmanager/cmakeprojectmanager.h | 2 + .../cmakeprojectmanager.pro | 6 +- .../cmakeprojectmanager.qbs | 4 +- .../cmakeprojectmanager/treescanner.cpp | 202 ++++++++++++++++++ src/plugins/cmakeprojectmanager/treescanner.h | 100 +++++++++ 13 files changed, 428 insertions(+), 85 deletions(-) create mode 100644 src/plugins/cmakeprojectmanager/treescanner.cpp create mode 100644 src/plugins/cmakeprojectmanager/treescanner.h diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.cpp b/src/plugins/cmakeprojectmanager/builddirmanager.cpp index 482eb50ab3b..3bc591cb2d0 100644 --- a/src/plugins/cmakeprojectmanager/builddirmanager.cpp +++ b/src/plugins/cmakeprojectmanager/builddirmanager.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include @@ -82,9 +81,6 @@ BuildDirManager::BuildDirManager(CMakeBuildConfiguration *bc) : m_reparseTimer.setSingleShot(true); connect(&m_reparseTimer, &QTimer::timeout, this, &BuildDirManager::parse); - - connect(&m_futureWatcher, &QFutureWatcher>::finished, - this, &BuildDirManager::emitDataAvailable); } BuildDirManager::~BuildDirManager() = default; @@ -105,7 +101,7 @@ const Utils::FileName BuildDirManager::workDirectory() const void BuildDirManager::emitDataAvailable() { - if (!isParsing() && m_futureWatcher.isFinished()) + if (!isParsing()) emit dataAvailable(); } @@ -145,16 +141,7 @@ void BuildDirManager::parseOnceReaderReady(bool force) TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); m_buildTargets.clear(); - - auto fi = new QFutureInterface>(); - m_scanFuture = fi->future(); - m_futureWatcher.setFuture(m_scanFuture); - m_cmakeCache.clear(); - - Core::ProgressManager::addTask(fi->future(), "Scan CMake project tree", "CMake.Scan.Tree"); - Utils::runAsync([this, fi]() { BuildDirManager::asyncScanForFiles(fi); }); - checkConfiguration(); m_reader->stop(); m_reader->parse(force); @@ -246,50 +233,6 @@ void BuildDirManager::becameDirty() m_reparseTimer.start(1000); } -void BuildDirManager::asyncScanForFiles(QFutureInterface> *fi) -{ - std::unique_ptr>> fip(fi); - fip->reportStarted(); - Utils::MimeDatabase mdb; - - QList nodes - = FileNode::scanForFiles(m_buildConfiguration->target()->project()->projectDirectory(), - [&mdb](const Utils::FileName &fn) -> FileNode * { - QTC_ASSERT(!fn.isEmpty(), return nullptr); - const Utils::MimeType mimeType = mdb.mimeTypeForFile(fn.toString()); - FileType type = FileType::Unknown; - if (mimeType.isValid()) { - const QString mt = mimeType.name(); - if (mt == CppTools::Constants::C_SOURCE_MIMETYPE - || mt == CppTools::Constants::CPP_SOURCE_MIMETYPE - || mt == CppTools::Constants::OBJECTIVE_C_SOURCE_MIMETYPE - || mt == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE - || mt == CppTools::Constants::QDOC_MIMETYPE - || mt == CppTools::Constants::MOC_MIMETYPE) - type = FileType::Source; - else if (mt == CppTools::Constants::C_HEADER_MIMETYPE - || mt == CppTools::Constants::CPP_HEADER_MIMETYPE) - type = FileType::Header; - else if (mt == ProjectExplorer::Constants::FORM_MIMETYPE) - type = FileType::Form; - else if (mt == ProjectExplorer::Constants::RESOURCE_MIMETYPE) - type = FileType::Resource; - else if (mt == ProjectExplorer::Constants::SCXML_MIMETYPE) - type = FileType::StateChart; - else if (mt == CMakeProjectManager::Constants::CMAKEPROJECTMIMETYPE - || mt == CMakeProjectManager::Constants::CMAKEMIMETYPE) - type = FileType::Project; - else if (mt == ProjectExplorer::Constants::QML_MIMETYPE) - type = FileType::QML; - } - return new FileNode(fn, type, false); - }, - fip.get()); - fip->setProgressValue(fip->progressMaximum()); - fip->reportResult(nodes); - fip->reportFinished(); -} - void BuildDirManager::forceReparse() { if (m_buildConfiguration->target()->activeBuildConfiguration() != m_buildConfiguration) @@ -308,7 +251,6 @@ void BuildDirManager::resetData() m_reader->resetData(); m_cmakeCache.clear(); - m_futureWatcher.setFuture(QFuture>()); m_reader.reset(); m_buildTargets.clear(); @@ -334,22 +276,19 @@ bool BuildDirManager::persistCMakeState() return true; } -void BuildDirManager::generateProjectTree(CMakeListsNode *root) +void BuildDirManager::generateProjectTree(CMakeListsNode *root, const QList &allFiles) { QTC_ASSERT(m_reader, return); - QTC_ASSERT(m_scanFuture.isFinished(), return); const Utils::FileName projectFile = m_buildConfiguration->target()->project()->projectFilePath(); - QList tmp = Utils::filtered(m_scanFuture.result(), - [projectFile](const FileNode *fn) -> bool { - return !fn->filePath().toString().startsWith(projectFile.toString() + ".user"); + + // input files only a reference, it persistent between calls + // make copy of them for concrete configuration + auto tmp = Utils::transform(allFiles, [](const FileNode* fn) { + return new FileNode(*fn); }); - Utils::sort(tmp, ProjectExplorer::Node::sortByPath); - m_scanFuture = QFuture>(); // flush stale results - - const QList allFiles = tmp; - m_reader->generateProjectTree(root, allFiles); + m_reader->generateProjectTree(root, tmp); QSet usedNodes; foreach (FileNode *fn, root->recursiveFileNodes()) usedNodes.insert(fn); @@ -358,7 +297,7 @@ void BuildDirManager::generateProjectTree(CMakeListsNode *root) if (root->fileNodes().isEmpty() && root->folderNodes().isEmpty() && root->projectNodes().isEmpty()) { - FileNode *cm = Utils::findOrDefault(allFiles, [&projectFile](const FileNode *fn) { + FileNode *cm = Utils::findOrDefault(tmp, [&projectFile](const FileNode *fn) { return fn->filePath() == projectFile; }); if (cm) { @@ -367,7 +306,7 @@ void BuildDirManager::generateProjectTree(CMakeListsNode *root) } } - QList leftOvers = Utils::filtered(allFiles, [&usedNodes](FileNode *fn) { + QList leftOvers = Utils::filtered(tmp, [&usedNodes](FileNode *fn) { return !usedNodes.contains(fn); }); qDeleteAll(leftOvers); diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.h b/src/plugins/cmakeprojectmanager/builddirmanager.h index 0f36a11ddb4..d253262648e 100644 --- a/src/plugins/cmakeprojectmanager/builddirmanager.h +++ b/src/plugins/cmakeprojectmanager/builddirmanager.h @@ -30,7 +30,6 @@ #include -#include #include #include #include @@ -72,7 +71,7 @@ public: bool updateCMakeStateBeforeBuild(); bool persistCMakeState(); - void generateProjectTree(CMakeListsNode *root); + void generateProjectTree(CMakeListsNode *root, const QList &allFiles); QSet updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder); QList buildTargets() const; @@ -103,8 +102,6 @@ private: void becameDirty(); - void asyncScanForFiles(QFutureInterface> *fi); - CMakeBuildConfiguration *m_buildConfiguration = nullptr; mutable std::unique_ptr m_tempDir = nullptr; mutable CMakeConfig m_cmakeCache; @@ -112,8 +109,6 @@ private: QTimer m_reparseTimer; std::unique_ptr m_reader; - QFutureWatcher> m_futureWatcher; - QFuture> m_scanFuture; mutable QList m_buildTargets; }; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index a13b81859e8..927c085ddf5 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -210,12 +210,12 @@ QList CMakeBuildConfiguration::buildTargets() const return m_buildDirManager->buildTargets(); } -void CMakeBuildConfiguration::generateProjectTree(CMakeListsNode *root) const +void CMakeBuildConfiguration::generateProjectTree(CMakeListsNode *root, const QList &allFiles) const { if (!m_buildDirManager || m_buildDirManager->isParsing()) return; - return m_buildDirManager->generateProjectTree(root); + m_buildDirManager->generateProjectTree(root, allFiles); } QSet CMakeBuildConfiguration::updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h index 3c8723e3c0f..393284db648 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h @@ -85,7 +85,7 @@ public: void clearCache(); QList buildTargets() const; - void generateProjectTree(CMakeListsNode *root) const; + void generateProjectTree(CMakeListsNode *root, const QList &allFiles) const; QSet updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder); static Utils::FileName diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 8dbdb0ed7c5..ba599d704ff 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -32,9 +32,11 @@ #include "cmakerunconfiguration.h" #include "cmakeprojectmanager.h" +#include #include #include #include +#include #include #include #include @@ -85,6 +87,43 @@ CMakeProject::CMakeProject(CMakeManager *manager, const FileName &fileName) rootProjectNode()->setDisplayName(fileName.parentDir().fileName()); connect(this, &CMakeProject::activeTargetChanged, this, &CMakeProject::handleActiveTargetChanged); + + connect(&m_treeScanner, &TreeScanner::finished, this, &CMakeProject::handleTreeScanningFinished); + + m_treeScanner.setFilter([this](const Utils::MimeType &mimeType, const Utils::FileName &fn) { + // Mime checks requires more resources, so keep it last in check list + auto isIgnored = + fn.toString().startsWith(projectFilePath().toString() + ".user") || + TreeScanner::isWellKnownBinary(mimeType, fn); + + // Cache mime check result for speed up + if (!isIgnored) { + auto it = m_mimeBinaryCache.find(mimeType.name()); + if (it != m_mimeBinaryCache.end()) { + isIgnored = *it; + } else { + isIgnored = TreeScanner::isMimeBinary(mimeType, fn); + m_mimeBinaryCache[mimeType.name()] = isIgnored; + } + } + + return isIgnored; + }); + + m_treeScanner.setTypeFactory([](const Utils::MimeType &mimeType, const Utils::FileName &fn) { + auto type = TreeScanner::genericFileType(mimeType, fn); + if (type == FileType::Unknown) { + if (mimeType.isValid()) { + const QString mt = mimeType.name(); + if (mt == CMakeProjectManager::Constants::CMAKEPROJECTMIMETYPE + || mt == CMakeProjectManager::Constants::CMAKEMIMETYPE) + type = FileType::Project; + } + } + return type; + }); + + scanProjectTree(); } CMakeProject::~CMakeProject() @@ -92,6 +131,7 @@ CMakeProject::~CMakeProject() setRootProjectNode(nullptr); m_codeModelFuture.cancel(); qDeleteAll(m_extraCompilers); + qDeleteAll(m_allFiles); } void CMakeProject::updateProjectData(CMakeBuildConfiguration *bc) @@ -101,9 +141,13 @@ void CMakeProject::updateProjectData(CMakeBuildConfiguration *bc) Target *t = activeTarget(); if (!t || t->activeBuildConfiguration() != bc) return; + + if (!m_treeScanner.isFinished() || bc->isParsing()) + return; + Kit *k = t->kit(); - bc->generateProjectTree(static_cast(rootProjectNode())); + bc->generateProjectTree(static_cast(rootProjectNode()), m_allFiles); updateApplicationAndDeploymentTargets(); updateTargetRunConfigurations(t); @@ -290,6 +334,16 @@ bool CMakeProject::setupTarget(Target *t) return true; } +void CMakeProject::scanProjectTree() +{ + if (!m_treeScanner.isFinished()) + return; + m_treeScanner.asyncScanForFiles(projectDirectory()); + Core::ProgressManager::addTask(m_treeScanner.future(), + tr("Scan \"%1\" project tree").arg(displayName()), + "CMake.Scan.Tree"); +} + void CMakeProject::handleActiveTargetChanged() { if (m_connectedTarget) { @@ -335,6 +389,22 @@ void CMakeProject::handleParsingStarted() emit parsingStarted(); } +void CMakeProject::handleTreeScanningFinished() +{ + qDeleteAll(m_allFiles); + m_allFiles = m_treeScanner.release(); + + auto t = activeTarget(); + if (!t) + return; + + auto bc = qobject_cast(t->activeBuildConfiguration()); + if (!bc) + return; + + updateProjectData(bc); +} + CMakeBuildTarget CMakeProject::buildTargetForTitle(const QString &title) { foreach (const CMakeBuildTarget &ct, buildTargets()) diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index 5fa7de4415c..295c3a81f78 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -26,6 +26,7 @@ #pragma once #include "cmake_global.h" +#include "treescanner.h" #include #include @@ -33,6 +34,7 @@ #include #include +#include QT_BEGIN_NAMESPACE class QFileSystemWatcher; @@ -95,6 +97,7 @@ public: bool supportsKit(ProjectExplorer::Kit *k, QString *errorMessage = 0) const final; void runCMake(); + void scanProjectTree(); // Context menu actions: void buildCMakeTarget(const QString &buildTarget); @@ -113,6 +116,7 @@ private: void handleActiveTargetChanged(); void handleActiveBuildConfigurationChanged(); void handleParsingStarted(); + void handleTreeScanningFinished(); void updateProjectData(Internal::CMakeBuildConfiguration *cmakeBc); void updateQmlJSCodeModel(); @@ -128,6 +132,10 @@ private: QFuture m_codeModelFuture; QList m_extraCompilers; + Internal::TreeScanner m_treeScanner; + QHash m_mimeBinaryCache; + QList m_allFiles; + friend class Internal::CMakeBuildConfiguration; friend class Internal::CMakeBuildSettingsWidget; }; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h b/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h index 6d0bb31f4da..c84e3f08dd8 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h +++ b/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h @@ -37,6 +37,7 @@ const char CMAKE_EDITOR_ID[] = "CMakeProject.CMakeEditor"; const char CMAKE_EDITOR_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("CMakeProjectManager::Internal::CMakeEditorFactory", "CMake Editor"); const char RUNCMAKE[] = "CMakeProject.RunCMake"; const char CLEARCMAKECACHE[] = "CMakeProject.ClearCache"; +const char RESCANPROJECT[] = "CMakeProject.RescanProject"; const char RUNCMAKECONTEXTMENU[] = "CMakeProject.RunCMakeContextMenu"; // Project diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp index 41f811736af..145bbb5d6a3 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp @@ -56,7 +56,8 @@ using namespace CMakeProjectManager::Internal; CMakeManager::CMakeManager() : m_runCMakeAction(new QAction(QIcon(), tr("Run CMake"), this)), m_clearCMakeCacheAction(new QAction(QIcon(), tr("Clear CMake Configuration"), this)), - m_runCMakeActionContextMenu(new QAction(QIcon(), tr("Run CMake"), this)) + m_runCMakeActionContextMenu(new QAction(QIcon(), tr("Run CMake"), this)), + m_rescanProjectAction(new QAction(QIcon(), tr("Rescan project"), this)) { Core::ActionContainer *mbuild = Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT); @@ -93,6 +94,14 @@ CMakeManager::CMakeManager() : runCMake(ProjectTree::currentProject()); }); + command = Core::ActionManager::registerAction(m_rescanProjectAction, + Constants::RESCANPROJECT, globalContext); + command->setAttribute(Core::Command::CA_Hide); + mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_DEPLOY); + connect(m_rescanProjectAction, &QAction::triggered, [this]() { + rescanProject(ProjectTree::currentProject()); + }); + connect(SessionManager::instance(), &SessionManager::startupProjectChanged, this, &CMakeManager::updateCmakeActions); connect(BuildManager::instance(), &BuildManager::buildStateChanged, @@ -107,6 +116,7 @@ void CMakeManager::updateCmakeActions() const bool visible = project && !BuildManager::isBuilding(project); m_runCMakeAction->setVisible(visible); m_clearCMakeCacheAction->setVisible(visible); + m_rescanProjectAction->setVisible(visible); } void CMakeManager::clearCMakeCache(Project *project) @@ -134,6 +144,18 @@ void CMakeManager::runCMake(Project *project) cmakeProject->runCMake(); } +void CMakeManager::rescanProject(Project *project) +{ + if (!project) + return; + CMakeProject *cmakeProject = qobject_cast(project); + if (!cmakeProject || !cmakeProject->activeTarget() || !cmakeProject->activeTarget()->activeBuildConfiguration()) + return; + + cmakeProject->scanProjectTree(); + cmakeProject->runCMake(); // by my experience: every rescan run requires cmake run too +} + Project *CMakeManager::openProject(const QString &fileName, QString *errorString) { Utils::FileName file = Utils::FileName::fromString(fileName); diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h index 795406494bf..0fa0b6f4586 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h @@ -61,10 +61,12 @@ private: void updateCmakeActions(); void clearCMakeCache(ProjectExplorer::Project *project); void runCMake(ProjectExplorer::Project *project); + void rescanProject(ProjectExplorer::Project *project); QAction *m_runCMakeAction; QAction *m_clearCMakeCacheAction; QAction *m_runCMakeActionContextMenu; + QAction *m_rescanProjectAction; }; } // namespace Internal diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro index 658564b177c..8043387d9dd 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro @@ -32,7 +32,8 @@ HEADERS = builddirmanager.h \ configmodelitemdelegate.h \ servermode.h \ servermodereader.h \ - tealeafreader.h + tealeafreader.h \ + treescanner.h SOURCES = builddirmanager.cpp \ builddirreader.cpp \ @@ -62,6 +63,7 @@ SOURCES = builddirmanager.cpp \ configmodelitemdelegate.cpp \ servermode.cpp \ servermodereader.cpp \ - tealeafreader.cpp + tealeafreader.cpp \ + treescanner.cpp RESOURCES += cmakeproject.qrc diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 6bf3e624b78..a3c3c478aed 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -79,6 +79,8 @@ QtcPlugin { "servermodereader.cpp", "servermodereader.h", "tealeafreader.cpp", - "tealeafreader.h" + "tealeafreader.h", + "treescanner.cpp", + "treescanner.h" ] } diff --git a/src/plugins/cmakeprojectmanager/treescanner.cpp b/src/plugins/cmakeprojectmanager/treescanner.cpp new file mode 100644 index 00000000000..b8a865c8e98 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/treescanner.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alexander Drozdov. +** Contact: Alexander Drozdov (adrozdoff@gmail.com) +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "treescanner.h" + +#include + +#include + +#include +#include +#include + +#include + +using namespace ProjectExplorer; + +namespace CMakeProjectManager { +namespace Internal { + +TreeScanner::TreeScanner(QObject *parent) : QObject(parent) +{ + m_factory = TreeScanner::genericFileType; + m_filter = [](const Utils::MimeType &mimeType, const Utils::FileName &fn) { + return isWellKnownBinary(mimeType, fn) && isMimeBinary(mimeType, fn); + }; + + connect(&m_futureWatcher, &FutureWatcher::finished, this, &TreeScanner::finished); +} + +bool TreeScanner::asyncScanForFiles(const Utils::FileName &directory) +{ + if (!m_futureWatcher.isFinished()) + return false; + + auto fi = new FutureInterface(); + m_scanFuture = fi->future(); + m_futureWatcher.setFuture(m_scanFuture); + + Utils::runAsync([this, fi, directory]() { TreeScanner::scanForFiles(fi, directory, m_filter, m_factory); }); + + return true; +} + +void TreeScanner::setFilter(TreeScanner::FileFilter filter) +{ + if (isFinished()) + m_filter = filter; +} + +void TreeScanner::setTypeFactory(TreeScanner::FileTypeFactory factory) +{ + if (isFinished()) + m_factory = factory; +} + +TreeScanner::Future TreeScanner::future() const +{ + return m_scanFuture; +} + +bool TreeScanner::isFinished() const +{ + return m_futureWatcher.isFinished(); +} + +TreeScanner::Result TreeScanner::result() const +{ + if (isFinished()) + return m_scanFuture.result(); + return Result(); +} + +TreeScanner::Result TreeScanner::release() +{ + if (isFinished()) { + auto result = m_scanFuture.result(); + m_scanFuture = Future(); + return result; + } + return Result(); +} + +void TreeScanner::reset() +{ + if (isFinished()) + m_scanFuture = Future(); +} + +bool TreeScanner::isWellKnownBinary(const Utils::MimeType & /*mdb*/, const Utils::FileName &fn) +{ + return fn.endsWith(QLatin1String(".a")) || + fn.endsWith(QLatin1String(".o")) || + fn.endsWith(QLatin1String(".d")) || + fn.endsWith(QLatin1String(".exe")) || + fn.endsWith(QLatin1String(".dll")) || + fn.endsWith(QLatin1String(".obj")) || + fn.endsWith(QLatin1String(".elf")); +} + +bool TreeScanner::isMimeBinary(const Utils::MimeType &mimeType, const Utils::FileName &/*fn*/) +{ + bool isBinary = false; + if (mimeType.isValid()) { + QStringList mimes; + mimes << mimeType.name() << mimeType.allAncestors(); + isBinary = !mimes.contains(QLatin1String("text/plain")); + } + return isBinary; +} + +FileType TreeScanner::genericFileType(const Utils::MimeType &mimeType, const Utils::FileName &/*fn*/) +{ + FileType type = FileType::Unknown; + if (mimeType.isValid()) { + const QString mt = mimeType.name(); + if (mt == CppTools::Constants::C_SOURCE_MIMETYPE + || mt == CppTools::Constants::CPP_SOURCE_MIMETYPE + || mt == CppTools::Constants::OBJECTIVE_C_SOURCE_MIMETYPE + || mt == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE + || mt == CppTools::Constants::QDOC_MIMETYPE + || mt == CppTools::Constants::MOC_MIMETYPE) + type = FileType::Source; + else if (mt == CppTools::Constants::C_HEADER_MIMETYPE + || mt == CppTools::Constants::CPP_HEADER_MIMETYPE) + type = FileType::Header; + else if (mt == ProjectExplorer::Constants::FORM_MIMETYPE) + type = FileType::Form; + else if (mt == ProjectExplorer::Constants::RESOURCE_MIMETYPE) + type = FileType::Resource; + else if (mt == ProjectExplorer::Constants::SCXML_MIMETYPE) + type = FileType::StateChart; + else if (mt == ProjectExplorer::Constants::QML_MIMETYPE) + type = FileType::QML; + } + return type; +} + +void TreeScanner::scanForFiles(FutureInterface *fi, const Utils::FileName& directory, const FileFilter &filter, const FileTypeFactory &factory) +{ + std::unique_ptr fip(fi); + fip->reportStarted(); + Utils::MimeDatabase mdb; + + Result nodes + = FileNode::scanForFiles(directory, + [&mdb,&filter,&factory](const Utils::FileName &fn) -> FileNode * { + QTC_ASSERT(!fn.isEmpty(), return nullptr); + + const Utils::MimeType mimeType = mdb.mimeTypeForFile(fn.toString()); + + // Skip some files during scan. + // Filter out nullptr records after. + if (filter && filter(mimeType, fn)) + return nullptr; + + // Type detection + FileType type = FileType::Unknown; + if (factory) + type = factory(mimeType, fn); + + return new FileNode(fn, type, false); + }, + fip.get()); + + // Clean up nodes and keep it sorted + Result tmp = Utils::filtered(nodes, [](const FileNode *fn) -> bool { + // Simple skip null entries + // TODO: fix Node::scanForFiles() to skip null factory results + return fn; + }); + Utils::sort(tmp, ProjectExplorer::Node::sortByPath); + + fip->setProgressValue(fip->progressMaximum()); + fip->reportResult(tmp); + fip->reportFinished(); +} + +} // namespace Internal +} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/treescanner.h b/src/plugins/cmakeprojectmanager/treescanner.h new file mode 100644 index 00000000000..e58f2b625c9 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/treescanner.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alexander Drozdov. +** Contact: Alexander Drozdov (adrozdoff@gmail.com) +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "projectexplorer/projectnodes.h" + +#include +#include "utils/fileutils.h" + +#include +#include +#include + +#include + +namespace CMakeProjectManager { +namespace Internal { + +class TreeScanner : public QObject +{ + Q_OBJECT + +public: + using Result = QList; + using Future = QFuture; + using FutureWatcher = QFutureWatcher; + using FutureInterface = QFutureInterface; + + using FileFilter = std::function; + using FileTypeFactory = std::function; + + explicit TreeScanner(QObject *parent = nullptr); + + // Start scanning in given directory + bool asyncScanForFiles(const Utils::FileName& directory); + + // Setup filter for ignored files + void setFilter(FileFilter filter); + + // Setup factory for file types + void setTypeFactory(FileTypeFactory factory); + + Future future() const; + bool isFinished() const; + + // Takes not-owning result + Result result() const; + // Takes owning of result + Result release(); + // Clear scan results + void reset(); + + // Standard filters helpers + static bool isWellKnownBinary(const Utils::MimeType &mimeType, const Utils::FileName &fn); + static bool isMimeBinary(const Utils::MimeType &mimeType, const Utils::FileName &fn); + + // Standard file factory + static ProjectExplorer::FileType genericFileType(const Utils::MimeType &mdb, const Utils::FileName& fn); + +signals: + void finished(); + +private: + static void scanForFiles(FutureInterface *fi, const Utils::FileName &directory, const FileFilter &filter, const FileTypeFactory &factory); + +private: + FileFilter m_filter; + FileTypeFactory m_factory; + + FutureWatcher m_futureWatcher; + Future m_scanFuture; +}; + +} // namespace Internal +} // namespace CMakeProjectManager + +