From d115276b34f95e4d78fd94fabd7281ccf96da5b1 Mon Sep 17 00:00:00 2001 From: Tobias Hunger Date: Tue, 6 Aug 2019 12:41:46 +0200 Subject: [PATCH] CMake: Move code from CMakeProject into CMakeBuildSystem Introduce BuildSystem to implement functionality common to all build systems out there. This includes things like delaying the parsing by 1s. The actual CMake specific code is then moved into a derived class CMakeBuildSystem. Change-Id: I84f4344430f19a44e16534db294382c436169ed5 Reviewed-by: Christian Kandeler --- .../cmakeprojectmanager/CMakeLists.txt | 1 + .../cmakeprojectmanager/builddirmanager.h | 22 +- .../cmakebuildconfiguration.cpp | 30 +- .../cmakebuildconfiguration.h | 3 + .../cmakeprojectmanager/cmakebuildsystem.cpp | 483 ++++++++++++++++++ .../cmakeprojectmanager/cmakebuildsystem.h | 174 +++++++ .../cmakeprojectmanager/cmakeproject.cpp | 375 +------------- .../cmakeprojectmanager/cmakeproject.h | 42 +- .../cmakeprojectmanager.pro | 2 + .../cmakeprojectmanager.qbs | 2 + src/plugins/projectexplorer/project.h | 6 +- 11 files changed, 718 insertions(+), 422 deletions(-) create mode 100644 src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp create mode 100644 src/plugins/cmakeprojectmanager/cmakebuildsystem.h diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index 245698b5c29..651b557e861 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -10,6 +10,7 @@ add_qtc_plugin(CMakeProjectManager cmakeautocompleter.cpp cmakeautocompleter.h cmakebuildconfiguration.cpp cmakebuildconfiguration.h cmakebuildsettingswidget.cpp cmakebuildsettingswidget.h + cmakebuildsystem.cpp cmakebuildsystem.h cmakebuildstep.cpp cmakebuildstep.h cmakebuildtarget.h cmakecbpparser.cpp cmakecbpparser.h diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.h b/src/plugins/cmakeprojectmanager/builddirmanager.h index 39a20856a10..5e0f017a813 100644 --- a/src/plugins/cmakeprojectmanager/builddirmanager.h +++ b/src/plugins/cmakeprojectmanager/builddirmanager.h @@ -27,6 +27,7 @@ #include "builddirparameters.h" #include "builddirreader.h" +#include "cmakebuildsystem.h" #include "cmakebuildtarget.h" #include "cmakeconfigitem.h" @@ -87,16 +88,21 @@ public: CMakeConfig takeCMakeConfiguration(QString &errorMessage) const; static CMakeConfig parseCMakeConfiguration(const Utils::FilePath &cacheFile, - QString *errorMessage); + QString *errorMessage); enum ReparseParameters { - REPARSE_DEFAULT = 0, // use defaults - REPARSE_URGENT = 1, // Do not wait for more requests, start ASAP - REPARSE_FORCE_CMAKE_RUN = 2, // Force cmake to run - REPARSE_FORCE_CONFIGURATION = 4, // Force configuration arguments to cmake - REPARSE_CHECK_CONFIGURATION = 8, // Check and warn if on-disk config and QtC config differ - REPARSE_SCAN = 16, - REPARSE_IGNORE = 32, // Do not reparse:-) + REPARSE_DEFAULT = BuildSystem::PARAM_DEFAULT, // use defaults + REPARSE_URGENT = BuildSystem::PARAM_URGENT, // Do not wait for more requests, start ASAP + REPARSE_IGNORE = BuildSystem::PARAM_IGNORE, + + REPARSE_FORCE_CMAKE_RUN = (1 + << (BuildSystem::PARAM_CUSTOM_OFFSET + 0)), // Force cmake to run + REPARSE_FORCE_CONFIGURATION = (1 << (BuildSystem::PARAM_CUSTOM_OFFSET + + 1)), // Force configuration arguments to cmake + REPARSE_CHECK_CONFIGURATION + = (1 << (BuildSystem::PARAM_CUSTOM_OFFSET + + 2)), // Check and warn if on-disk config and QtC config differ + REPARSE_SCAN = (1 << (BuildSystem::PARAM_CUSTOM_OFFSET + 3)), // Run filesystem scan }; static QString flagsString(int reparseFlags); diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 6c1f835bdad..5fd514ce3b1 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -91,25 +91,24 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *parent, Core::Id id) BuildConfiguration::Unknown)); connect(project(), &Project::parsingFinished, this, &BuildConfiguration::enabledChanged); + BuildSystem *bs = qobject_cast(project()->buildSystem()); + // BuildDirManager: - connect(&m_buildDirManager, &BuildDirManager::requestReparse, this, [this](int options) { + connect(&m_buildDirManager, &BuildDirManager::requestReparse, this, [this, bs](int options) { if (isActive()) { qCDebug(cmakeBuildConfigurationLog) << "Passing on reparse request with flags" << BuildDirManager::flagsString(options); - project()->requestReparse(options); + bs->requestParse(options); } }); connect(&m_buildDirManager, &BuildDirManager::dataAvailable, this, &CMakeBuildConfiguration::handleParsingSucceeded); - connect(&m_buildDirManager, &BuildDirManager::errorOccured, this, [this](const QString &msg) { - setError(msg); - QString errorMessage; - setConfigurationFromCMake(m_buildDirManager.takeCMakeConfiguration(errorMessage)); - // ignore errorMessage here, we already got one. - project()->handleParsingError(this); - }); + connect(&m_buildDirManager, + &BuildDirManager::errorOccured, + this, + &CMakeBuildConfiguration::handleParsingFailed); connect(&m_buildDirManager, &BuildDirManager::parsingStarted, this, [this]() { clearError(CMakeBuildConfiguration::ForceEnabledChanged::True); }); @@ -538,7 +537,18 @@ void CMakeBuildConfiguration::handleParsingSucceeded() target()->setDeploymentData(deploymentData()); } - project()->handleParsingSuccess(this); + static_cast(project()->buildSystem())->handleParsingSuccess(this); +} + +void CMakeBuildConfiguration::handleParsingFailed(const QString &msg) +{ + setError(msg); + + QString errorMessage; + setConfigurationFromCMake(m_buildDirManager.takeCMakeConfiguration(errorMessage)); + // ignore errorMessage here, we already got one. + + static_cast(project()->buildSystem())->handleParsingError(this); } std::unique_ptr CMakeBuildConfiguration::generateProjectTree( diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h index eef0d88c1f0..7c0d07a3e9d 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h @@ -36,6 +36,7 @@ #include namespace CMakeProjectManager { +class CMakeBuildSystem; class CMakeExtraBuildInfo; class CMakeProject; @@ -106,6 +107,7 @@ private: void setWarning(const QString &message); void handleParsingSucceeded(); + void handleParsingFailed(const QString &msg); std::unique_ptr generateProjectTree( const QList &allFiles); @@ -123,6 +125,7 @@ private: QList m_buildTargets; friend class CMakeBuildSettingsWidget; + friend class CMakeProjectManager::CMakeBuildSystem; friend class CMakeProjectManager::CMakeProject; friend class BuildDirManager; }; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp new file mode 100644 index 00000000000..b39c29d6c9c --- /dev/null +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -0,0 +1,483 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 "cmakebuildsystem.h" + +#include "cmakebuildconfiguration.h" +#include "cmakeproject.h" +#include "cmakeprojectconstants.h" +#include "cmakeprojectnodes.h" + +#if 0 +#include "cmakebuildstep.h" +#include "cmakekitinformation.h" +#include "cmakeprojectmanager.h" +#include "cmakeprojectnodes.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace { + +CMakeProjectManager::Internal::CMakeBuildConfiguration *activeBc(Project *p) +{ + if (!p) + return nullptr; + + return qobject_cast( + p->activeTarget() ? p->activeTarget()->activeBuildConfiguration() : nullptr); +} + +} // namespace + +namespace CMakeProjectManager { + +using namespace Internal; + +Q_LOGGING_CATEGORY(cmakeBuildSystemLog, "qtc.cmake.buildsystem", QtWarningMsg); + +// -------------------------------------------------------------------- +// BuildSystem: +// -------------------------------------------------------------------- + +BuildSystem::BuildSystem(Project *project) + : m_project(project) +{ + QTC_CHECK(project); + + // Timer: + m_delayedParsingTimer.setSingleShot(true); + + connect(&m_delayedParsingTimer, &QTimer::timeout, this, &BuildSystem::triggerParsing); +} + +BuildSystem::~BuildSystem() = default; + +Project *BuildSystem::project() const +{ + return m_project; +} + +bool BuildSystem::isWaitingForParse() const +{ + return m_delayedParsingTimer.isActive(); +} + +void BuildSystem::requestParse(int reparseParameters) +{ + QTC_ASSERT(!(reparseParameters & PARAM_ERROR), return ); + if (reparseParameters & PARAM_IGNORE) + return; + + m_delayedParsingTimer.setInterval((reparseParameters & PARAM_URGENT) ? 0 : 1000); + m_delayedParsingTimer.start(); + m_delayedParsingParameters = m_delayedParsingParameters | reparseParameters; +} + +void BuildSystem::triggerParsing() +{ + int parameters = m_delayedParsingParameters; + m_delayedParsingParameters = BuildSystem::PARAM_DEFAULT; + + QTC_CHECK(!m_project->isParsing()); + QTC_ASSERT((parameters & BuildSystem::PARAM_ERROR) == 0, return ); + if (parameters & BuildSystem::PARAM_IGNORE) + return; + + // Clear buildsystem specific parameters before passing them on! + parameters = parameters + & ~(BuildSystem::PARAM_ERROR | BuildSystem::PARAM_IGNORE + | BuildSystem::PARAM_URGENT); + + { + ParsingContext ctx(m_project->guardParsingRun(), parameters, m_project, activeBc(m_project)); + if (validateParsingContext(ctx)) + parseProject(std::move(ctx)); + } +} + +// -------------------------------------------------------------------- +// CMakeBuildSystem: +// -------------------------------------------------------------------- + +CMakeBuildSystem::CMakeBuildSystem(CMakeProject *p) + : BuildSystem(p) + , m_cppCodeModelUpdater(new CppTools::CppProjectUpdater) +{ + // TreeScanner: + connect(&m_treeScanner, + &TreeScanner::finished, + this, + &CMakeBuildSystem::handleTreeScanningFinished); + + m_treeScanner.setFilter([this, p](const Utils::MimeType &mimeType, const Utils::FilePath &fn) { + // Mime checks requires more resources, so keep it last in check list + auto isIgnored = fn.toString().startsWith(p->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::FilePath &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; + }); +} + +CMakeBuildSystem::~CMakeBuildSystem() +{ + if (!m_treeScanner.isFinished()) { + auto future = m_treeScanner.future(); + future.cancel(); + future.waitForFinished(); + } + delete m_cppCodeModelUpdater; + qDeleteAll(m_extraCompilers); + qDeleteAll(m_allFiles); +} + +bool CMakeBuildSystem::validateParsingContext(const ParsingContext &ctx) +{ + return ctx.project && qobject_cast(ctx.buildConfiguration); +} + +void CMakeBuildSystem::parseProject(ParsingContext &&ctx) +{ + m_currentContext = std::move(ctx); + + auto bc = qobject_cast(m_currentContext.buildConfiguration); + + int parameters = m_currentContext.parameters; + + if (m_allFiles.isEmpty()) + parameters |= BuildDirManager::REPARSE_SCAN; + + m_waitingForScan = parameters & BuildDirManager::REPARSE_SCAN; + m_waitingForParse = true; + m_combinedScanAndParseResult = true; + + if (m_waitingForScan) { + QTC_CHECK(m_treeScanner.isFinished()); + m_treeScanner.asyncScanForFiles(m_currentContext.project->projectDirectory()); + Core::ProgressManager::addTask(m_treeScanner.future(), + tr("Scan \"%1\" project tree") + .arg(m_currentContext.project->displayName()), + "CMake.Scan.Tree"); + } + + bc->m_buildDirManager.parse(parameters); +} + +void CMakeBuildSystem::handleTreeScanningFinished() +{ + QTC_CHECK(m_waitingForScan); + + qDeleteAll(m_allFiles); + m_allFiles = Utils::transform(m_treeScanner.release(), [](const FileNode *fn) { return fn; }); + + m_combinedScanAndParseResult = m_combinedScanAndParseResult && true; + m_waitingForScan = false; + + combineScanAndParse(); +} + +void CMakeBuildSystem::handleParsingSuccess(CMakeBuildConfiguration *bc) +{ + if (bc != m_currentContext.buildConfiguration) + return; // Not current information, ignore. + + QTC_ASSERT(m_waitingForParse, return ); + + m_waitingForParse = false; + m_combinedScanAndParseResult = m_combinedScanAndParseResult && true; + + combineScanAndParse(); +} + +void CMakeBuildSystem::handleParsingError(CMakeBuildConfiguration *bc) +{ + if (bc != m_currentContext.buildConfiguration) + return; // Not current information, ignore. + + QTC_CHECK(m_waitingForParse); + + m_waitingForParse = false; + m_combinedScanAndParseResult = false; + + combineScanAndParse(); +} + +void CMakeBuildSystem::combineScanAndParse() +{ + auto bc = qobject_cast(m_currentContext.buildConfiguration); + if (bc && bc->isActive()) { + if (m_waitingForParse || m_waitingForScan) + return; + + if (m_combinedScanAndParseResult) { + updateProjectData(qobject_cast(m_currentContext.project), bc); + } + } + + m_currentContext = BuildSystem::ParsingContext(); +} + +void CMakeBuildSystem::updateProjectData(CMakeProject *p, CMakeBuildConfiguration *bc) +{ + qCDebug(cmakeBuildSystemLog) << "Updating CMake project data"; + + QTC_ASSERT(m_treeScanner.isFinished() && !bc->m_buildDirManager.isParsing(), return ); + + CMakeConfig patchedConfig = bc->configurationFromCMake(); + { + CMakeConfigItem settingFileItem; + settingFileItem.key = "ANDROID_DEPLOYMENT_SETTINGS_FILE"; + settingFileItem.value = bc->buildDirectory() + .pathAppended("android_deployment_settings.json") + .toString() + .toUtf8(); + patchedConfig.append(settingFileItem); + } + { + QSet res; + QStringList apps; + for (const auto &target : bc->buildTargets()) { + if (target.targetType == CMakeProjectManager::DynamicLibraryType) { + res.insert(target.executable.parentDir().toString()); + apps.push_back(target.executable.toUserOutput()); + } + // ### shall we add also the ExecutableType ? + } + { + CMakeConfigItem paths; + paths.key = "ANDROID_SO_LIBS_PATHS"; + paths.values = Utils::toList(res); + patchedConfig.append(paths); + } + + apps.sort(); + { + CMakeConfigItem appsPaths; + appsPaths.key = "TARGETS_BUILD_PATH"; + appsPaths.values = apps; + patchedConfig.append(appsPaths); + } + } + + { + auto newRoot = bc->generateProjectTree(m_allFiles); + if (newRoot) { + p->setRootProjectNode(std::move(newRoot)); + if (p->rootProjectNode()) + p->setDisplayName(p->rootProjectNode()->displayName()); + + for (const CMakeBuildTarget &bt : bc->buildTargets()) { + const QString buildKey = bt.title; + if (ProjectNode *node = p->findNodeForBuildKey(buildKey)) { + if (auto targetNode = dynamic_cast(node)) + targetNode->setConfig(patchedConfig); + } + } + } + } + + { + qDeleteAll(m_extraCompilers); + m_extraCompilers = findExtraCompilers(p); + CppTools::GeneratedCodeModelSupport::update(m_extraCompilers); + qCDebug(cmakeBuildSystemLog) << "Extra compilers updated."; + } + + QtSupport::CppKitInfo kitInfo(p); + QTC_ASSERT(kitInfo.isValid(), return ); + + { + QString errorMessage; + CppTools::RawProjectParts rpps = bc->m_buildDirManager.createRawProjectParts(errorMessage); + if (!errorMessage.isEmpty()) + bc->setError(errorMessage); + qCDebug(cmakeBuildSystemLog) << "Raw project parts created." << errorMessage; + + for (CppTools::RawProjectPart &rpp : rpps) { + rpp.setQtVersion( + kitInfo.projectPartQtVersion); // TODO: Check if project actually uses Qt. + if (kitInfo.cxxToolChain) + rpp.setFlagsForCxx({kitInfo.cxxToolChain, rpp.flagsForCxx.commandLineFlags}); + if (kitInfo.cToolChain) + rpp.setFlagsForC({kitInfo.cToolChain, rpp.flagsForC.commandLineFlags}); + } + + m_cppCodeModelUpdater->update({p, kitInfo, bc->environment(), rpps}); + } + { + updateQmlJSCodeModel(p, bc); + } + + emit p->fileListChanged(); + + emit bc->emitBuildTypeChanged(); + + bc->m_buildDirManager.resetData(); + + qCDebug(cmakeBuildSystemLog) << "All CMake project data up to date."; +} + +QList CMakeBuildSystem::findExtraCompilers(CMakeProject *p) +{ + qCDebug(cmakeBuildSystemLog) << "Finding Extra Compilers: start."; + + QList extraCompilers; + const QList factories = ExtraCompilerFactory::extraCompilerFactories(); + + qCDebug(cmakeBuildSystemLog) << "Finding Extra Compilers: Got factories."; + + const QSet fileExtensions = Utils::transform(factories, + &ExtraCompilerFactory::sourceTag); + + qCDebug(cmakeBuildSystemLog) << "Finding Extra Compilers: Got file extensions:" + << fileExtensions; + + // Find all files generated by any of the extra compilers, in a rather crude way. + const FilePathList fileList = p->files([&fileExtensions, p](const Node *n) { + if (!p->SourceFiles(n)) + return false; + const QString fp = n->filePath().toString(); + const int pos = fp.lastIndexOf('.'); + return pos >= 0 && fileExtensions.contains(fp.mid(pos + 1)); + }); + + qCDebug(cmakeBuildSystemLog) << "Finding Extra Compilers: Got list of files to check."; + + // Generate the necessary information: + for (const FilePath &file : fileList) { + qCDebug(cmakeBuildSystemLog) + << "Finding Extra Compilers: Processing" << file.toUserOutput(); + ExtraCompilerFactory *factory = Utils::findOrDefault(factories, + [&file](const ExtraCompilerFactory *f) { + return file.endsWith( + '.' + f->sourceTag()); + }); + QTC_ASSERT(factory, continue); + + QStringList generated = p->filesGeneratedFrom(file.toString()); + qCDebug(cmakeBuildSystemLog) + << "Finding Extra Compilers: generated files:" << generated; + if (generated.isEmpty()) + continue; + + const FilePathList fileNames = transform(generated, [](const QString &s) { + return FilePath::fromString(s); + }); + extraCompilers.append(factory->create(p, file, fileNames)); + qCDebug(cmakeBuildSystemLog) + << "Finding Extra Compilers: done with" << file.toUserOutput(); + } + + qCDebug(cmakeBuildSystemLog) << "Finding Extra Compilers: done."; + + return extraCompilers; +} + +void CMakeBuildSystem::updateQmlJSCodeModel(CMakeProject *p, CMakeBuildConfiguration *bc) +{ + QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); + + if (!modelManager) + return; + + QmlJS::ModelManagerInterface::ProjectInfo projectInfo = modelManager + ->defaultProjectInfoForProject(p); + + projectInfo.importPaths.clear(); + + const CMakeConfig &cm = bc->configurationFromCMake(); + const QString cmakeImports = QString::fromUtf8(CMakeConfigItem::valueOf("QML_IMPORT_PATH", cm)); + + foreach (const QString &cmakeImport, CMakeConfigItem::cmakeSplitValue(cmakeImports)) + projectInfo.importPaths.maybeInsert(FilePath::fromString(cmakeImport), QmlJS::Dialect::Qml); + + modelManager->updateProjectInfo(projectInfo, p); +} + +} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h new file mode 100644 index 00000000000..c30a29d5a78 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 +#include + +#include + +namespace CppTools { +class CppProjectUpdater; +} // namespace CppTools + +namespace ProjectExplorer { +class BuildConfiguration; +class ExtraCompiler; +} // namespace ProjectExplorer + +namespace CMakeProjectManager { + +class CMakeProject; + +namespace Internal { +class CMakeBuildConfiguration; +} // namespace Internal + +// -------------------------------------------------------------------- +// BuildSystem: +// -------------------------------------------------------------------- + +class BuildSystem : public QObject +{ + Q_OBJECT + +public: + const static int PARAM_CUSTOM_OFFSET = 3; + enum Parameters : int { + PARAM_DEFAULT = 0, // use defaults + + PARAM_IGNORE = (1 << (PARAM_CUSTOM_OFFSET - 3)), // Ignore this request without raising a fuss + PARAM_ERROR = (1 << (PARAM_CUSTOM_OFFSET - 2)), // Ignore this request and warn + + PARAM_URGENT = (1 << (PARAM_CUSTOM_OFFSET - 1)), // Do not wait for more requests, start ASAP + }; + + explicit BuildSystem(ProjectExplorer::Project *project); + ~BuildSystem() override; + + BuildSystem(const BuildSystem &other) = delete; + + ProjectExplorer::Project *project() const; + + bool isWaitingForParse() const; + void requestParse(int reparseParameters); // request a (delayed!) parser run. + +protected: + class ParsingContext + { + public: + ParsingContext() = default; + + ParsingContext(const ParsingContext &other) = delete; + ParsingContext &operator=(const ParsingContext &other) = delete; + ParsingContext(ParsingContext &&other) = default; + ParsingContext &operator=(ParsingContext &&other) = default; + + ProjectExplorer::Project::ParseGuard guard; + + int parameters = PARAM_DEFAULT; + ProjectExplorer::Project *project = nullptr; + ProjectExplorer::BuildConfiguration *buildConfiguration = nullptr; + + private: + ParsingContext(ProjectExplorer::Project::ParseGuard &&g, + int params, + ProjectExplorer::Project *p, + ProjectExplorer::BuildConfiguration *bc) + : guard(std::move(g)) + , parameters(params) + , project(p) + , buildConfiguration(bc) + {} + + friend class BuildSystem; + }; + + virtual bool validateParsingContext(const ParsingContext &ctx) + { + Q_UNUSED(ctx) + return true; + } + + virtual void parseProject(ParsingContext &&ctx) = 0; // actual code to parse project + +private: + void triggerParsing(); + + ProjectExplorer::Project *m_project; + + QTimer m_delayedParsingTimer; + int m_delayedParsingParameters = PARAM_DEFAULT; +}; + +// -------------------------------------------------------------------- +// CMakeBuildSystem: +// -------------------------------------------------------------------- + +class CMakeBuildSystem : public BuildSystem +{ + Q_OBJECT + +public: + explicit CMakeBuildSystem(CMakeProject *project); + ~CMakeBuildSystem() final; + +protected: + bool validateParsingContext(const ParsingContext &ctx) final; + void parseProject(ParsingContext &&ctx) final; + +private: + // Treescanner states: + void handleTreeScanningFinished(); + + // Parser states: + void handleParsingSuccess(Internal::CMakeBuildConfiguration *bc); + void handleParsingError(Internal::CMakeBuildConfiguration *bc); + + // Combining Treescanner and Parser states: + void combineScanAndParse(); + + void updateProjectData(CMakeProject *p, Internal::CMakeBuildConfiguration *bc); + QList findExtraCompilers(CMakeProject *p); + void updateQmlJSCodeModel(CMakeProject *p, Internal::CMakeBuildConfiguration *bc); + + ProjectExplorer::TreeScanner m_treeScanner; + QHash m_mimeBinaryCache; + QList m_allFiles; + + bool m_waitingForScan = false; + bool m_waitingForParse = false; + bool m_combinedScanAndParseResult = false; + + ParsingContext m_currentContext; + + CppTools::CppProjectUpdater *m_cppCodeModelUpdater = nullptr; + QList m_extraCompilers; + + friend class Internal::CMakeBuildConfiguration; // For handleParsing* callbacks +}; + +} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index d174f602767..caa8e0eb130 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -67,27 +67,6 @@ using namespace Utils; namespace CMakeProjectManager { -Q_LOGGING_CATEGORY(cmakeProjectLog, "qtc.cmake.project", QtWarningMsg); - -class TraceTimer -{ -public: - TraceTimer(const QString &msg) - : m_message(msg) - { - m_timer.start(); - } - - ~TraceTimer() - { - qCInfo(cmakeProjectLog) << QString("%1 (%2ms)").arg(m_message).arg(m_timer.elapsed()); - } - -private: - QElapsedTimer m_timer; - QString m_message; -}; - using namespace Internal; static CMakeBuildConfiguration *activeBc(const CMakeProject *p) @@ -105,200 +84,18 @@ static CMakeBuildConfiguration *activeBc(const CMakeProject *p) */ CMakeProject::CMakeProject(const FilePath &fileName) : Project(Constants::CMAKEMIMETYPE, fileName) - , m_cppCodeModelUpdater(new CppTools::CppProjectUpdater) { + m_buildsystem = std::make_unique(this); + setId(CMakeProjectManager::Constants::CMAKEPROJECT_ID); setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID)); setDisplayName(projectDirectory().fileName()); setCanBuildProducts(); setKnowsAllBuildExecutables(false); setHasMakeInstallEquivalent(true); - - // Timer: - m_delayedParsingTimer.setSingleShot(true); - - connect(&m_delayedParsingTimer, &QTimer::timeout, - this, [this]() { startParsing(m_delayedParsingParameters); }); - - // TreeScanner: - connect(&m_treeScanner, &TreeScanner::finished, this, &CMakeProject::handleTreeScanningFinished); - - m_treeScanner.setFilter([this](const Utils::MimeType &mimeType, const Utils::FilePath &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::FilePath &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; - }); } -CMakeProject::~CMakeProject() -{ - if (!m_treeScanner.isFinished()) { - auto future = m_treeScanner.future(); - future.cancel(); - future.waitForFinished(); - } - delete m_cppCodeModelUpdater; - qDeleteAll(m_extraCompilers); - qDeleteAll(m_allFiles); -} - -void CMakeProject::updateProjectData(CMakeBuildConfiguration *bc) -{ - TraceTimer updateProjectTotalTimer(Q_FUNC_INFO); - qCDebug(cmakeProjectLog) << "Updating CMake project data"; - const CMakeBuildConfiguration *aBc = activeBc(this); - QString errorMessage; - - QTC_ASSERT(bc, return); - QTC_ASSERT(bc == aBc, return); - QTC_ASSERT(m_treeScanner.isFinished() && !bc->m_buildDirManager.isParsing(), return ); - - CMakeConfig patchedConfig = bc->configurationFromCMake(); - { - CMakeConfigItem settingFileItem; - settingFileItem.key = "ANDROID_DEPLOYMENT_SETTINGS_FILE"; - settingFileItem.value = bc->buildDirectory() - .pathAppended("android_deployment_settings.json") - .toString() - .toUtf8(); - patchedConfig.append(settingFileItem); - } - { - TraceTimer appsTimer(" application data"); - QSet res; - QStringList apps; - for (const auto &target : bc->buildTargets()) { - if (target.targetType == CMakeProjectManager::DynamicLibraryType) { - res.insert(target.executable.parentDir().toString()); - apps.push_back(target.executable.toUserOutput()); - } - // ### shall we add also the ExecutableType ? - } - { - CMakeConfigItem paths; - paths.key = "ANDROID_SO_LIBS_PATHS"; - paths.values = Utils::toList(res); - patchedConfig.append(paths); - } - - apps.sort(); - { - CMakeConfigItem appsPaths; - appsPaths.key = "TARGETS_BUILD_PATH"; - appsPaths.values = apps; - patchedConfig.append(appsPaths); - } - } - - { - TraceTimer projectTreeTimer(" project tree"); - auto newRoot = bc->generateProjectTree(m_allFiles); - if (newRoot) { - setRootProjectNode(std::move(newRoot)); - if (rootProjectNode()) - setDisplayName(rootProjectNode()->displayName()); - - for (const CMakeBuildTarget &bt : bc->buildTargets()) { - const QString buildKey = bt.title; - if (ProjectNode *node = findNodeForBuildKey(buildKey)) { - if (auto targetNode = dynamic_cast(node)) - targetNode->setConfig(patchedConfig); - } - } - } - } - - { - TraceTimer projectTreeTimer(" extra compilers"); - qDeleteAll(m_extraCompilers); - m_extraCompilers = findExtraCompilers(); - CppTools::GeneratedCodeModelSupport::update(m_extraCompilers); - qCDebug(cmakeProjectLog) << "Extra compilers updated."; - } - - QtSupport::CppKitInfo kitInfo(this); - QTC_ASSERT(kitInfo.isValid(), return); - - { - TraceTimer cxxCodemodelTimer(" cxx codemodel"); - CppTools::RawProjectParts rpps = bc->m_buildDirManager.createRawProjectParts(errorMessage); - checkAndReportError(errorMessage); - qCDebug(cmakeProjectLog) << "Raw project parts created."; - - for (CppTools::RawProjectPart &rpp : rpps) { - rpp.setQtVersion( - kitInfo.projectPartQtVersion); // TODO: Check if project actually uses Qt. - if (kitInfo.cxxToolChain) - rpp.setFlagsForCxx({kitInfo.cxxToolChain, rpp.flagsForCxx.commandLineFlags}); - if (kitInfo.cToolChain) - rpp.setFlagsForC({kitInfo.cToolChain, rpp.flagsForC.commandLineFlags}); - } - - m_cppCodeModelUpdater->update({this, kitInfo, activeBuildEnvironment(), rpps}); - } - { - TraceTimer qmlCodemodelTimer(" qml codemodel"); - updateQmlJSCodeModel(bc); - } - - emit fileListChanged(); - - emit bc->emitBuildTypeChanged(); - - bc->m_buildDirManager.resetData(); // Clear remaining data - - qCDebug(cmakeProjectLog) << "All CMake project data up to date."; -} - -void CMakeProject::updateQmlJSCodeModel(CMakeBuildConfiguration *bc) -{ - QTC_ASSERT(bc, return ); - - QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); - - if (!modelManager) - return; - - QmlJS::ModelManagerInterface::ProjectInfo projectInfo = - modelManager->defaultProjectInfoForProject(this); - - projectInfo.importPaths.clear(); - - const CMakeConfig &cm = bc->configurationFromCMake(); - const QString cmakeImports = QString::fromUtf8(CMakeConfigItem::valueOf("QML_IMPORT_PATH", cm)); - - foreach (const QString &cmakeImport, CMakeConfigItem::cmakeSplitValue(cmakeImports)) - projectInfo.importPaths.maybeInsert(FilePath::fromString(cmakeImport), QmlJS::Dialect::Qml); - - modelManager->updateProjectInfo(projectInfo, this); -} +CMakeProject::~CMakeProject() = default; Tasks CMakeProject::projectIssues(const Kit *k) const { @@ -330,7 +127,6 @@ void CMakeProject::runCMakeAndScanProjectTree() CMakeBuildConfiguration *bc = activeBc(this); if (isParsing() || !bc) return; - QTC_ASSERT(m_treeScanner.isFinished(), return); BuildDirParameters parameters(bc); bc->m_buildDirManager.setParametersAndRequestParse(parameters, @@ -366,33 +162,6 @@ void CMakeProject::clearCMakeCache() bc->m_buildDirManager.clearCache(); } -void CMakeProject::startParsing(int reparseParameters) -{ - m_delayedParsingParameters = BuildDirManager::REPARSE_DEFAULT; - - if (reparseParameters & BuildDirManager::REPARSE_IGNORE) - return; - - CMakeBuildConfiguration *bc = activeBc(this); - QTC_ASSERT(bc, return ); - - m_parseGuard = std::move(guardParsingRun()); - - m_waitingForScan = reparseParameters & BuildDirManager::REPARSE_SCAN; - m_waitingForParse = true; - m_combinedScanAndParseResult = true; - - if (m_waitingForScan) { - QTC_CHECK(m_treeScanner.isFinished()); - m_treeScanner.asyncScanForFiles(projectDirectory()); - Core::ProgressManager::addTask(m_treeScanner.future(), - tr("Scan \"%1\" project tree").arg(displayName()), - "CMake.Scan.Tree"); - } - - bc->m_buildDirManager.parse(reparseParameters); -} - bool CMakeProject::setupTarget(Target *t) { t->updateDefaultBuildConfigurations(); @@ -402,80 +171,6 @@ bool CMakeProject::setupTarget(Target *t) return true; } -void CMakeProject::reportError(const QString &errorMessage) const -{ - CMakeBuildConfiguration *bc = activeBc(this); - if (bc) - bc->setError(errorMessage); -} - -void CMakeProject::requestReparse(int reparseParameters) -{ - if (reparseParameters & BuildDirManager::REPARSE_IGNORE) - return; - - m_delayedParsingTimer.setInterval((reparseParameters & BuildDirManager::REPARSE_URGENT) ? 0 - : 1000); - m_delayedParsingTimer.start(); - m_delayedParsingParameters = m_delayedParsingParameters | reparseParameters; - if (m_allFiles.isEmpty()) - m_delayedParsingParameters |= BuildDirManager::REPARSE_SCAN; -} - -void CMakeProject::handleTreeScanningFinished() -{ - QTC_CHECK(m_waitingForScan); - - qDeleteAll(m_allFiles); - m_allFiles = Utils::transform(m_treeScanner.release(), [](const FileNode *fn) { return fn; }); - - CMakeBuildConfiguration *bc = activeBc(this); - QTC_ASSERT(bc, return); - - m_combinedScanAndParseResult = m_combinedScanAndParseResult && true; - m_waitingForScan = false; - - combineScanAndParse(bc); -} - -void CMakeProject::handleParsingSuccess(CMakeBuildConfiguration *bc) -{ - QTC_ASSERT(m_waitingForParse, return); - - m_waitingForParse = false; - m_combinedScanAndParseResult = m_combinedScanAndParseResult && true; - - combineScanAndParse(bc); -} - -void CMakeProject::handleParsingError(CMakeBuildConfiguration *bc) -{ - QTC_CHECK(m_waitingForParse); - - m_waitingForParse = false; - m_combinedScanAndParseResult = false; - - combineScanAndParse(bc); -} - -void CMakeProject::combineScanAndParse(CMakeBuildConfiguration *bc) -{ - QTC_ASSERT(bc && bc->isActive(), return); - - if (m_waitingForParse || m_waitingForScan) - return; - - if (m_combinedScanAndParseResult) { - m_parseGuard.markAsSuccess(); - updateProjectData(bc); - } - - { - TraceTimer parsingDoneTimer(" parsing finished signal"); - m_parseGuard = {}; - } -} - QStringList CMakeProject::filesGeneratedFrom(const QString &sourceFile) const { if (!activeTarget()) @@ -538,69 +233,9 @@ MakeInstallCommand CMakeProject::makeInstallCommand(const Target *target, return cmd; } -bool CMakeProject::mustUpdateCMakeStateBeforeBuild() +bool CMakeProject::mustUpdateCMakeStateBeforeBuild() const { - return m_delayedParsingTimer.isActive(); -} - -void CMakeProject::checkAndReportError(QString &errorMessage) const -{ - if (!errorMessage.isEmpty()) { - reportError(errorMessage); - errorMessage.clear(); - } -} - -QList CMakeProject::findExtraCompilers() const -{ - qCDebug(cmakeProjectLog) << "Finding Extra Compilers: start."; - - QList extraCompilers; - const QList factories = ExtraCompilerFactory::extraCompilerFactories(); - - qCDebug(cmakeProjectLog) << "Finding Extra Compilers: Got factories."; - - const QSet fileExtensions = Utils::transform(factories, - &ExtraCompilerFactory::sourceTag); - - qCDebug(cmakeProjectLog) << "Finding Extra Compilers: Got file extensions:" << fileExtensions; - - // Find all files generated by any of the extra compilers, in a rather crude way. - const FilePathList fileList = files([&fileExtensions](const Node *n) { - if (!SourceFiles(n)) - return false; - const QString fp = n->filePath().toString(); - const int pos = fp.lastIndexOf('.'); - return pos >= 0 && fileExtensions.contains(fp.mid(pos + 1)); - }); - - qCDebug(cmakeProjectLog) << "Finding Extra Compilers: Got list of files to check."; - - // Generate the necessary information: - for (const FilePath &file : fileList) { - qCDebug(cmakeProjectLog) << "Finding Extra Compilers: Processing" << file.toUserOutput(); - ExtraCompilerFactory *factory = Utils::findOrDefault(factories, - [&file](const ExtraCompilerFactory *f) { - return file.endsWith( - '.' + f->sourceTag()); - }); - QTC_ASSERT(factory, continue); - - QStringList generated = filesGeneratedFrom(file.toString()); - qCDebug(cmakeProjectLog) << "Finding Extra Compilers: generated files:" << generated; - if (generated.isEmpty()) - continue; - - const FilePathList fileNames - = transform(generated, - [](const QString &s) { return FilePath::fromString(s); }); - extraCompilers.append(factory->create(this, file, fileNames)); - qCDebug(cmakeProjectLog) << "Finding Extra Compilers: done with" << file.toUserOutput(); - } - - qCDebug(cmakeProjectLog) << "Finding Extra Compilers: done."; - - return extraCompilers; + return buildSystem()->isWaitingForParse(); } } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index 32e1a12628c..75a8ed81c41 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -28,6 +28,7 @@ #include "cmake_global.h" #include "builddirmanager.h" +#include "cmakebuildsystem.h" #include "cmakebuildtarget.h" #include "cmakeprojectimporter.h" @@ -49,6 +50,8 @@ namespace ProjectExplorer { class FileNode; } namespace CMakeProjectManager { +class BuildSystem; + namespace Internal { class CMakeBuildConfiguration; class CMakeBuildSettingsWidget; @@ -63,6 +66,8 @@ public: explicit CMakeProject(const Utils::FilePath &filename); ~CMakeProject() final; + BuildSystem *buildSystem() const { return m_buildsystem.get(); } + ProjectExplorer::Tasks projectIssues(const ProjectExplorer::Kit *k) const final; void runCMake(); @@ -75,53 +80,26 @@ public: bool persistCMakeState(); void clearCMakeCache(); - bool mustUpdateCMakeStateBeforeBuild(); - - void checkAndReportError(QString &errorMessage) const; - void reportError(const QString &errorMessage) const; - - void requestReparse(int reparseParameters); + bool mustUpdateCMakeStateBeforeBuild() const; protected: bool setupTarget(ProjectExplorer::Target *t) final; private: - void startParsing(int reparseParameters); - - void handleTreeScanningFinished(); - void handleParsingSuccess(Internal::CMakeBuildConfiguration *bc); - void handleParsingError(Internal::CMakeBuildConfiguration *bc); - void combineScanAndParse(Internal::CMakeBuildConfiguration *bc); - void updateProjectData(Internal::CMakeBuildConfiguration *bc); - void updateQmlJSCodeModel(Internal::CMakeBuildConfiguration *bc); - - QList findExtraCompilers() const; QStringList filesGeneratedFrom(const QString &sourceFile) const final; ProjectExplorer::DeploymentKnowledge deploymentKnowledge() const override; ProjectExplorer::MakeInstallCommand makeInstallCommand(const ProjectExplorer::Target *target, const QString &installRoot) override; - CppTools::CppProjectUpdater *m_cppCodeModelUpdater = nullptr; - QList m_extraCompilers; - - ProjectExplorer::TreeScanner m_treeScanner; - - bool m_waitingForScan = false; - bool m_waitingForParse = false; - bool m_combinedScanAndParseResult = false; - - QHash m_mimeBinaryCache; - QList m_allFiles; mutable std::unique_ptr m_projectImporter; - QTimer m_delayedParsingTimer; - int m_delayedParsingParameters = 0; + std::unique_ptr m_buildsystem; - ParseGuard m_parseGuard; + // friend class Internal::CMakeBuildConfiguration; + // friend class Internal::CMakeBuildSettingsWidget; - friend class Internal::CMakeBuildConfiguration; - friend class Internal::CMakeBuildSettingsWidget; + friend class CMakeBuildSystem; }; } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro index 4a3cde818af..c1b26eabd80 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro @@ -5,6 +5,7 @@ HEADERS = builddirmanager.h \ builddirparameters.h \ builddirreader.h \ cmakebuildstep.h \ + cmakebuildsystem.h \ cmakebuildtarget.h \ cmakeconfigitem.h \ cmakeprocess.h \ @@ -45,6 +46,7 @@ SOURCES = builddirmanager.cpp \ builddirparameters.cpp \ builddirreader.cpp \ cmakebuildstep.cpp \ + cmakebuildsystem.cpp \ cmakeconfigitem.cpp \ cmakeprocess.cpp \ cmakeproject.cpp \ diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 3edac535604..6b002345ed7 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -32,6 +32,8 @@ QtcPlugin { "cmakebuildsettingswidget.h", "cmakebuildstep.cpp", "cmakebuildstep.h", + "cmakebuildsystem.cpp", + "cmakebuildsystem.h", "cmakebuildtarget.h", "cmakecbpparser.cpp", "cmakecbpparser.h", diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index 091b5b163fd..4dfe1b2adc1 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -249,6 +249,9 @@ public: friend class Project; }; + // FIXME: Make this private and the BuildSystem a friend + ParseGuard guardParsingRun() { return ParseGuard(this); } + signals: void displayNameChanged(); void fileListChanged(); @@ -280,8 +283,6 @@ signals: void rootProjectDirectoryChanged(); protected: - ParseGuard guardParsingRun() { return ParseGuard(this); } - virtual RestoreResult fromMap(const QVariantMap &map, QString *errorMessage); void createTargetFromMap(const QVariantMap &map, int index); virtual bool setupTarget(Target *t); @@ -323,6 +324,7 @@ private: void handleSubTreeChanged(FolderNode *node); void setActiveTarget(Target *target); + ProjectPrivate *d; friend class ContainerNode;