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;