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 <alessandro.portale@qt.io>
This commit is contained in:
Tobias Hunger
2020-04-24 10:29:43 +02:00
parent db4f6a0e94
commit 59473389b9
12 changed files with 475 additions and 816 deletions

View File

@@ -3,7 +3,6 @@ add_qtc_plugin(CMakeProjectManager
PLUGIN_DEPENDS Core CppTools ProjectExplorer TextEditor QtSupport PLUGIN_DEPENDS Core CppTools ProjectExplorer TextEditor QtSupport
PLUGIN_RECOMMENDS Designer PLUGIN_RECOMMENDS Designer
SOURCES SOURCES
builddirmanager.cpp builddirmanager.h
builddirparameters.cpp builddirparameters.h builddirparameters.cpp builddirparameters.h
cmake_global.h cmake_global.h
cmakeautocompleter.cpp cmakeautocompleter.h cmakeautocompleter.cpp cmakeautocompleter.h

View File

@@ -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 <coreplugin/icore.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/taskhub.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QPointer>
#include <QPushButton>
#include <app/app_version.h>
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 &parameters) 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<Utils::TemporaryDirectory>("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 &parameters,
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<FileApiReader>();
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<FilePath> 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<CMakeProjectNode> BuildDirManager::generateProjectTree(
const QList<const FileNode *> &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<CMakeBuildTarget> BuildDirManager::takeBuildTargets(QString &errorMessage) const
{
QList<CMakeBuildTarget> 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<CMakeBuildTarget> 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 = "<NONE>";
} 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<QString, QPair<QString, QString>> 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("<removed>"), 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("<table><tr><th>%1</th><th>%2</th><th>%3</th></tr>")
.arg(tr("Key"))
.arg(tr("%1 Project").arg(Core::Constants::IDE_DISPLAY_NAME))
.arg(tr("Changed value"));
foreach (const QString &k, keyList) {
const QPair<QString, QString> data = changedKeys.value(k);
table += QString::fromLatin1("\n<tr><td>%1</td><td>%2</td><td>%3</td></tr>")
.arg(k)
.arg(data.second.toHtmlEscaped())
.arg(data.first.toHtmlEscaped());
}
table += QLatin1String("\n</table>");
QPointer<QMessageBox> 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

View File

@@ -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 <projectexplorer/rawprojectpart.h>
#include <utils/fileutils.h>
#include <utils/temporarydirectory.h>
#include <QObject>
#include <QString>
#include <memory>
#include <unordered_map>
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 &parameters,
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<Utils::FilePath> projectFilesToWatch() const;
std::unique_ptr<CMakeProjectNode> generateProjectTree(const QList<const ProjectExplorer::FileNode *> &allFiles,
QString &errorMessage) const;
ProjectExplorer::RawProjectParts createRawProjectParts(QString &errorMessage) const;
QList<CMakeBuildTarget> 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 &parameters) 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<Utils::FilePath, std::unique_ptr<Utils::TemporaryDirectory>> m_buildDirToTempDir;
mutable std::unique_ptr<FileApiReader> m_reader;
mutable bool m_isHandlingError = false;
};
} // namespace Internal
} // namespace CMakeProjectManager

View File

@@ -25,7 +25,6 @@
#include "cmakebuildconfiguration.h" #include "cmakebuildconfiguration.h"
#include "builddirmanager.h"
#include "cmakebuildsettingswidget.h" #include "cmakebuildsettingswidget.h"
#include "cmakebuildstep.h" #include "cmakebuildstep.h"
#include "cmakebuildsystem.h" #include "cmakebuildsystem.h"

View File

@@ -35,7 +35,6 @@ class CMakeProject;
namespace Internal { namespace Internal {
class BuildDirManager;
class CMakeBuildSystem; class CMakeBuildSystem;
class CMakeBuildSettingsWidget; class CMakeBuildSettingsWidget;
@@ -97,7 +96,6 @@ private:
friend class CMakeBuildSettingsWidget; friend class CMakeBuildSettingsWidget;
friend class CMakeBuildSystem; friend class CMakeBuildSystem;
friend class CMakeProject; friend class CMakeProject;
friend class BuildDirManager;
}; };
class CMakeProjectImporter; class CMakeProjectImporter;

View File

@@ -25,6 +25,7 @@
#include "cmakebuildsystem.h" #include "cmakebuildsystem.h"
#include "builddirparameters.h"
#include "cmakebuildconfiguration.h" #include "cmakebuildconfiguration.h"
#include "cmakekitinformation.h" #include "cmakekitinformation.h"
#include "cmakeprojectconstants.h" #include "cmakeprojectconstants.h"
@@ -42,10 +43,12 @@
#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <qmljs/qmljsmodelmanagerinterface.h> #include <qmljs/qmljsmodelmanagerinterface.h>
#include <qtsupport/qtcppkitinfo.h> #include <qtsupport/qtcppkitinfo.h>
#include <app/app_version.h>
#include <utils/checkablemessagebox.h> #include <utils/checkablemessagebox.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/mimetypes/mimetype.h> #include <utils/mimetypes/mimetype.h>
@@ -55,6 +58,7 @@
#include <QDir> #include <QDir>
#include <QGuiApplication> #include <QGuiApplication>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QPushButton>
using namespace ProjectExplorer; using namespace ProjectExplorer;
using namespace Utils; using namespace Utils;
@@ -138,7 +142,6 @@ static Q_LOGGING_CATEGORY(cmakeBuildSystemLog, "qtc.cmake.buildsystem", QtWarnin
CMakeBuildSystem::CMakeBuildSystem(CMakeBuildConfiguration *bc) CMakeBuildSystem::CMakeBuildSystem(CMakeBuildConfiguration *bc)
: BuildSystem(bc) : BuildSystem(bc)
, m_buildDirManager(this)
, m_cppCodeModelUpdater(new CppTools::CppProjectUpdater) , m_cppCodeModelUpdater(new CppTools::CppProjectUpdater)
{ {
// TreeScanner: // TreeScanner:
@@ -177,26 +180,17 @@ CMakeBuildSystem::CMakeBuildSystem(CMakeBuildConfiguration *bc)
return type; return type;
}); });
// BuildDirManager: connect(&m_reader, &FileApiReader::configurationStarted, this, [this]() {
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]() {
cmakeBuildConfiguration()->clearError(CMakeBuildConfiguration::ForceEnabledChanged::True); 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(), connect(SessionManager::instance(),
&SessionManager::projectAdded, &SessionManager::projectAdded,
this, this,
@@ -225,22 +219,25 @@ void CMakeBuildSystem::triggerParsing()
// This can legitimately trigger if e.g. Build->Run CMake // This can legitimately trigger if e.g. Build->Run CMake
// is selected while this here is already running. // is selected while this here is already running.
// FIXME: Instead of aborting the second run here we could try to // Stop old parse run and keep that ParseGuard!
// cancel the first one in the Build->Run CMake handler and then stopParsingAndClearState();
// continue to here normally. This here could then be an Assert. } else {
return; // 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()) 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_waitingForParse = true;
m_combinedScanAndParseResult = true; m_combinedScanAndParseResult = true;
if (m_waitingForScan) { if (m_waitingForScan) {
qCDebug(cmakeBuildSystemLog) << "Starting TreeScanner";
QTC_CHECK(m_treeScanner.isFinished()); QTC_CHECK(m_treeScanner.isFinished());
m_treeScanner.asyncScanForFiles(projectDirectory()); m_treeScanner.asyncScanForFiles(projectDirectory());
Core::ProgressManager::addTask(m_treeScanner.future(), Core::ProgressManager::addTask(m_treeScanner.future(),
@@ -249,7 +246,34 @@ void CMakeBuildSystem::triggerParsing()
"CMake.Scan.Tree"); "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 bool CMakeBuildSystem::supportsAction(Node *context, ProjectAction action, const Node *node) const
@@ -312,23 +336,89 @@ QStringList CMakeBuildSystem::filesGeneratedFrom(const QString &sourceFile) cons
return {}; return {};
} }
QString CMakeBuildSystem::reparseParametersString(int reparseFlags)
{
QString result;
if (reparseFlags == REPARSE_DEFAULT) {
result = "<NONE>";
} 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 &parameters,
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() void CMakeBuildSystem::runCMake()
{ {
BuildDirParameters parameters(cmakeBuildConfiguration()); BuildDirParameters parameters(cmakeBuildConfiguration());
qCDebug(cmakeBuildSystemLog) << "Requesting parse due \"Run CMake\" command"; qCDebug(cmakeBuildSystemLog) << "Requesting parse due \"Run CMake\" command";
m_buildDirManager.setParametersAndRequestParse(parameters, setParametersAndRequestParse(parameters,
BuildDirManager::REPARSE_CHECK_CONFIGURATION REPARSE_CHECK_CONFIGURATION | REPARSE_FORCE_CMAKE_RUN
| BuildDirManager::REPARSE_FORCE_CMAKE_RUN | REPARSE_URGENT);
| BuildDirManager::REPARSE_URGENT);
} }
void CMakeBuildSystem::runCMakeAndScanProjectTree() void CMakeBuildSystem::runCMakeAndScanProjectTree()
{ {
BuildDirParameters parameters(cmakeBuildConfiguration()); BuildDirParameters parameters(cmakeBuildConfiguration());
qCDebug(cmakeBuildSystemLog) << "Requesting parse due to \"Rescan Project\" command"; qCDebug(cmakeBuildSystemLog) << "Requesting parse due to \"Rescan Project\" command";
m_buildDirManager.setParametersAndRequestParse(parameters, setParametersAndRequestParse(parameters, REPARSE_CHECK_CONFIGURATION | REPARSE_SCAN);
BuildDirManager::REPARSE_CHECK_CONFIGURATION
| BuildDirManager::REPARSE_SCAN);
} }
void CMakeBuildSystem::buildCMakeTarget(const QString &buildTarget) void CMakeBuildSystem::buildCMakeTarget(const QString &buildTarget)
@@ -351,19 +441,44 @@ void CMakeBuildSystem::handleTreeScanningFinished()
bool CMakeBuildSystem::persistCMakeState() 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() 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<CMakeProjectNode> std::unique_ptr<CMakeProjectNode>
CMakeBuildSystem::generateProjectTree(const QList<const FileNode *> &allFiles) CMakeBuildSystem::generateProjectTree(const QList<const FileNode *> &allFiles)
{ {
QString errorMessage; QString errorMessage;
auto root = m_buildDirManager.generateProjectTree(allFiles, errorMessage); auto root = m_reader.generateProjectTree(allFiles, errorMessage);
checkAndReportError(errorMessage); checkAndReportError(errorMessage);
return root; return root;
} }
@@ -380,6 +495,8 @@ void CMakeBuildSystem::combineScanAndParse()
} }
} }
m_reader.resetData();
m_currentGuard = {}; m_currentGuard = {};
emitBuildSystemUpdated(); emitBuildSystemUpdated();
@@ -397,10 +514,9 @@ void CMakeBuildSystem::updateProjectData()
{ {
qCDebug(cmakeBuildSystemLog) << "Updating CMake project data"; 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( cmakeBuildConfiguration()->project()->setExtraProjectFiles(m_reader.projectFilesToWatch());
m_buildDirManager.projectFilesToWatch());
CMakeConfig patchedConfig = cmakeBuildConfiguration()->configurationFromCMake(); CMakeConfig patchedConfig = cmakeBuildConfiguration()->configurationFromCMake();
{ {
@@ -469,7 +585,7 @@ void CMakeBuildSystem::updateProjectData()
{ {
QString errorMessage; QString errorMessage;
RawProjectParts rpps = m_buildDirManager.createRawProjectParts(errorMessage); RawProjectParts rpps = m_reader.createRawProjectParts(errorMessage);
if (!errorMessage.isEmpty()) if (!errorMessage.isEmpty())
cmakeBuildConfiguration()->setError(errorMessage); cmakeBuildConfiguration()->setError(errorMessage);
qCDebug(cmakeBuildSystemLog) << "Raw project parts created." << errorMessage; qCDebug(cmakeBuildSystemLog) << "Raw project parts created." << errorMessage;
@@ -491,15 +607,13 @@ void CMakeBuildSystem::updateProjectData()
emit cmakeBuildConfiguration()->buildTypeChanged(); emit cmakeBuildConfiguration()->buildTypeChanged();
m_buildDirManager.resetData();
qCDebug(cmakeBuildSystemLog) << "All CMake project data up to date."; qCDebug(cmakeBuildSystemLog) << "All CMake project data up to date.";
} }
void CMakeBuildSystem::handleParsingSucceeded() void CMakeBuildSystem::handleParsingSucceeded()
{ {
if (!cmakeBuildConfiguration()->isActive()) { if (!cmakeBuildConfiguration()->isActive()) {
m_buildDirManager.stopParsingAndClearState(); stopParsingAndClearState();
return; return;
} }
@@ -507,14 +621,16 @@ void CMakeBuildSystem::handleParsingSucceeded()
QString errorMessage; QString errorMessage;
{ {
m_buildTargets = m_buildDirManager.takeBuildTargets(errorMessage); m_buildTargets = m_reader.takeBuildTargets(errorMessage);
checkAndReportError(errorMessage); checkAndReportError(errorMessage);
} }
{ {
const CMakeConfig cmakeConfig = m_buildDirManager.takeCMakeConfiguration(errorMessage); CMakeConfig cmakeConfig = m_reader.takeParsedConfiguration(errorMessage);
checkAndReportError(errorMessage); for (auto &ci : cmakeConfig)
ci.inCMakeCache = true;
cmakeBuildConfiguration()->setConfigurationFromCMake(cmakeConfig); cmakeBuildConfiguration()->setConfigurationFromCMake(cmakeConfig);
checkAndReportError(errorMessage);
} }
setApplicationTargets(appTargets()); setApplicationTargets(appTargets());
@@ -531,8 +647,10 @@ void CMakeBuildSystem::handleParsingFailed(const QString &msg)
cmakeBuildConfiguration()->setError(msg); cmakeBuildConfiguration()->setError(msg);
QString errorMessage; QString errorMessage;
cmakeBuildConfiguration()->setConfigurationFromCMake( CMakeConfig cmakeConfig = m_reader.takeParsedConfiguration(errorMessage);
m_buildDirManager.takeCMakeConfiguration(errorMessage)); for (auto &ci : cmakeConfig)
ci.inCMakeCache = true;
cmakeBuildConfiguration()->setConfigurationFromCMake(cmakeConfig);
// ignore errorMessage here, we already got one. // ignore errorMessage here, we already got one.
QTC_CHECK(m_waitingForParse); QTC_CHECK(m_waitingForParse);
@@ -559,8 +677,8 @@ void CMakeBuildSystem::wireUpConnections(const Project *p)
// Build configuration has not changed, but Kit settings might have: // Build configuration has not changed, but Kit settings might have:
// reparse and check the configuration, independent of whether the reader has changed // reparse and check the configuration, independent of whether the reader has changed
qCDebug(cmakeBuildSystemLog) << "Requesting parse due to kit being updated"; qCDebug(cmakeBuildSystemLog) << "Requesting parse due to kit being updated";
m_buildDirManager.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()),
BuildDirManager::REPARSE_CHECK_CONFIGURATION); CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION);
}); });
// Became active/inactive: // Became active/inactive:
@@ -570,11 +688,10 @@ void CMakeBuildSystem::wireUpConnections(const Project *p)
// * Check configuration if reader changes due to it not existing yet:-) // * Check configuration if reader changes due to it not existing yet:-)
// * run cmake without configuration arguments if the reader stays // * run cmake without configuration arguments if the reader stays
qCDebug(cmakeBuildSystemLog) << "Requesting parse due to active target changed"; qCDebug(cmakeBuildSystemLog) << "Requesting parse due to active target changed";
m_buildDirManager setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()),
.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION);
BuildDirManager::REPARSE_CHECK_CONFIGURATION);
} else { } else {
m_buildDirManager.stopParsingAndClearState(); stopParsingAndClearState();
} }
}); });
connect(target(), &Target::activeBuildConfigurationChanged, this, [this](BuildConfiguration *bc) { 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:-) // * Check configuration if reader changes due to it not existing yet:-)
// * run cmake without configuration arguments if the reader stays // * run cmake without configuration arguments if the reader stays
qCDebug(cmakeBuildSystemLog) << "Requesting parse due to active BC changed"; qCDebug(cmakeBuildSystemLog) << "Requesting parse due to active BC changed";
m_buildDirManager setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()),
.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION);
BuildDirManager::REPARSE_CHECK_CONFIGURATION);
} else { } 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. // * Error out if the reader updates, cannot happen since all BCs share a target/kit.
// * run cmake without configuration arguments if the reader stays // * run cmake without configuration arguments if the reader stays
qCDebug(cmakeBuildSystemLog) << "Requesting parse due to environment change"; qCDebug(cmakeBuildSystemLog) << "Requesting parse due to environment change";
m_buildDirManager setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()),
.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION);
BuildDirManager::REPARSE_CHECK_CONFIGURATION);
} }
}); });
connect(cmakeBuildConfiguration(), &CMakeBuildConfiguration::buildDirectoryChanged, this, [this]() { 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 // If no configuration exists, then the arguments will get added automatically by
// the reader. // the reader.
qCDebug(cmakeBuildSystemLog) << "Requesting parse due to build directory change"; qCDebug(cmakeBuildSystemLog) << "Requesting parse due to build directory change";
m_buildDirManager setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()),
.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION);
BuildDirManager::REPARSE_CHECK_CONFIGURATION);
} }
}); });
connect(cmakeBuildConfiguration(), connect(cmakeBuildConfiguration(),
@@ -628,9 +742,8 @@ void CMakeBuildSystem::wireUpConnections(const Project *p)
// * run cmake with configuration arguments if the reader stays // * run cmake with configuration arguments if the reader stays
qCDebug(cmakeBuildSystemLog) qCDebug(cmakeBuildSystemLog)
<< "Requesting parse due to cmake configuration change"; << "Requesting parse due to cmake configuration change";
m_buildDirManager setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()),
.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), CMakeBuildSystem::REPARSE_FORCE_CONFIGURATION);
BuildDirManager::REPARSE_FORCE_CONFIGURATION);
} }
}); });
@@ -639,16 +752,224 @@ void CMakeBuildSystem::wireUpConnections(const Project *p)
const auto cmake = CMakeKitAspect::cmakeTool(cmakeBuildConfiguration()->target()->kit()); const auto cmake = CMakeKitAspect::cmakeTool(cmakeBuildConfiguration()->target()->kit());
if (cmake && cmake->isAutoRun()) { if (cmake && cmake->isAutoRun()) {
qCDebug(cmakeBuildSystemLog) << "Requesting parse due to dirty project file"; qCDebug(cmakeBuildSystemLog) << "Requesting parse due to dirty project file";
m_buildDirManager.setParametersAndRequestParse(BuildDirParameters( setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()),
cmakeBuildConfiguration()), CMakeBuildSystem::REPARSE_DEFAULT);
BuildDirManager::REPARSE_DEFAULT);
} }
} }
}); });
// Force initial parsing run: // Force initial parsing run:
m_buildDirManager.setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()), if (cmakeBuildConfiguration()->isActive())
BuildDirManager::REPARSE_CHECK_CONFIGURATION); setParametersAndRequestParse(BuildDirParameters(cmakeBuildConfiguration()),
CMakeBuildSystem::REPARSE_CHECK_CONFIGURATION);
}
FilePath CMakeBuildSystem::workDirectory(const BuildDirParameters &parameters)
{
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<Utils::TemporaryDirectory>("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<QString, QPair<QString, QString>> 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("<removed>"), 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("<table><tr><th>%1</th><th>%2</th><th>%3</th></tr>")
.arg(tr("Key"))
.arg(tr("%1 Project").arg(Core::Constants::IDE_DISPLAY_NAME))
.arg(tr("Changed value"));
foreach (const QString &k, keyList) {
const QPair<QString, QString> data = changedKeys.value(k);
table += QString::fromLatin1("\n<tr><td>%1</td><td>%2</td><td>%3</td></tr>")
.arg(k)
.arg(data.second.toHtmlEscaped())
.arg(data.first.toHtmlEscaped());
}
table += QLatin1String("\n</table>");
QPointer<QMessageBox> 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 CMakeBuildConfiguration *CMakeBuildSystem::cmakeBuildConfiguration() const
@@ -709,6 +1030,20 @@ const QList<CMakeBuildTarget> &CMakeBuildSystem::buildTargets() const
return m_buildTargets; 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 CMakeBuildSystem::deploymentData() const
{ {
DeploymentData result; DeploymentData result;

View File

@@ -25,10 +25,17 @@
#pragma once #pragma once
#include "builddirmanager.h" #include "builddirparameters.h"
#include "cmakebuildtarget.h"
#include "cmakeprojectnodes.h"
#include "fileapireader.h"
#include "utils/macroexpander.h"
#include <projectexplorer/buildsystem.h> #include <projectexplorer/buildsystem.h>
#include <utils/fileutils.h>
#include <utils/temporarydirectory.h>
namespace ProjectExplorer { class ExtraCompiler; } namespace ProjectExplorer { class ExtraCompiler; }
namespace CppTools { namespace CppTools {
@@ -81,7 +88,28 @@ public:
CMakeBuildConfiguration *cmakeBuildConfiguration() const; CMakeBuildConfiguration *cmakeBuildConfiguration() const;
// Generic CMake helper functions:
static CMakeConfig parseCMakeCacheDotTxt(const Utils::FilePath &cacheFile,
QString *errorMessage);
private: 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 &parameters,
const int reparseParameters);
void writeConfigurationIntoBuildDirectory(const Utils::MacroExpander *expander);
// State handling:
// Parser states: // Parser states:
void handleParsingSuccess(); void handleParsingSuccess();
void handleParsingError(); void handleParsingError();
@@ -106,7 +134,12 @@ private:
void wireUpConnections(const ProjectExplorer::Project *p); void wireUpConnections(const ProjectExplorer::Project *p);
BuildDirManager m_buildDirManager; Utils::FilePath workDirectory(const BuildDirParameters &parameters);
void stopParsingAndClearState();
void becameDirty();
void updateReparseParameters(const int parameters);
int takeReparseParameters();
ProjectExplorer::TreeScanner m_treeScanner; ProjectExplorer::TreeScanner m_treeScanner;
QHash<QString, bool> m_mimeBinaryCache; QHash<QString, bool> m_mimeBinaryCache;
@@ -121,6 +154,17 @@ private:
CppTools::CppProjectUpdater *m_cppCodeModelUpdater = nullptr; CppTools::CppProjectUpdater *m_cppCodeModelUpdater = nullptr;
QList<ProjectExplorer::ExtraCompiler *> m_extraCompilers; QList<ProjectExplorer::ExtraCompiler *> m_extraCompilers;
QList<CMakeBuildTarget> m_buildTargets; QList<CMakeBuildTarget> m_buildTargets;
bool checkConfiguration();
bool hasConfigChanged();
// Parsing state:
BuildDirParameters m_parameters;
int m_reparseParameters;
mutable std::unordered_map<Utils::FilePath, std::unique_ptr<Utils::TemporaryDirectory>>
m_buildDirToTempDir;
FileApiReader m_reader;
mutable bool m_isHandlingError = false;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -60,7 +60,6 @@ sequenceDiagram
participant User participant User
participant ParseGuard participant ParseGuard
participant CMakeBuildSystem participant CMakeBuildSystem
participant BuildDirManager
participant FileApiReader participant FileApiReader
alt Trigger Parsing alt Trigger Parsing
@@ -69,14 +68,12 @@ sequenceDiagram
User ->> CMakeBuildSystem: Signal from outside the CMakeBuildSystem User ->> CMakeBuildSystem: Signal from outside the CMakeBuildSystem
end end
activate CMakeBuildSystem activate CMakeBuildSystem
CMakeBuildSystem ->> BuildDirManager: call setParametersAndRequestReparse() CMakeBuildSystem ->> CMakeBuildSystem: call setParametersAndRequestReparse()
activate BuildDirManager CMakeBuildSystem ->> CMakeBuildSystem: Validate parameters
BuildDirManager ->> BuildDirManager: Validate parameters CMakeBuildSystem ->> FileApiReader: Construct
BuildDirManager ->> FileApiReader: Construct
activate FileApiReader activate FileApiReader
BuildDirManager ->> FileApiReader: call setParameters CMakeBuildSystem ->> FileApiReader: call setParameters
BuildDirManager ->> CMakeBuildSystem: call request*Reparse() CMakeBuildSystem ->> CMakeBuildSystem: call request*Reparse()
deactivate BuildDirManager
deactivate CMakeBuildSystem deactivate CMakeBuildSystem
CMakeBuildSystem ->> CMakeBuildSystem: m_delayedParsingTimer sends timeout() triggering triggerParsing() CMakeBuildSystem ->> CMakeBuildSystem: m_delayedParsingTimer sends timeout() triggering triggerParsing()
@@ -92,10 +89,8 @@ sequenceDiagram
CMakeBuildSystem ->>+ TreeScanner: call asyncScanForFiles() CMakeBuildSystem ->>+ TreeScanner: call asyncScanForFiles()
CMakeBuildSystem ->>+ BuildDirManager: call parse(...) CMakeBuildSystem ->>+ FileApiReader: call parse(...)
BuildDirManager ->>+ FileApiReader: call parse(...)
FileApiReader ->> FileApiReader: startState() FileApiReader ->> FileApiReader: startState()
deactivate BuildDirManager
deactivate CMakeBuildSystem deactivate CMakeBuildSystem
opt Parse opt Parse
@@ -106,17 +101,12 @@ sequenceDiagram
FileApiReader ->> FileApiReader: call endState(...) FileApiReader ->> FileApiReader: call endState(...)
alt Return Result from FileApiReader alt Return Result from FileApiReader
FileApiReader ->> BuildDirManager: signal dataAvailable() FileApiReader ->> CMakeBuildSystem: signal dataAvailable() and trigger handleParsingSucceeded()
BuildDirManager ->> CMakeBuildSystem: signal dataAvailable() and trigger handleParsingSucceeded() CMakeBuildSystem ->> FileApiReader: call takeBuildTargets()
CMakeBuildSystem ->> BuildDirManager: call takeBuildTargets() CMakeBuildSystem ->> FileApiReader: call takeParsedConfiguration(....)
BuildDirManager ->> FileApiReader: call takeBuildTargets()
CMakeBuildSystem ->> BuildDirManager: call takeCMakeConfiguration(...)
BuildDirManager ->> FileApiReader: call takeCMakeConfiguration(....)
else else
FileApiReader ->> BuildDirManager: signal errorOccurred(...) FileApiReader ->> CMakeBuildSystem: signal errorOccurred(...) and trigger handelParsingFailed(...)
BuildDirManager ->> CMakeBuildSystem: signal errorOccurred(...) and trigger handelParsingFailed(...) CMakeBuildSystem ->> FileApiReader: call takeParsedConfiguration(....)
CMakeBuildSystem ->> BuildDirManager: call takeCMakeConfiguration(...)
BuildDirManager ->> FileApiReader: call takeCMakeConfiguration(....)
end end
deactivate FileApiReader deactivate FileApiReader
@@ -132,20 +122,17 @@ sequenceDiagram
activate CMakeBuildSystem activate CMakeBuildSystem
opt: Parsing was a success opt: Parsing was a success
CMakeBuildSystem ->> CMakeBuildSystem: call updateProjectData() CMakeBuildSystem ->> CMakeBuildSystem: call updateProjectData()
CMakeBuildSystem ->> BuildDirManager: call projectFilesToWatch() CMakeBuildSystem ->> FileApiReader: call projectFilesToWatch()
BuildDirManager ->> FileApiReader: call projectFilesToWatch() CMakeBuildSystem ->> FileApiReader: call createRawProjectParts(...)
CMakeBuildSystem ->> BuildDirManager: call createRawProjectParts(...) CMakeBuildSystem ->> FileApiReader: call resetData()
BuildDirManager ->> FileApiReader: call createRawProjectParts(...)
CMakeBuildSystem ->> BuildDirManager: call resetData()
BuildDirManager ->> FileApiReader: Destruct
CMakeBuildSystem ->> ParseGuard: call markAsSuccess() CMakeBuildSystem ->> ParseGuard: call markAsSuccess()
end end
deactivate FileApiReader
CMakeBuildSystem ->> ParseGuard: Destruct CMakeBuildSystem ->> ParseGuard: Destruct
deactivate ParseGuard deactivate ParseGuard
CMakeBuildSystem ->> CMakeBuildSystem: call emitBuildSystemUpdated() CMakeBuildSystem ->> CMakeBuildSystem: call emitBuildSystemUpdated()
deactivate FileApiReader
deactivate CMakeBuildSystem deactivate CMakeBuildSystem
``` ```

View File

@@ -25,8 +25,8 @@
#include "cmakeprojectimporter.h" #include "cmakeprojectimporter.h"
#include "builddirmanager.h"
#include "cmakebuildconfiguration.h" #include "cmakebuildconfiguration.h"
#include "cmakebuildsystem.h"
#include "cmakekitinformation.h" #include "cmakekitinformation.h"
#include "cmaketoolmanager.h" #include "cmaketoolmanager.h"
@@ -229,7 +229,7 @@ QList<void *> CMakeProjectImporter::examineDirectory(const Utils::FilePath &impo
} }
QString errorMessage; QString errorMessage;
const CMakeConfig config = BuildDirManager::parseCMakeConfiguration(cacheFile, &errorMessage); const CMakeConfig config = CMakeBuildSystem::parseCMakeCacheDotTxt(cacheFile, &errorMessage);
if (config.isEmpty() || !errorMessage.isEmpty()) { if (config.isEmpty() || !errorMessage.isEmpty()) {
qCDebug(cmInputLog()) << "Failed to read configuration from" << cacheFile << errorMessage; qCDebug(cmInputLog()) << "Failed to read configuration from" << cacheFile << errorMessage;
return { }; return { };

View File

@@ -1,8 +1,7 @@
DEFINES += CMAKEPROJECTMANAGER_LIBRARY DEFINES += CMAKEPROJECTMANAGER_LIBRARY
include(../../qtcreatorplugin.pri) include(../../qtcreatorplugin.pri)
HEADERS = builddirmanager.h \ HEADERS = builddirparameters.h \
builddirparameters.h \
cmakebuildstep.h \ cmakebuildstep.h \
cmakebuildsystem.h \ cmakebuildsystem.h \
cmakebuildtarget.h \ cmakebuildtarget.h \
@@ -37,8 +36,7 @@ HEADERS = builddirmanager.h \
fileapireader.h \ fileapireader.h \
projecttreehelper.h projecttreehelper.h
SOURCES = builddirmanager.cpp \ SOURCES = builddirparameters.cpp \
builddirparameters.cpp \
cmakebuildstep.cpp \ cmakebuildstep.cpp \
cmakebuildsystem.cpp \ cmakebuildsystem.cpp \
cmakeconfigitem.cpp \ cmakeconfigitem.cpp \

View File

@@ -21,8 +21,6 @@ QtcPlugin {
files: [ files: [
"builddirparameters.cpp", "builddirparameters.cpp",
"builddirparameters.h", "builddirparameters.h",
"builddirmanager.cpp",
"builddirmanager.h",
"cmake_global.h", "cmake_global.h",
"cmakebuildconfiguration.cpp", "cmakebuildconfiguration.cpp",
"cmakebuildconfiguration.h", "cmakebuildconfiguration.h",

View File

@@ -136,6 +136,8 @@ void FileApiReader::parse(bool forceCMakeRun, bool forceConfiguration)
void FileApiReader::stop() void FileApiReader::stop()
{ {
if (m_cmakeProcess)
disconnect(m_cmakeProcess.get(), nullptr, this, nullptr);
m_cmakeProcess.reset(); m_cmakeProcess.reset();
} }