From 59473389b90b7f311c72bd90a16a8813181a226f Mon Sep 17 00:00:00 2001 From: Tobias Hunger Date: Fri, 24 Apr 2020 10:29:43 +0200 Subject: [PATCH] CMake: Remove BuildDirManager The BuildDirManager was used to switch between different BuildDirReaders. Now that only the FileApiReader is left, that infrastructure is no longer needed. Change-Id: I2d339a3407bb633cff6a8f7502b7b09094f63fef Reviewed-by: Alessandro Portale --- .../cmakeprojectmanager/CMakeLists.txt | 1 - .../cmakeprojectmanager/builddirmanager.cpp | 567 ------------------ .../cmakeprojectmanager/builddirmanager.h | 134 ----- .../cmakebuildconfiguration.cpp | 1 - .../cmakebuildconfiguration.h | 2 - .../cmakeprojectmanager/cmakebuildsystem.cpp | 481 ++++++++++++--- .../cmakeprojectmanager/cmakebuildsystem.h | 48 +- .../cmakeprojectmanager/cmakebuildsystem.md | 43 +- .../cmakeprojectimporter.cpp | 4 +- .../cmakeprojectmanager.pro | 6 +- .../cmakeprojectmanager.qbs | 2 - .../cmakeprojectmanager/fileapireader.cpp | 2 + 12 files changed, 475 insertions(+), 816 deletions(-) delete mode 100644 src/plugins/cmakeprojectmanager/builddirmanager.cpp delete mode 100644 src/plugins/cmakeprojectmanager/builddirmanager.h diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index 32c800941ed..14a17e17b12 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -3,7 +3,6 @@ add_qtc_plugin(CMakeProjectManager PLUGIN_DEPENDS Core CppTools ProjectExplorer TextEditor QtSupport PLUGIN_RECOMMENDS Designer SOURCES - builddirmanager.cpp builddirmanager.h builddirparameters.cpp builddirparameters.h cmake_global.h cmakeautocompleter.cpp cmakeautocompleter.h diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.cpp b/src/plugins/cmakeprojectmanager/builddirmanager.cpp deleted file mode 100644 index 67068b9f621..00000000000 --- a/src/plugins/cmakeprojectmanager/builddirmanager.cpp +++ /dev/null @@ -1,567 +0,0 @@ -/**************************************************************************** -** -** 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 "builddirmanager.h" - -#include "cmakebuildconfiguration.h" -#include "cmakebuildstep.h" -#include "cmakebuildsystem.h" -#include "cmakeprojectnodes.h" - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include - -using namespace ProjectExplorer; -using namespace Utils; - -namespace CMakeProjectManager { -namespace Internal { - -static Q_LOGGING_CATEGORY(cmakeBuildDirManagerLog, "qtc.cmake.builddirmanager", QtWarningMsg); - -// -------------------------------------------------------------------- -// BuildDirManager: -// -------------------------------------------------------------------- - -BuildDirManager::BuildDirManager(CMakeBuildSystem *buildSystem) - : m_buildSystem(buildSystem) -{ - assert(buildSystem); -} - -BuildDirManager::~BuildDirManager() = default; - -FilePath BuildDirManager::workDirectory(const BuildDirParameters ¶meters) const -{ - const Utils::FilePath bdir = parameters.buildDirectory; - const CMakeTool *cmake = parameters.cmakeTool(); - if (bdir.exists()) { - m_buildDirToTempDir.erase(bdir); - return bdir; - } - - if (cmake && cmake->autoCreateBuildDirectory()) { - if (!m_buildSystem->buildConfiguration()->createBuildDirectory()) - emitErrorOccurred( - tr("Failed to create build directory \"%1\".").arg(bdir.toUserOutput())); - return bdir; - } - - auto tmpDirIt = m_buildDirToTempDir.find(bdir); - if (tmpDirIt == m_buildDirToTempDir.end()) { - auto ret = m_buildDirToTempDir.emplace(std::make_pair(bdir, std::make_unique("qtc-cmake-XXXXXXXX"))); - QTC_ASSERT(ret.second, return bdir); - tmpDirIt = ret.first; - - if (!tmpDirIt->second->isValid()) { - emitErrorOccurred(tr("Failed to create temporary directory \"%1\".") - .arg(QDir::toNativeSeparators(tmpDirIt->second->path()))); - return bdir; - } - } - return Utils::FilePath::fromString(tmpDirIt->second->path()); -} - -void BuildDirManager::updateReparseParameters(const int parameters) -{ - m_reparseParameters |= parameters; -} - -int BuildDirManager::takeReparseParameters() -{ - int result = m_reparseParameters; - m_reparseParameters = REPARSE_DEFAULT; - return result; -} - -void BuildDirManager::emitDataAvailable() -{ - if (!isParsing()) - emit dataAvailable(); -} - -void BuildDirManager::emitErrorOccurred(const QString &message) const -{ - m_isHandlingError = true; - emit errorOccurred(message); - m_isHandlingError = false; -} - -void BuildDirManager::emitReparseRequest() const -{ - if (m_reparseParameters & REPARSE_URGENT) { - qCDebug(cmakeBuildDirManagerLog) << "emitting requestReparse"; - emit requestReparse(); - } else { - qCDebug(cmakeBuildDirManagerLog) << "emitting requestDelayedReparse"; - emit requestDelayedReparse(); - } -} - -bool BuildDirManager::hasConfigChanged() -{ - checkConfiguration(); - - const QByteArray GENERATOR_KEY = "CMAKE_GENERATOR"; - const QByteArray EXTRA_GENERATOR_KEY = "CMAKE_EXTRA_GENERATOR"; - const QByteArray CMAKE_COMMAND_KEY = "CMAKE_COMMAND"; - const QByteArray CMAKE_C_COMPILER_KEY = "CMAKE_C_COMPILER"; - const QByteArray CMAKE_CXX_COMPILER_KEY = "CMAKE_CXX_COMPILER"; - - const QByteArrayList criticalKeys - = {GENERATOR_KEY, CMAKE_COMMAND_KEY, CMAKE_C_COMPILER_KEY, CMAKE_CXX_COMPILER_KEY}; - - QString errorMessage; - const CMakeConfig currentConfig = takeCMakeConfiguration(errorMessage); - if (!errorMessage.isEmpty()) - return false; - - const CMakeTool *tool = m_parameters.cmakeTool(); - QTC_ASSERT(tool, return false); // No cmake... we should not have ended up here in the first place - const QString extraKitGenerator = m_parameters.extraGenerator; - const QString mainKitGenerator = m_parameters.generator; - CMakeConfig targetConfig = m_parameters.configuration; - targetConfig.append(CMakeConfigItem(GENERATOR_KEY, CMakeConfigItem::INTERNAL, - QByteArray(), mainKitGenerator.toUtf8())); - if (!extraKitGenerator.isEmpty()) - targetConfig.append(CMakeConfigItem(EXTRA_GENERATOR_KEY, CMakeConfigItem::INTERNAL, - QByteArray(), extraKitGenerator.toUtf8())); - targetConfig.append(CMakeConfigItem(CMAKE_COMMAND_KEY, CMakeConfigItem::INTERNAL, - QByteArray(), tool->cmakeExecutable().toUserOutput().toUtf8())); - Utils::sort(targetConfig, CMakeConfigItem::sortOperator()); - - bool mustReparse = false; - auto ccit = currentConfig.constBegin(); - auto kcit = targetConfig.constBegin(); - - while (ccit != currentConfig.constEnd() && kcit != targetConfig.constEnd()) { - if (ccit->key == kcit->key) { - if (ccit->value != kcit->value) { - if (criticalKeys.contains(kcit->key)) { - clearCache(); - return false; // no need to trigger a new reader, clearCache will do that - } - mustReparse = true; - } - ++ccit; - ++kcit; - } else { - if (ccit->key < kcit->key) { - ++ccit; - } else { - ++kcit; - mustReparse = true; - } - } - } - - // If we have keys that do not exist yet, then reparse. - // - // The critical keys *must* be set in cmake configuration, so those were already - // handled above. - return mustReparse || kcit != targetConfig.constEnd(); -} - -void BuildDirManager::writeConfigurationIntoBuildDirectory(const Utils::MacroExpander *expander) -{ - QTC_ASSERT(expander, return ); - - const FilePath buildDir = workDirectory(m_parameters); - QTC_ASSERT(buildDir.exists(), return ); - - const FilePath settingsFile = buildDir.pathAppended("qtcsettings.cmake"); - - QByteArray contents; - contents.append("# This file is managed by Qt Creator, do not edit!\n\n"); - contents.append( - transform(m_parameters.configuration, - [expander](const CMakeConfigItem &item) { return item.toCMakeSetLine(expander); }) - .join('\n') - .toUtf8()); - - QFile file(settingsFile.toString()); - QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return ); - file.write(contents); -} - -bool BuildDirManager::isParsing() const -{ - return m_reader && m_reader->isParsing(); -} - -void BuildDirManager::stopParsingAndClearState() -{ - qCDebug(cmakeBuildDirManagerLog) << "stopping parsing run!"; - m_reader.reset(); -} - -void BuildDirManager::setParametersAndRequestParse(const BuildDirParameters ¶meters, - const int reparseParameters) -{ - qCDebug(cmakeBuildDirManagerLog) << "setting parameters and requesting reparse"; - if (!parameters.cmakeTool()) { - TaskHub::addTask(BuildSystemTask(Task::Error, tr( - "The kit needs to define a CMake tool to parse this project."))); - return; - } - QTC_ASSERT(parameters.isValid(), return ); - - m_parameters = parameters; - m_parameters.workDirectory = workDirectory(parameters); - updateReparseParameters(reparseParameters); - - QTC_CHECK(!m_reader); // The parsing should have stopped at this time already! - m_reader = std::make_unique(); - - connect(m_reader.get(), - &FileApiReader::configurationStarted, - this, - &BuildDirManager::parsingStarted); - connect(m_reader.get(), - &FileApiReader::dataAvailable, - this, - &BuildDirManager::emitDataAvailable); - connect(m_reader.get(), - &FileApiReader::errorOccurred, - this, - &BuildDirManager::emitErrorOccurred); - connect(m_reader.get(), &FileApiReader::dirty, this, &BuildDirManager::becameDirty); - - m_reader->setParameters(m_parameters); - emitReparseRequest(); -} - -CMakeBuildSystem *BuildDirManager::buildSystem() const -{ - return m_buildSystem; -} - -FilePath BuildDirManager::buildDirectory() const -{ - return m_parameters.buildDirectory; -} - -void BuildDirManager::becameDirty() -{ - qCDebug(cmakeBuildDirManagerLog) << "BuildDirManager: becameDirty was triggered."; - if (isParsing() || !buildSystem()) - return; - - const CMakeTool *tool = m_parameters.cmakeTool(); - if (!tool->isAutoRun()) - return; - - updateReparseParameters(REPARSE_CHECK_CONFIGURATION | REPARSE_SCAN); - emit requestReparse(); -} - -void BuildDirManager::resetData() -{ - m_reader.reset(); -} - -bool BuildDirManager::persistCMakeState() -{ - QTC_ASSERT(m_parameters.isValid(), return false); - - if (m_parameters.workDirectory == m_parameters.buildDirectory) - return false; - - if (!m_buildSystem->buildConfiguration()->createBuildDirectory()) - return false; - - BuildDirParameters newParameters = m_parameters; - newParameters.workDirectory.clear(); - qCDebug(cmakeBuildDirManagerLog) << "Requesting parse due to persisting CMake State"; - setParametersAndRequestParse(newParameters, - REPARSE_URGENT | REPARSE_FORCE_CMAKE_RUN - | REPARSE_FORCE_CONFIGURATION | REPARSE_CHECK_CONFIGURATION); - return true; -} - -void BuildDirManager::requestFilesystemScan() -{ - updateReparseParameters(REPARSE_SCAN); -} - -bool BuildDirManager::isFilesystemScanRequested() const -{ - return m_reparseParameters & REPARSE_SCAN; -} - -void BuildDirManager::parse() -{ - qCDebug(cmakeBuildDirManagerLog) << "parsing!"; - QTC_ASSERT(m_parameters.isValid(), return ); - QTC_ASSERT(m_reader, return ); - QTC_ASSERT(!m_reader->isParsing(), return ); - - m_reader->stop(); - - TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); - - int reparseParameters = takeReparseParameters(); - - qCDebug(cmakeBuildDirManagerLog) - << "Parse called with flags:" << flagsString(reparseParameters); - - const QString cache = m_parameters.workDirectory.pathAppended("CMakeCache.txt").toString(); - if (!QFileInfo::exists(cache)) { - reparseParameters |= REPARSE_FORCE_CONFIGURATION | REPARSE_FORCE_CMAKE_RUN; - qCDebug(cmakeBuildDirManagerLog) - << "No" << cache << "file found, new flags:" << flagsString(reparseParameters); - } else if (reparseParameters & REPARSE_CHECK_CONFIGURATION) { - if (checkConfiguration()) { - reparseParameters |= REPARSE_FORCE_CONFIGURATION | REPARSE_FORCE_CMAKE_RUN; - qCDebug(cmakeBuildDirManagerLog) - << "Config check triggered flags change:" << flagsString(reparseParameters); - } - } - - writeConfigurationIntoBuildDirectory(m_parameters.expander); - - qCDebug(cmakeBuildDirManagerLog) << "Asking reader to parse"; - m_reader->parse(reparseParameters & REPARSE_FORCE_CMAKE_RUN, - reparseParameters & REPARSE_FORCE_CONFIGURATION); -} - -QSet BuildDirManager::projectFilesToWatch() const -{ - QTC_ASSERT(!m_isHandlingError, return {}); - QTC_ASSERT(m_reader, return {}); - - Utils::FilePath sourceDir = m_parameters.sourceDirectory; - Utils::FilePath buildDir = m_parameters.workDirectory; - - return Utils::filtered(m_reader->projectFilesToWatch(), - [&sourceDir, - &buildDir](const Utils::FilePath &p) { - return p.isChildOf(sourceDir) - || p.isChildOf(buildDir); - }); -} - -std::unique_ptr BuildDirManager::generateProjectTree( - const QList &allFiles, QString &errorMessage) const -{ - QTC_ASSERT(!m_isHandlingError, return {}); - QTC_ASSERT(m_reader, return {}); - - return m_reader->generateProjectTree(allFiles, errorMessage); -} - -RawProjectParts BuildDirManager::createRawProjectParts(QString &errorMessage) const -{ - QTC_ASSERT(!m_isHandlingError, return {}); - QTC_ASSERT(m_reader, return {}); - return m_reader->createRawProjectParts(errorMessage); -} - -void BuildDirManager::clearCache() -{ - QTC_ASSERT(m_parameters.isValid(), return); - QTC_ASSERT(!m_isHandlingError, return); - - const FilePath cmakeCache = m_parameters.workDirectory / "CMakeCache.txt"; - const FilePath cmakeFiles = m_parameters.workDirectory / "CMakeFiles"; - - const bool mustCleanUp = cmakeCache.exists() || cmakeFiles.exists(); - if (!mustCleanUp) - return; - - Utils::FileUtils::removeRecursively(cmakeCache); - Utils::FileUtils::removeRecursively(cmakeFiles); - - m_reader.reset(); -} - -static CMakeBuildTarget utilityTarget(const QString &title, const BuildDirManager *bdm) -{ - CMakeBuildTarget target; - - target.title = title; - target.targetType = UtilityType; - target.workingDirectory = bdm->buildDirectory(); - target.sourceDirectory = bdm->buildSystem()->project()->projectDirectory(); - - return target; -} - -QList BuildDirManager::takeBuildTargets(QString &errorMessage) const -{ - QList result = { utilityTarget(CMakeBuildStep::allTarget(), this), - utilityTarget(CMakeBuildStep::cleanTarget(), this), - utilityTarget(CMakeBuildStep::installTarget(), this), - utilityTarget(CMakeBuildStep::testTarget(), this) }; - QTC_ASSERT(!m_isHandlingError, return result); - - if (m_reader) { - QList readerTargets - = Utils::filtered(m_reader->takeBuildTargets(errorMessage), - [](const CMakeBuildTarget &bt) { - return bt.title != CMakeBuildStep::allTarget() - && bt.title != CMakeBuildStep::cleanTarget() - && bt.title != CMakeBuildStep::installTarget() - && bt.title != CMakeBuildStep::testTarget(); - }); - - // Guess at the target definition position when no details are known - for (CMakeBuildTarget &t : readerTargets) { - if (t.backtrace.isEmpty()) { - t.backtrace.append( - FolderNode::LocationInfo(tr("CMakeLists.txt in source directory"), - t.sourceDirectory.pathAppended("CMakeLists.txt"))); - } - } - result.append(readerTargets); - } - return result; -} - -CMakeConfig BuildDirManager::takeCMakeConfiguration(QString &errorMessage) const -{ - if (!m_reader) - return CMakeConfig(); - - CMakeConfig result = m_reader->takeParsedConfiguration(errorMessage); - for (auto &ci : result) - ci.inCMakeCache = true; - - return result; -} - -CMakeConfig BuildDirManager::parseCMakeConfiguration(const Utils::FilePath &cacheFile, - QString *errorMessage) -{ - if (!cacheFile.exists()) { - if (errorMessage) - *errorMessage = tr("CMakeCache.txt file not found."); - return { }; - } - CMakeConfig result = CMakeConfigItem::itemsFromFile(cacheFile, errorMessage); - if (!errorMessage->isEmpty()) - return { }; - return result; -} - -QString BuildDirManager::flagsString(int reparseFlags) -{ - QString result; - if (reparseFlags == REPARSE_DEFAULT) { - result = ""; - } else { - if (reparseFlags & REPARSE_URGENT) - result += " URGENT"; - if (reparseFlags & REPARSE_FORCE_CMAKE_RUN) - result += " FORCE_CMAKE_RUN"; - if (reparseFlags & REPARSE_FORCE_CONFIGURATION) - result += " FORCE_CONFIG"; - if (reparseFlags & REPARSE_CHECK_CONFIGURATION) - result += " CHECK_CONFIG"; - if (reparseFlags & REPARSE_SCAN) - result += " SCAN"; - } - return result.trimmed(); -} - -bool BuildDirManager::checkConfiguration() -{ - if (m_parameters.workDirectory != m_parameters.buildDirectory) // always throw away changes in the tmpdir! - return false; - - const CMakeConfig cache = m_buildSystem->cmakeBuildConfiguration()->configurationFromCMake(); - if (cache.isEmpty()) - return false; // No cache file yet. - - CMakeConfig newConfig; - QHash> changedKeys; - foreach (const CMakeConfigItem &projectItem, m_parameters.configuration) { - const QString projectKey = QString::fromUtf8(projectItem.key); - const QString projectValue = projectItem.expandedValue(m_parameters.expander); - const CMakeConfigItem &cmakeItem - = Utils::findOrDefault(cache, [&projectItem](const CMakeConfigItem &i) { return i.key == projectItem.key; }); - const QString iCacheValue = QString::fromUtf8(cmakeItem.value); - if (cmakeItem.isNull()) { - changedKeys.insert(projectKey, qMakePair(tr(""), projectValue)); - } else if (iCacheValue != projectValue) { - changedKeys.insert(projectKey, qMakePair(iCacheValue, projectValue)); - newConfig.append(cmakeItem); - } else { - newConfig.append(projectItem); - } - } - - if (!changedKeys.isEmpty()) { - QStringList keyList = changedKeys.keys(); - Utils::sort(keyList); - QString table = QString::fromLatin1("") - .arg(tr("Key")) - .arg(tr("%1 Project").arg(Core::Constants::IDE_DISPLAY_NAME)) - .arg(tr("Changed value")); - foreach (const QString &k, keyList) { - const QPair data = changedKeys.value(k); - table += QString::fromLatin1("\n") - .arg(k) - .arg(data.second.toHtmlEscaped()) - .arg(data.first.toHtmlEscaped()); - } - table += QLatin1String("\n
%1%2%3
%1%2%3
"); - - QPointer box = new QMessageBox(Core::ICore::mainWindow()); - box->setText(tr("The project has been changed outside of %1.") - .arg(Core::Constants::IDE_DISPLAY_NAME)); - box->setInformativeText(table); - auto *defaultButton = box->addButton(tr("Discard External Changes"), - QMessageBox::RejectRole); - auto *applyButton = box->addButton(tr("Adapt %1 Project to Changes") - .arg(Core::Constants::IDE_DISPLAY_NAME), - QMessageBox::ApplyRole); - box->setDefaultButton(defaultButton); - - box->exec(); - if (box->clickedButton() == applyButton) { - m_parameters.configuration = newConfig; - QSignalBlocker blocker(m_buildSystem->buildConfiguration()); - m_buildSystem->cmakeBuildConfiguration()->setConfigurationForCMake(newConfig); - return false; - } else if (box->clickedButton() == defaultButton) - return true; - } - return false; -} - -} // namespace Internal -} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.h b/src/plugins/cmakeprojectmanager/builddirmanager.h deleted file mode 100644 index 8ed72f091ca..00000000000 --- a/src/plugins/cmakeprojectmanager/builddirmanager.h +++ /dev/null @@ -1,134 +0,0 @@ -/**************************************************************************** -** -** 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 "builddirparameters.h" -#include "cmakebuildtarget.h" -#include "fileapireader.h" - -#include - -#include -#include - -#include -#include - -#include -#include - -namespace ProjectExplorer { class FileNode; } - -namespace CMakeProjectManager { -namespace Internal { - -class CMakeBuildConfiguration; -class CMakeBuildSystem; -class CMakeProjectNode; - -class BuildDirManager final : public QObject -{ - Q_OBJECT - -public: - enum ReparseParameters { - REPARSE_DEFAULT = 0, // Nothing special:-) - REPARSE_FORCE_CMAKE_RUN = (1 << 0), // Force cmake to run - REPARSE_FORCE_CONFIGURATION = (1 << 1), // Force configuration arguments to cmake - REPARSE_CHECK_CONFIGURATION = (1 << 2), // Check for on-disk config and QtC config diff - REPARSE_SCAN = (1 << 3), // Run filesystem scan - REPARSE_URGENT = (1 << 4), // Do not delay the parser run by 1s - }; - - static QString flagsString(int reparseFlags); - - explicit BuildDirManager(CMakeBuildSystem *buildSystem); - ~BuildDirManager() final; - - bool isParsing() const; - - void stopParsingAndClearState(); - - void setParametersAndRequestParse(const BuildDirParameters ¶meters, - const int reparseOptions); - // nullptr if the BC is not active anymore! - CMakeBuildSystem *buildSystem() const; - Utils::FilePath buildDirectory() const; - - void clearCache(); - - void resetData(); - bool persistCMakeState(); - - void requestFilesystemScan(); - bool isFilesystemScanRequested() const; - void parse(); - - QSet projectFilesToWatch() const; - std::unique_ptr generateProjectTree(const QList &allFiles, - QString &errorMessage) const; - ProjectExplorer::RawProjectParts createRawProjectParts(QString &errorMessage) const; - - QList takeBuildTargets(QString &errorMessage) const; - CMakeConfig takeCMakeConfiguration(QString &errorMessage) const; - - static CMakeConfig parseCMakeConfiguration(const Utils::FilePath &cacheFile, - QString *errorMessage); - -signals: - void requestReparse() const; - void requestDelayedReparse() const; - void parsingStarted() const; - void dataAvailable() const; - void errorOccurred(const QString &err) const; - -private: - void updateReparseParameters(const int parameters); - int takeReparseParameters(); - - void emitDataAvailable(); - void emitErrorOccurred(const QString &message) const; - void emitReparseRequest() const; - bool checkConfiguration(); - - Utils::FilePath workDirectory(const BuildDirParameters ¶meters) const; - - bool hasConfigChanged(); - - void writeConfigurationIntoBuildDirectory(const Utils::MacroExpander *expander); - - void becameDirty(); - - BuildDirParameters m_parameters; - int m_reparseParameters; - CMakeBuildSystem *m_buildSystem = nullptr; - mutable std::unordered_map> m_buildDirToTempDir; - mutable std::unique_ptr m_reader; - mutable bool m_isHandlingError = false; -}; - -} // namespace Internal -} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 3c94b74a160..8221fc1be90 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -25,7 +25,6 @@ #include "cmakebuildconfiguration.h" -#include "builddirmanager.h" #include "cmakebuildsettingswidget.h" #include "cmakebuildstep.h" #include "cmakebuildsystem.h" diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h index ee34640e913..c3d0f06254f 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h @@ -35,7 +35,6 @@ class CMakeProject; namespace Internal { -class BuildDirManager; class CMakeBuildSystem; class CMakeBuildSettingsWidget; @@ -97,7 +96,6 @@ private: friend class CMakeBuildSettingsWidget; friend class CMakeBuildSystem; friend class CMakeProject; - friend class BuildDirManager; }; class CMakeProjectImporter; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 966ebabd0a4..58b0c11ba9a 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -25,6 +25,7 @@ #include "cmakebuildsystem.h" +#include "builddirparameters.h" #include "cmakebuildconfiguration.h" #include "cmakekitinformation.h" #include "cmakeprojectconstants.h" @@ -42,10 +43,12 @@ #include #include #include +#include #include - #include +#include + #include #include #include @@ -55,6 +58,7 @@ #include #include #include +#include using namespace ProjectExplorer; using namespace Utils; @@ -138,7 +142,6 @@ static Q_LOGGING_CATEGORY(cmakeBuildSystemLog, "qtc.cmake.buildsystem", QtWarnin CMakeBuildSystem::CMakeBuildSystem(CMakeBuildConfiguration *bc) : BuildSystem(bc) - , m_buildDirManager(this) , m_cppCodeModelUpdater(new CppTools::CppProjectUpdater) { // TreeScanner: @@ -177,26 +180,17 @@ CMakeBuildSystem::CMakeBuildSystem(CMakeBuildConfiguration *bc) return type; }); - // BuildDirManager: - connect(&m_buildDirManager, &BuildDirManager::requestReparse, this, [this] { - if (cmakeBuildConfiguration()->isActive()) - requestParse(); - }); - connect(&m_buildDirManager, &BuildDirManager::requestDelayedReparse, this, [this] { - if (cmakeBuildConfiguration()->isActive()) - requestDelayedParse(); - }); - - connect(&m_buildDirManager, &BuildDirManager::dataAvailable, - this, &CMakeBuildSystem::handleParsingSucceeded); - - connect(&m_buildDirManager, &BuildDirManager::errorOccurred, - this, &CMakeBuildSystem::handleParsingFailed); - - connect(&m_buildDirManager, &BuildDirManager::parsingStarted, this, [this]() { + connect(&m_reader, &FileApiReader::configurationStarted, this, [this]() { cmakeBuildConfiguration()->clearError(CMakeBuildConfiguration::ForceEnabledChanged::True); }); + connect(&m_reader, + &FileApiReader::dataAvailable, + this, + &CMakeBuildSystem::handleParsingSucceeded); + connect(&m_reader, &FileApiReader::errorOccurred, this, &CMakeBuildSystem::handleParsingFailed); + connect(&m_reader, &FileApiReader::dirty, this, &CMakeBuildSystem::becameDirty); + connect(SessionManager::instance(), &SessionManager::projectAdded, this, @@ -225,22 +219,25 @@ void CMakeBuildSystem::triggerParsing() // This can legitimately trigger if e.g. Build->Run CMake // is selected while this here is already running. - // FIXME: Instead of aborting the second run here we could try to - // cancel the first one in the Build->Run CMake handler and then - // continue to here normally. This here could then be an Assert. - return; + // Stop old parse run and keep that ParseGuard! + stopParsingAndClearState(); + } else { + // Use new ParseGuard + m_currentGuard = std::move(guard); } + QTC_ASSERT(!m_reader.isParsing(), return ); - m_currentGuard = std::move(guard); + qCDebug(cmakeBuildSystemLog) << "ParseGuard acquired."; if (m_allFiles.isEmpty()) - m_buildDirManager.requestFilesystemScan(); + updateReparseParameters(REPARSE_SCAN); - m_waitingForScan = m_buildDirManager.isFilesystemScanRequested(); + m_waitingForScan = (m_reparseParameters | REPARSE_SCAN) != 0; m_waitingForParse = true; m_combinedScanAndParseResult = true; if (m_waitingForScan) { + qCDebug(cmakeBuildSystemLog) << "Starting TreeScanner"; QTC_CHECK(m_treeScanner.isFinished()); m_treeScanner.asyncScanForFiles(projectDirectory()); Core::ProgressManager::addTask(m_treeScanner.future(), @@ -249,7 +246,34 @@ void CMakeBuildSystem::triggerParsing() "CMake.Scan.Tree"); } - m_buildDirManager.parse(); + QTC_ASSERT(m_parameters.isValid(), return ); + + TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); + + int reparseParameters = takeReparseParameters(); + + qCDebug(cmakeBuildSystemLog) << "Parse called with flags:" + << reparseParametersString(reparseParameters); + + const QString cache = m_parameters.workDirectory.pathAppended("CMakeCache.txt").toString(); + if (!QFileInfo::exists(cache)) { + reparseParameters |= REPARSE_FORCE_CONFIGURATION | REPARSE_FORCE_CMAKE_RUN; + qCDebug(cmakeBuildSystemLog) + << "No" << cache + << "file found, new flags:" << reparseParametersString(reparseParameters); + } else if (reparseParameters & REPARSE_CHECK_CONFIGURATION) { + if (checkConfiguration()) { + reparseParameters |= REPARSE_FORCE_CONFIGURATION | REPARSE_FORCE_CMAKE_RUN; + qCDebug(cmakeBuildSystemLog) << "Config check triggered flags change:" + << reparseParametersString(reparseParameters); + } + } + + writeConfigurationIntoBuildDirectory(m_parameters.expander); + + qCDebug(cmakeBuildSystemLog) << "Asking reader to parse"; + m_reader.parse(reparseParameters & REPARSE_FORCE_CMAKE_RUN, + reparseParameters & REPARSE_FORCE_CONFIGURATION); } bool CMakeBuildSystem::supportsAction(Node *context, ProjectAction action, const Node *node) const @@ -312,23 +336,89 @@ QStringList CMakeBuildSystem::filesGeneratedFrom(const QString &sourceFile) cons return {}; } +QString CMakeBuildSystem::reparseParametersString(int reparseFlags) +{ + QString result; + if (reparseFlags == REPARSE_DEFAULT) { + result = ""; + } else { + if (reparseFlags & REPARSE_URGENT) + result += " URGENT"; + if (reparseFlags & REPARSE_FORCE_CMAKE_RUN) + result += " FORCE_CMAKE_RUN"; + if (reparseFlags & REPARSE_FORCE_CONFIGURATION) + result += " FORCE_CONFIG"; + if (reparseFlags & REPARSE_CHECK_CONFIGURATION) + result += " CHECK_CONFIG"; + if (reparseFlags & REPARSE_SCAN) + result += " SCAN"; + } + return result.trimmed(); +} + +void CMakeBuildSystem::setParametersAndRequestParse(const BuildDirParameters ¶meters, + const int reparseParameters) +{ + qCDebug(cmakeBuildSystemLog) << "setting parameters and requesting reparse"; + if (!parameters.cmakeTool()) { + TaskHub::addTask( + BuildSystemTask(Task::Error, + tr("The kit needs to define a CMake tool to parse this project."))); + return; + } + QTC_ASSERT(parameters.isValid(), return ); + + m_parameters = parameters; + m_parameters.workDirectory = workDirectory(parameters); + updateReparseParameters(reparseParameters); + + m_reader.setParameters(m_parameters); + + if (reparseParameters & REPARSE_URGENT) { + qCDebug(cmakeBuildSystemLog) << "calling requestReparse"; + requestParse(); + } else { + qCDebug(cmakeBuildSystemLog) << "calling requestDelayedReparse"; + requestDelayedParse(); + } +} + +void CMakeBuildSystem::writeConfigurationIntoBuildDirectory(const Utils::MacroExpander *expander) +{ + QTC_ASSERT(expander, return ); + + const FilePath buildDir = workDirectory(m_parameters); + QTC_ASSERT(buildDir.exists(), return ); + + const FilePath settingsFile = buildDir.pathAppended("qtcsettings.cmake"); + + QByteArray contents; + contents.append("# This file is managed by Qt Creator, do not edit!\n\n"); + contents.append( + transform(m_parameters.configuration, + [expander](const CMakeConfigItem &item) { return item.toCMakeSetLine(expander); }) + .join('\n') + .toUtf8()); + + QFile file(settingsFile.toString()); + QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return ); + file.write(contents); +} + void CMakeBuildSystem::runCMake() { BuildDirParameters parameters(cmakeBuildConfiguration()); qCDebug(cmakeBuildSystemLog) << "Requesting parse due \"Run CMake\" command"; - m_buildDirManager.setParametersAndRequestParse(parameters, - BuildDirManager::REPARSE_CHECK_CONFIGURATION - | BuildDirManager::REPARSE_FORCE_CMAKE_RUN - | BuildDirManager::REPARSE_URGENT); + setParametersAndRequestParse(parameters, + REPARSE_CHECK_CONFIGURATION | REPARSE_FORCE_CMAKE_RUN + | REPARSE_URGENT); } void CMakeBuildSystem::runCMakeAndScanProjectTree() { BuildDirParameters parameters(cmakeBuildConfiguration()); qCDebug(cmakeBuildSystemLog) << "Requesting parse due to \"Rescan Project\" command"; - m_buildDirManager.setParametersAndRequestParse(parameters, - BuildDirManager::REPARSE_CHECK_CONFIGURATION - | BuildDirManager::REPARSE_SCAN); + setParametersAndRequestParse(parameters, REPARSE_CHECK_CONFIGURATION | REPARSE_SCAN); } void CMakeBuildSystem::buildCMakeTarget(const QString &buildTarget) @@ -351,19 +441,44 @@ void CMakeBuildSystem::handleTreeScanningFinished() bool CMakeBuildSystem::persistCMakeState() { - return m_buildDirManager.persistCMakeState(); + QTC_ASSERT(m_parameters.isValid(), return false); + + if (m_parameters.workDirectory == m_parameters.buildDirectory) + return false; + + if (!buildConfiguration()->createBuildDirectory()) + return false; + + BuildDirParameters newParameters = m_parameters; + newParameters.workDirectory.clear(); + qCDebug(cmakeBuildSystemLog) << "Requesting parse due to persisting CMake State"; + setParametersAndRequestParse(newParameters, + REPARSE_URGENT | REPARSE_FORCE_CMAKE_RUN + | REPARSE_FORCE_CONFIGURATION | REPARSE_CHECK_CONFIGURATION); + return true; } void CMakeBuildSystem::clearCMakeCache() { - m_buildDirManager.clearCache(); + QTC_ASSERT(m_parameters.isValid(), return ); + QTC_ASSERT(!m_isHandlingError, return ); + + stopParsingAndClearState(); + + const FilePath cmakeCache = m_parameters.workDirectory / "CMakeCache.txt"; + const FilePath cmakeFiles = m_parameters.workDirectory / "CMakeFiles"; + + if (cmakeCache.exists()) + Utils::FileUtils::removeRecursively(cmakeCache); + if (cmakeFiles.exists()) + Utils::FileUtils::removeRecursively(cmakeFiles); } std::unique_ptr CMakeBuildSystem::generateProjectTree(const QList &allFiles) { QString errorMessage; - auto root = m_buildDirManager.generateProjectTree(allFiles, errorMessage); + auto root = m_reader.generateProjectTree(allFiles, errorMessage); checkAndReportError(errorMessage); return root; } @@ -380,6 +495,8 @@ void CMakeBuildSystem::combineScanAndParse() } } + m_reader.resetData(); + m_currentGuard = {}; emitBuildSystemUpdated(); @@ -397,10 +514,9 @@ void CMakeBuildSystem::updateProjectData() { qCDebug(cmakeBuildSystemLog) << "Updating CMake project data"; - QTC_ASSERT(m_treeScanner.isFinished() && !m_buildDirManager.isParsing(), return); + QTC_ASSERT(m_treeScanner.isFinished() && !m_reader.isParsing(), return ); - cmakeBuildConfiguration()->project()->setExtraProjectFiles( - m_buildDirManager.projectFilesToWatch()); + cmakeBuildConfiguration()->project()->setExtraProjectFiles(m_reader.projectFilesToWatch()); CMakeConfig patchedConfig = cmakeBuildConfiguration()->configurationFromCMake(); { @@ -469,7 +585,7 @@ void CMakeBuildSystem::updateProjectData() { QString errorMessage; - RawProjectParts rpps = m_buildDirManager.createRawProjectParts(errorMessage); + RawProjectParts rpps = m_reader.createRawProjectParts(errorMessage); if (!errorMessage.isEmpty()) cmakeBuildConfiguration()->setError(errorMessage); qCDebug(cmakeBuildSystemLog) << "Raw project parts created." << errorMessage; @@ -491,15 +607,13 @@ void CMakeBuildSystem::updateProjectData() emit cmakeBuildConfiguration()->buildTypeChanged(); - m_buildDirManager.resetData(); - qCDebug(cmakeBuildSystemLog) << "All CMake project data up to date."; } void CMakeBuildSystem::handleParsingSucceeded() { if (!cmakeBuildConfiguration()->isActive()) { - m_buildDirManager.stopParsingAndClearState(); + stopParsingAndClearState(); return; } @@ -507,14 +621,16 @@ void CMakeBuildSystem::handleParsingSucceeded() QString errorMessage; { - m_buildTargets = m_buildDirManager.takeBuildTargets(errorMessage); + m_buildTargets = m_reader.takeBuildTargets(errorMessage); checkAndReportError(errorMessage); } { - const CMakeConfig cmakeConfig = m_buildDirManager.takeCMakeConfiguration(errorMessage); - checkAndReportError(errorMessage); + CMakeConfig cmakeConfig = m_reader.takeParsedConfiguration(errorMessage); + for (auto &ci : cmakeConfig) + ci.inCMakeCache = true; cmakeBuildConfiguration()->setConfigurationFromCMake(cmakeConfig); + checkAndReportError(errorMessage); } setApplicationTargets(appTargets()); @@ -531,8 +647,10 @@ void CMakeBuildSystem::handleParsingFailed(const QString &msg) cmakeBuildConfiguration()->setError(msg); QString errorMessage; - cmakeBuildConfiguration()->setConfigurationFromCMake( - m_buildDirManager.takeCMakeConfiguration(errorMessage)); + CMakeConfig cmakeConfig = m_reader.takeParsedConfiguration(errorMessage); + for (auto &ci : cmakeConfig) + ci.inCMakeCache = true; + cmakeBuildConfiguration()->setConfigurationFromCMake(cmakeConfig); // ignore errorMessage here, we already got one. QTC_CHECK(m_waitingForParse); @@ -559,8 +677,8 @@ void CMakeBuildSystem::wireUpConnections(const Project *p) // Build configuration has not changed, but Kit settings might have: // reparse and check the configuration, independent of whether the reader has changed qCDebug(cmakeBuildSystemLog) << "Requesting parse due to kit being updated"; - m_buildDirManager.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), - BuildDirManager::REPARSE_CHECK_CONFIGURATION); + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION); }); // Became active/inactive: @@ -570,11 +688,10 @@ void CMakeBuildSystem::wireUpConnections(const Project *p) // * Check configuration if reader changes due to it not existing yet:-) // * run cmake without configuration arguments if the reader stays qCDebug(cmakeBuildSystemLog) << "Requesting parse due to active target changed"; - m_buildDirManager - .setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), - BuildDirManager::REPARSE_CHECK_CONFIGURATION); + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION); } else { - m_buildDirManager.stopParsingAndClearState(); + stopParsingAndClearState(); } }); connect(target(), &Target::activeBuildConfigurationChanged, this, [this](BuildConfiguration *bc) { @@ -584,11 +701,10 @@ void CMakeBuildSystem::wireUpConnections(const Project *p) // * Check configuration if reader changes due to it not existing yet:-) // * run cmake without configuration arguments if the reader stays qCDebug(cmakeBuildSystemLog) << "Requesting parse due to active BC changed"; - m_buildDirManager - .setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), - BuildDirManager::REPARSE_CHECK_CONFIGURATION); + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION); } else { - m_buildDirManager.stopParsingAndClearState(); + stopParsingAndClearState(); } } }); @@ -600,9 +716,8 @@ void CMakeBuildSystem::wireUpConnections(const Project *p) // * Error out if the reader updates, cannot happen since all BCs share a target/kit. // * run cmake without configuration arguments if the reader stays qCDebug(cmakeBuildSystemLog) << "Requesting parse due to environment change"; - m_buildDirManager - .setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), - BuildDirManager::REPARSE_CHECK_CONFIGURATION); + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION); } }); connect(cmakeBuildConfiguration(), &CMakeBuildConfiguration::buildDirectoryChanged, this, [this]() { @@ -613,9 +728,8 @@ void CMakeBuildSystem::wireUpConnections(const Project *p) // If no configuration exists, then the arguments will get added automatically by // the reader. qCDebug(cmakeBuildSystemLog) << "Requesting parse due to build directory change"; - m_buildDirManager - .setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), - BuildDirManager::REPARSE_CHECK_CONFIGURATION); + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION); } }); connect(cmakeBuildConfiguration(), @@ -628,9 +742,8 @@ void CMakeBuildSystem::wireUpConnections(const Project *p) // * run cmake with configuration arguments if the reader stays qCDebug(cmakeBuildSystemLog) << "Requesting parse due to cmake configuration change"; - m_buildDirManager - .setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), - BuildDirManager::REPARSE_FORCE_CONFIGURATION); + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + CMakeBuildSystem::REPARSE_FORCE_CONFIGURATION); } }); @@ -639,16 +752,224 @@ void CMakeBuildSystem::wireUpConnections(const Project *p) const auto cmake = CMakeKitAspect::cmakeTool(cmakeBuildConfiguration()->target()->kit()); if (cmake && cmake->isAutoRun()) { qCDebug(cmakeBuildSystemLog) << "Requesting parse due to dirty project file"; - m_buildDirManager.setParametersAndRequestParse(BuildDirParameters( - cmakeBuildConfiguration()), - BuildDirManager::REPARSE_DEFAULT); + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + CMakeBuildSystem::REPARSE_DEFAULT); } } }); // Force initial parsing run: - m_buildDirManager.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), - BuildDirManager::REPARSE_CHECK_CONFIGURATION); + if (cmakeBuildConfiguration()->isActive()) + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION); +} + +FilePath CMakeBuildSystem::workDirectory(const BuildDirParameters ¶meters) +{ + const Utils::FilePath bdir = parameters.buildDirectory; + const CMakeTool *cmake = parameters.cmakeTool(); + if (bdir.exists()) { + m_buildDirToTempDir.erase(bdir); + return bdir; + } + + if (cmake && cmake->autoCreateBuildDirectory()) { + if (!cmakeBuildConfiguration()->createBuildDirectory()) + handleParsingFailed( + tr("Failed to create build directory \"%1\".").arg(bdir.toUserOutput())); + return bdir; + } + + auto tmpDirIt = m_buildDirToTempDir.find(bdir); + if (tmpDirIt == m_buildDirToTempDir.end()) { + auto ret = m_buildDirToTempDir.emplace( + std::make_pair(bdir, std::make_unique("qtc-cmake-XXXXXXXX"))); + QTC_ASSERT(ret.second, return bdir); + tmpDirIt = ret.first; + + if (!tmpDirIt->second->isValid()) { + handleParsingFailed(tr("Failed to create temporary directory \"%1\".") + .arg(QDir::toNativeSeparators(tmpDirIt->second->path()))); + return bdir; + } + } + return Utils::FilePath::fromString(tmpDirIt->second->path()); +} + +void CMakeBuildSystem::stopParsingAndClearState() +{ + qCDebug(cmakeBuildSystemLog) << "stopping parsing run!"; + m_reader.stop(); + m_reader.resetData(); +} + +void CMakeBuildSystem::becameDirty() +{ + qCDebug(cmakeBuildSystemLog) << "CMakeBuildSystem: becameDirty was triggered."; + if (isParsing()) + return; + + const CMakeTool *tool = m_parameters.cmakeTool(); + if (!tool->isAutoRun()) + return; + + setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), + REPARSE_CHECK_CONFIGURATION | REPARSE_SCAN); +} + +void CMakeBuildSystem::updateReparseParameters(const int parameters) +{ + m_reparseParameters |= parameters; +} + +int CMakeBuildSystem::takeReparseParameters() +{ + int result = m_reparseParameters; + m_reparseParameters = REPARSE_DEFAULT; + return result; +} + +bool CMakeBuildSystem::hasConfigChanged() +{ + checkConfiguration(); + + const QByteArray GENERATOR_KEY = "CMAKE_GENERATOR"; + const QByteArray EXTRA_GENERATOR_KEY = "CMAKE_EXTRA_GENERATOR"; + const QByteArray CMAKE_COMMAND_KEY = "CMAKE_COMMAND"; + const QByteArray CMAKE_C_COMPILER_KEY = "CMAKE_C_COMPILER"; + const QByteArray CMAKE_CXX_COMPILER_KEY = "CMAKE_CXX_COMPILER"; + + const QByteArrayList criticalKeys = {GENERATOR_KEY, + CMAKE_COMMAND_KEY, + CMAKE_C_COMPILER_KEY, + CMAKE_CXX_COMPILER_KEY}; + + QString errorMessage; + const CMakeConfig currentConfig = cmakeBuildConfiguration()->configurationFromCMake(); + if (!errorMessage.isEmpty()) + return false; + + const CMakeTool *tool = m_parameters.cmakeTool(); + QTC_ASSERT(tool, + return false); // No cmake... we should not have ended up here in the first place + const QString extraKitGenerator = m_parameters.extraGenerator; + const QString mainKitGenerator = m_parameters.generator; + CMakeConfig targetConfig = m_parameters.configuration; + targetConfig.append(CMakeConfigItem(GENERATOR_KEY, + CMakeConfigItem::INTERNAL, + QByteArray(), + mainKitGenerator.toUtf8())); + if (!extraKitGenerator.isEmpty()) + targetConfig.append(CMakeConfigItem(EXTRA_GENERATOR_KEY, + CMakeConfigItem::INTERNAL, + QByteArray(), + extraKitGenerator.toUtf8())); + targetConfig.append(CMakeConfigItem(CMAKE_COMMAND_KEY, + CMakeConfigItem::INTERNAL, + QByteArray(), + tool->cmakeExecutable().toUserOutput().toUtf8())); + Utils::sort(targetConfig, CMakeConfigItem::sortOperator()); + + bool mustReparse = false; + auto ccit = currentConfig.constBegin(); + auto kcit = targetConfig.constBegin(); + + while (ccit != currentConfig.constEnd() && kcit != targetConfig.constEnd()) { + if (ccit->key == kcit->key) { + if (ccit->value != kcit->value) { + if (criticalKeys.contains(kcit->key)) { + clearCMakeCache(); + return false; // no need to trigger a new reader, clearCache will do that + } + mustReparse = true; + } + ++ccit; + ++kcit; + } else { + if (ccit->key < kcit->key) { + ++ccit; + } else { + ++kcit; + mustReparse = true; + } + } + } + + // If we have keys that do not exist yet, then reparse. + // + // The critical keys *must* be set in cmake configuration, so those were already + // handled above. + return mustReparse || kcit != targetConfig.constEnd(); +} + +bool CMakeBuildSystem::checkConfiguration() +{ + if (m_parameters.workDirectory + != m_parameters.buildDirectory) // always throw away changes in the tmpdir! + return false; + + const CMakeConfig cache = cmakeBuildConfiguration()->configurationFromCMake(); + if (cache.isEmpty()) + return false; // No cache file yet. + + CMakeConfig newConfig; + QHash> changedKeys; + foreach (const CMakeConfigItem &projectItem, m_parameters.configuration) { + const QString projectKey = QString::fromUtf8(projectItem.key); + const QString projectValue = projectItem.expandedValue(m_parameters.expander); + const CMakeConfigItem &cmakeItem = Utils::findOrDefault(cache, + [&projectItem]( + const CMakeConfigItem &i) { + return i.key == projectItem.key; + }); + const QString iCacheValue = QString::fromUtf8(cmakeItem.value); + if (cmakeItem.isNull()) { + changedKeys.insert(projectKey, qMakePair(tr(""), projectValue)); + } else if (iCacheValue != projectValue) { + changedKeys.insert(projectKey, qMakePair(iCacheValue, projectValue)); + newConfig.append(cmakeItem); + } else { + newConfig.append(projectItem); + } + } + + if (!changedKeys.isEmpty()) { + QStringList keyList = changedKeys.keys(); + Utils::sort(keyList); + QString table = QString::fromLatin1("") + .arg(tr("Key")) + .arg(tr("%1 Project").arg(Core::Constants::IDE_DISPLAY_NAME)) + .arg(tr("Changed value")); + foreach (const QString &k, keyList) { + const QPair data = changedKeys.value(k); + table += QString::fromLatin1("\n") + .arg(k) + .arg(data.second.toHtmlEscaped()) + .arg(data.first.toHtmlEscaped()); + } + table += QLatin1String("\n
%1%2%3
%1%2%3
"); + + QPointer box = new QMessageBox(Core::ICore::mainWindow()); + box->setText(tr("The project has been changed outside of %1.") + .arg(Core::Constants::IDE_DISPLAY_NAME)); + box->setInformativeText(table); + auto *defaultButton = box->addButton(tr("Discard External Changes"), + QMessageBox::RejectRole); + auto *applyButton = box->addButton(tr("Adapt %1 Project to Changes") + .arg(Core::Constants::IDE_DISPLAY_NAME), + QMessageBox::ApplyRole); + box->setDefaultButton(defaultButton); + + box->exec(); + if (box->clickedButton() == applyButton) { + m_parameters.configuration = newConfig; + QSignalBlocker blocker(buildConfiguration()); + cmakeBuildConfiguration()->setConfigurationForCMake(newConfig); + return false; + } else if (box->clickedButton() == defaultButton) + return true; + } + return false; } CMakeBuildConfiguration *CMakeBuildSystem::cmakeBuildConfiguration() const @@ -709,6 +1030,20 @@ const QList &CMakeBuildSystem::buildTargets() const return m_buildTargets; } +CMakeConfig CMakeBuildSystem::parseCMakeCacheDotTxt(const Utils::FilePath &cacheFile, + QString *errorMessage) +{ + if (!cacheFile.exists()) { + if (errorMessage) + *errorMessage = tr("CMakeCache.txt file not found."); + return {}; + } + CMakeConfig result = CMakeConfigItem::itemsFromFile(cacheFile, errorMessage); + if (!errorMessage->isEmpty()) + return {}; + return result; +} + DeploymentData CMakeBuildSystem::deploymentData() const { DeploymentData result; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h index 7179a42f0ee..5ada71513c6 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h @@ -25,10 +25,17 @@ #pragma once -#include "builddirmanager.h" +#include "builddirparameters.h" +#include "cmakebuildtarget.h" +#include "cmakeprojectnodes.h" +#include "fileapireader.h" +#include "utils/macroexpander.h" #include +#include +#include + namespace ProjectExplorer { class ExtraCompiler; } namespace CppTools { @@ -81,7 +88,28 @@ public: CMakeBuildConfiguration *cmakeBuildConfiguration() const; + // Generic CMake helper functions: + static CMakeConfig parseCMakeCacheDotTxt(const Utils::FilePath &cacheFile, + QString *errorMessage); + private: + // Actually ask for parsing: + enum ReparseParameters { + REPARSE_DEFAULT = 0, // Nothing special:-) + REPARSE_FORCE_CMAKE_RUN = (1 << 0), // Force cmake to run + REPARSE_FORCE_CONFIGURATION = (1 << 1), // Force configuration arguments to cmake + REPARSE_CHECK_CONFIGURATION + = (1 << 2), // Check for on-disk config and QtC config diff // FIXME: Remove this! + REPARSE_SCAN = (1 << 3), // Run filesystem scan + REPARSE_URGENT = (1 << 4), // Do not delay the parser run by 1s + }; + QString reparseParametersString(int reparseFlags); + void setParametersAndRequestParse(const BuildDirParameters ¶meters, + const int reparseParameters); + + void writeConfigurationIntoBuildDirectory(const Utils::MacroExpander *expander); + + // State handling: // Parser states: void handleParsingSuccess(); void handleParsingError(); @@ -106,7 +134,12 @@ private: void wireUpConnections(const ProjectExplorer::Project *p); - BuildDirManager m_buildDirManager; + Utils::FilePath workDirectory(const BuildDirParameters ¶meters); + void stopParsingAndClearState(); + void becameDirty(); + + void updateReparseParameters(const int parameters); + int takeReparseParameters(); ProjectExplorer::TreeScanner m_treeScanner; QHash m_mimeBinaryCache; @@ -121,6 +154,17 @@ private: CppTools::CppProjectUpdater *m_cppCodeModelUpdater = nullptr; QList m_extraCompilers; QList m_buildTargets; + + bool checkConfiguration(); + bool hasConfigChanged(); + + // Parsing state: + BuildDirParameters m_parameters; + int m_reparseParameters; + mutable std::unordered_map> + m_buildDirToTempDir; + FileApiReader m_reader; + mutable bool m_isHandlingError = false; }; } // namespace Internal diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.md b/src/plugins/cmakeprojectmanager/cmakebuildsystem.md index 1e28c0ad523..ff42461bb17 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.md +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.md @@ -60,7 +60,6 @@ sequenceDiagram participant User participant ParseGuard participant CMakeBuildSystem - participant BuildDirManager participant FileApiReader alt Trigger Parsing @@ -69,14 +68,12 @@ sequenceDiagram User ->> CMakeBuildSystem: Signal from outside the CMakeBuildSystem end activate CMakeBuildSystem - CMakeBuildSystem ->> BuildDirManager: call setParametersAndRequestReparse() - activate BuildDirManager - BuildDirManager ->> BuildDirManager: Validate parameters - BuildDirManager ->> FileApiReader: Construct + CMakeBuildSystem ->> CMakeBuildSystem: call setParametersAndRequestReparse() + CMakeBuildSystem ->> CMakeBuildSystem: Validate parameters + CMakeBuildSystem ->> FileApiReader: Construct activate FileApiReader - BuildDirManager ->> FileApiReader: call setParameters - BuildDirManager ->> CMakeBuildSystem: call request*Reparse() - deactivate BuildDirManager + CMakeBuildSystem ->> FileApiReader: call setParameters + CMakeBuildSystem ->> CMakeBuildSystem: call request*Reparse() deactivate CMakeBuildSystem CMakeBuildSystem ->> CMakeBuildSystem: m_delayedParsingTimer sends timeout() triggering triggerParsing() @@ -92,10 +89,8 @@ sequenceDiagram CMakeBuildSystem ->>+ TreeScanner: call asyncScanForFiles() - CMakeBuildSystem ->>+ BuildDirManager: call parse(...) - BuildDirManager ->>+ FileApiReader: call parse(...) + CMakeBuildSystem ->>+ FileApiReader: call parse(...) FileApiReader ->> FileApiReader: startState() - deactivate BuildDirManager deactivate CMakeBuildSystem opt Parse @@ -106,17 +101,12 @@ sequenceDiagram FileApiReader ->> FileApiReader: call endState(...) alt Return Result from FileApiReader - FileApiReader ->> BuildDirManager: signal dataAvailable() - BuildDirManager ->> CMakeBuildSystem: signal dataAvailable() and trigger handleParsingSucceeded() - CMakeBuildSystem ->> BuildDirManager: call takeBuildTargets() - BuildDirManager ->> FileApiReader: call takeBuildTargets() - CMakeBuildSystem ->> BuildDirManager: call takeCMakeConfiguration(...) - BuildDirManager ->> FileApiReader: call takeCMakeConfiguration(....) + FileApiReader ->> CMakeBuildSystem: signal dataAvailable() and trigger handleParsingSucceeded() + CMakeBuildSystem ->> FileApiReader: call takeBuildTargets() + CMakeBuildSystem ->> FileApiReader: call takeParsedConfiguration(....) else - FileApiReader ->> BuildDirManager: signal errorOccurred(...) - BuildDirManager ->> CMakeBuildSystem: signal errorOccurred(...) and trigger handelParsingFailed(...) - CMakeBuildSystem ->> BuildDirManager: call takeCMakeConfiguration(...) - BuildDirManager ->> FileApiReader: call takeCMakeConfiguration(....) + FileApiReader ->> CMakeBuildSystem: signal errorOccurred(...) and trigger handelParsingFailed(...) + CMakeBuildSystem ->> FileApiReader: call takeParsedConfiguration(....) end deactivate FileApiReader @@ -132,20 +122,17 @@ sequenceDiagram activate CMakeBuildSystem opt: Parsing was a success CMakeBuildSystem ->> CMakeBuildSystem: call updateProjectData() - CMakeBuildSystem ->> BuildDirManager: call projectFilesToWatch() - BuildDirManager ->> FileApiReader: call projectFilesToWatch() - CMakeBuildSystem ->> BuildDirManager: call createRawProjectParts(...) - BuildDirManager ->> FileApiReader: call createRawProjectParts(...) - CMakeBuildSystem ->> BuildDirManager: call resetData() - BuildDirManager ->> FileApiReader: Destruct + CMakeBuildSystem ->> FileApiReader: call projectFilesToWatch() + CMakeBuildSystem ->> FileApiReader: call createRawProjectParts(...) + CMakeBuildSystem ->> FileApiReader: call resetData() CMakeBuildSystem ->> ParseGuard: call markAsSuccess() end - deactivate FileApiReader CMakeBuildSystem ->> ParseGuard: Destruct deactivate ParseGuard CMakeBuildSystem ->> CMakeBuildSystem: call emitBuildSystemUpdated() + deactivate FileApiReader deactivate CMakeBuildSystem ``` diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp index f2d7ba55adc..fc6b396997e 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp @@ -25,8 +25,8 @@ #include "cmakeprojectimporter.h" -#include "builddirmanager.h" #include "cmakebuildconfiguration.h" +#include "cmakebuildsystem.h" #include "cmakekitinformation.h" #include "cmaketoolmanager.h" @@ -229,7 +229,7 @@ QList CMakeProjectImporter::examineDirectory(const Utils::FilePath &impo } QString errorMessage; - const CMakeConfig config = BuildDirManager::parseCMakeConfiguration(cacheFile, &errorMessage); + const CMakeConfig config = CMakeBuildSystem::parseCMakeCacheDotTxt(cacheFile, &errorMessage); if (config.isEmpty() || !errorMessage.isEmpty()) { qCDebug(cmInputLog()) << "Failed to read configuration from" << cacheFile << errorMessage; return { }; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro index 09414efefb0..31d9d7287d3 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro @@ -1,8 +1,7 @@ DEFINES += CMAKEPROJECTMANAGER_LIBRARY include(../../qtcreatorplugin.pri) -HEADERS = builddirmanager.h \ - builddirparameters.h \ +HEADERS = builddirparameters.h \ cmakebuildstep.h \ cmakebuildsystem.h \ cmakebuildtarget.h \ @@ -37,8 +36,7 @@ HEADERS = builddirmanager.h \ fileapireader.h \ projecttreehelper.h -SOURCES = builddirmanager.cpp \ - builddirparameters.cpp \ +SOURCES = builddirparameters.cpp \ cmakebuildstep.cpp \ cmakebuildsystem.cpp \ cmakeconfigitem.cpp \ diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 9cddc1d5ee7..3575a93c7bb 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -21,8 +21,6 @@ QtcPlugin { files: [ "builddirparameters.cpp", "builddirparameters.h", - "builddirmanager.cpp", - "builddirmanager.h", "cmake_global.h", "cmakebuildconfiguration.cpp", "cmakebuildconfiguration.h", diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp index b87455f8604..a138b044395 100644 --- a/src/plugins/cmakeprojectmanager/fileapireader.cpp +++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp @@ -136,6 +136,8 @@ void FileApiReader::parse(bool forceCMakeRun, bool forceConfiguration) void FileApiReader::stop() { + if (m_cmakeProcess) + disconnect(m_cmakeProcess.get(), nullptr, this, nullptr); m_cmakeProcess.reset(); }