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 <tobias.hunger@qt.io>
This commit is contained in:
Alexander Drozdov
2016-12-04 03:36:12 +10:00
parent 357cefe67c
commit dfaf01614d
13 changed files with 428 additions and 85 deletions

View File

@@ -54,7 +54,6 @@
#include <utils/mimetypes/mimedatabase.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/runextensions.h>
#include <utils/synchronousprocess.h>
#include <QDateTime>
@@ -82,9 +81,6 @@ BuildDirManager::BuildDirManager(CMakeBuildConfiguration *bc) :
m_reparseTimer.setSingleShot(true);
connect(&m_reparseTimer, &QTimer::timeout, this, &BuildDirManager::parse);
connect(&m_futureWatcher, &QFutureWatcher<QList<FileNode *>>::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<QList<ProjectExplorer::FileNode *>>();
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<QList<FileNode *>> *fi)
{
std::unique_ptr<QFutureInterface<QList<FileNode *>>> fip(fi);
fip->reportStarted();
Utils::MimeDatabase mdb;
QList<FileNode *> 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<QList<FileNode *>>());
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<FileNode *> &allFiles)
{
QTC_ASSERT(m_reader, return);
QTC_ASSERT(m_scanFuture.isFinished(), return);
const Utils::FileName projectFile = m_buildConfiguration->target()->project()->projectFilePath();
QList<FileNode *> 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<QList<FileNode *>>(); // flush stale results
const QList<FileNode *> allFiles = tmp;
m_reader->generateProjectTree(root, allFiles);
m_reader->generateProjectTree(root, tmp);
QSet<FileNode *> 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<FileNode *> leftOvers = Utils::filtered(allFiles, [&usedNodes](FileNode *fn) {
QList<FileNode *> leftOvers = Utils::filtered(tmp, [&usedNodes](FileNode *fn) {
return !usedNodes.contains(fn);
});
qDeleteAll(leftOvers);

View File

@@ -30,7 +30,6 @@
#include <utils/fileutils.h>
#include <QFutureWatcher>
#include <QObject>
#include <QTemporaryDir>
#include <QTimer>
@@ -72,7 +71,7 @@ public:
bool updateCMakeStateBeforeBuild();
bool persistCMakeState();
void generateProjectTree(CMakeListsNode *root);
void generateProjectTree(CMakeListsNode *root, const QList<ProjectExplorer::FileNode *> &allFiles);
QSet<Core::Id> updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder);
QList<CMakeBuildTarget> buildTargets() const;
@@ -103,8 +102,6 @@ private:
void becameDirty();
void asyncScanForFiles(QFutureInterface<QList<ProjectExplorer::FileNode *>> *fi);
CMakeBuildConfiguration *m_buildConfiguration = nullptr;
mutable std::unique_ptr<QTemporaryDir> m_tempDir = nullptr;
mutable CMakeConfig m_cmakeCache;
@@ -112,8 +109,6 @@ private:
QTimer m_reparseTimer;
std::unique_ptr<BuildDirReader> m_reader;
QFutureWatcher<QList<ProjectExplorer::FileNode*>> m_futureWatcher;
QFuture<QList<ProjectExplorer::FileNode*>> m_scanFuture;
mutable QList<CMakeBuildTarget> m_buildTargets;
};

View File

@@ -210,12 +210,12 @@ QList<CMakeBuildTarget> CMakeBuildConfiguration::buildTargets() const
return m_buildDirManager->buildTargets();
}
void CMakeBuildConfiguration::generateProjectTree(CMakeListsNode *root) const
void CMakeBuildConfiguration::generateProjectTree(CMakeListsNode *root, const QList<FileNode*> &allFiles) const
{
if (!m_buildDirManager || m_buildDirManager->isParsing())
return;
return m_buildDirManager->generateProjectTree(root);
m_buildDirManager->generateProjectTree(root, allFiles);
}
QSet<Core::Id> CMakeBuildConfiguration::updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder)

View File

@@ -85,7 +85,7 @@ public:
void clearCache();
QList<CMakeBuildTarget> buildTargets() const;
void generateProjectTree(CMakeListsNode *root) const;
void generateProjectTree(CMakeListsNode *root, const QList<ProjectExplorer::FileNode *> &allFiles) const;
QSet<Core::Id> updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder);
static Utils::FileName

View File

@@ -32,9 +32,11 @@
#include "cmakerunconfiguration.h"
#include "cmakeprojectmanager.h"
#include <coreplugin/progressmanager/progressmanager.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/generatedcodemodelsupport.h>
#include <cpptools/projectinfo.h>
#include <cpptools/cpptoolsconstants.h>
#include <cpptools/projectpartbuilder.h>
#include <projectexplorer/buildtargetinfo.h>
#include <projectexplorer/deploymentdata.h>
@@ -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<CMakeListsNode *>(rootProjectNode()));
bc->generateProjectTree(static_cast<CMakeListsNode *>(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<CMakeBuildConfiguration*>(t->activeBuildConfiguration());
if (!bc)
return;
updateProjectData(bc);
}
CMakeBuildTarget CMakeProject::buildTargetForTitle(const QString &title)
{
foreach (const CMakeBuildTarget &ct, buildTargets())

View File

@@ -26,6 +26,7 @@
#pragma once
#include "cmake_global.h"
#include "treescanner.h"
#include <projectexplorer/extracompiler.h>
#include <projectexplorer/project.h>
@@ -33,6 +34,7 @@
#include <utils/fileutils.h>
#include <QFuture>
#include <QHash>
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<void> m_codeModelFuture;
QList<ProjectExplorer::ExtraCompiler *> m_extraCompilers;
Internal::TreeScanner m_treeScanner;
QHash<QString, bool> m_mimeBinaryCache;
QList<ProjectExplorer::FileNode *> m_allFiles;
friend class Internal::CMakeBuildConfiguration;
friend class Internal::CMakeBuildSettingsWidget;
};

View File

@@ -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

View File

@@ -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<CMakeProject *>(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);

View File

@@ -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

View File

@@ -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

View File

@@ -79,6 +79,8 @@ QtcPlugin {
"servermodereader.cpp",
"servermodereader.h",
"tealeafreader.cpp",
"tealeafreader.h"
"tealeafreader.h",
"treescanner.cpp",
"treescanner.h"
]
}

View File

@@ -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 <projectexplorer/projectexplorerconstants.h>
#include <cpptools/cpptoolsconstants.h>
#include <utils/qtcassert.h>
#include <utils/algorithm.h>
#include <utils/runextensions.h>
#include <memory>
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<FutureInterface> 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

View File

@@ -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 <utils/mimetypes/mimedatabase.h>
#include "utils/fileutils.h"
#include <QObject>
#include <QFuture>
#include <QFutureWatcher>
#include <functional>
namespace CMakeProjectManager {
namespace Internal {
class TreeScanner : public QObject
{
Q_OBJECT
public:
using Result = QList<ProjectExplorer::FileNode *>;
using Future = QFuture<Result>;
using FutureWatcher = QFutureWatcher<Result>;
using FutureInterface = QFutureInterface<Result>;
using FileFilter = std::function<bool(const Utils::MimeType &, const Utils::FileName &)>;
using FileTypeFactory = std::function<ProjectExplorer::FileType(const Utils::MimeType &, const Utils::FileName &)>;
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