Commit 76a050cb authored by Tobias Hunger's avatar Tobias Hunger

CMake: Move BuildDirManager from the project into the buildconfiguration

Change-Id: I90126ff22dd394eba4145db142b3914d211af476
Reviewed-by: default avatarTobias Hunger <tobias.hunger@theqtcompany.com>
parent 34fd3256
......@@ -24,6 +24,7 @@
****************************************************************************/
#include "builddirmanager.h"
#include "cmakebuildconfiguration.h"
#include "cmakekitinformation.h"
#include "cmakeparser.h"
#include "cmakeprojectmanager.h"
......@@ -32,8 +33,10 @@
#include <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <utils/algorithm.h>
......@@ -88,43 +91,49 @@ static QStringList toArguments(const CMakeConfig &config) {
// BuildDirManager:
// --------------------------------------------------------------------
BuildDirManager::BuildDirManager(const Utils::FileName &sourceDir, const ProjectExplorer::Kit *k,
const CMakeConfig &inputConfig, const Utils::Environment &env,
const Utils::FileName &buildDir) :
m_sourceDir(sourceDir),
m_buildDir(buildDir),
m_kit(k),
m_environment(env),
m_inputConfig(inputConfig),
BuildDirManager::BuildDirManager(const CMakeBuildConfiguration *bc) :
m_buildConfiguration(bc),
m_watcher(new QFileSystemWatcher(this))
{
QTC_CHECK(!sourceDir.isEmpty());
m_projectName = m_sourceDir.fileName();
if (m_buildDir.isEmpty()) {
m_tempDir = new QTemporaryDir(QLatin1String("cmake-tmp-XXXXXX"));
m_buildDir = Utils::FileName::fromString(m_tempDir->path());
}
QTC_CHECK(!m_buildDir.isEmpty());
QTC_CHECK(k);
QTC_ASSERT(bc, return);
m_projectName = sourceDirectory().fileName();
m_reparseTimer.setSingleShot(true);
m_reparseTimer.setInterval(500);
connect(&m_reparseTimer, &QTimer::timeout, this, &BuildDirManager::forceReparse);
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, [this]() {
if (!isBusy())
if (!isParsing())
m_reparseTimer.start();
});
QTimer::singleShot(0, this, &BuildDirManager::parse);
}
BuildDirManager::~BuildDirManager()
{
delete m_tempDir;
resetData();
}
const ProjectExplorer::Kit *BuildDirManager::kit() const
{
return m_buildConfiguration->target()->kit();
}
const Utils::FileName BuildDirManager::buildDirectory() const
{
return m_buildConfiguration->buildDirectory();
}
const Utils::FileName BuildDirManager::sourceDirectory() const
{
return m_buildConfiguration->target()->project()->projectDirectory();
}
bool BuildDirManager::isBusy() const
const CMakeConfig BuildDirManager::cmakeConfiguration() const
{
return m_buildConfiguration->cmakeConfiguration();
}
bool BuildDirManager::isParsing() const
{
if (m_cmakeProcess)
return m_cmakeProcess->state() != QProcess::NotRunning;
......@@ -133,53 +142,59 @@ bool BuildDirManager::isBusy() const
void BuildDirManager::forceReparse()
{
if (isBusy()) {
m_cmakeProcess->disconnect();
m_cmakeProcess->deleteLater();
m_cmakeProcess = nullptr;
}
stopProcess();
CMakeTool *tool = CMakeKitInformation::cmakeTool(m_kit);
const QString generator = CMakeGeneratorKitInformation::generator(m_kit);
CMakeTool *tool = CMakeKitInformation::cmakeTool(kit());
const QString generator = CMakeGeneratorKitInformation::generator(kit());
QTC_ASSERT(tool, return);
QTC_ASSERT(!generator.isEmpty(), return);
startCMake(tool, generator, m_inputConfig);
startCMake(tool, generator, cmakeConfiguration());
}
void BuildDirManager::setInputConfiguration(const CMakeConfig &config)
void BuildDirManager::resetData()
{
m_inputConfig = config;
forceReparse();
m_hasData = false;
m_projectName.clear();
m_buildTargets.clear();
m_watchedFiles.clear();
qDeleteAll(m_files);
m_files.clear();
const QStringList watchedFiles = m_watcher->files();
if (!watchedFiles.isEmpty())
m_watcher->removePaths(watchedFiles);
}
void BuildDirManager::parse()
{
CMakeTool *tool = CMakeKitInformation::cmakeTool(m_kit);
const QString generator = CMakeGeneratorKitInformation::generator(m_kit);
CMakeTool *tool = CMakeKitInformation::cmakeTool(kit());
const QString generator = CMakeGeneratorKitInformation::generator(kit());
QTC_ASSERT(tool, return);
QTC_ASSERT(!generator.isEmpty(), return);
// Pop up a dialog asking the user to rerun cmake
QString cbpFile = CMakeManager::findCbpFile(QDir(m_buildDir.toString()));
QString cbpFile = CMakeManager::findCbpFile(QDir(buildDirectory().toString()));
QFileInfo cbpFileFi(cbpFile);
if (!cbpFileFi.exists()) {
// Initial create:
startCMake(tool, generator, m_inputConfig);
startCMake(tool, generator, cmakeConfiguration());
return;
}
const bool mustUpdate
= Utils::anyOf(m_watchedFiles, [&cbpFileFi](const Utils::FileName &f) {
const bool mustUpdate = m_watchedFiles.isEmpty()
|| Utils::anyOf(m_watchedFiles, [&cbpFileFi](const Utils::FileName &f) {
return f.toFileInfo().lastModified() > cbpFileFi.lastModified();
});
if (mustUpdate) {
startCMake(tool, generator, CMakeConfig());
} else {
extractData();
m_hasData = true;
emit dataAvailable();
}
}
......@@ -199,7 +214,7 @@ QList<CMakeBuildTarget> BuildDirManager::buildTargets() const
return m_buildTargets;
}
QList<ProjectExplorer::FileNode *> BuildDirManager::files() const
QList<ProjectExplorer::FileNode *> BuildDirManager::files()
{
return m_files;
}
......@@ -211,25 +226,45 @@ void BuildDirManager::clearFiles()
CMakeConfig BuildDirManager::configuration() const
{
if (!m_hasData)
return CMakeConfig();
return parseConfiguration();
}
void BuildDirManager::stopProcess()
{
if (m_cmakeProcess) {
m_cmakeProcess->disconnect();
if (m_cmakeProcess->state() == QProcess::Running) {
m_cmakeProcess->terminate();
if (!m_cmakeProcess->waitForFinished(500))
m_cmakeProcess->kill();
}
delete m_cmakeProcess;
m_cmakeProcess = nullptr;
// Delete issue parser:
m_parser->flush();
delete m_parser;
m_parser = nullptr;
}
}
void BuildDirManager::extractData()
{
const Utils::FileName topCMake
= Utils::FileName::fromString(m_sourceDir.toString() + QLatin1String("/CMakeLists.txt"));
= Utils::FileName::fromString(sourceDirectory().toString() + QLatin1String("/CMakeLists.txt"));
m_projectName = m_sourceDir.fileName();
m_buildTargets.clear();
m_watchedFiles.clear();
m_files.clear();
resetData();
m_projectName = sourceDirectory().fileName();
m_files.append(new ProjectExplorer::FileNode(topCMake, ProjectExplorer::ProjectFileType, false));
m_watchedFiles.insert(topCMake);
m_watcher->removePaths(m_watcher->files());
// Find cbp file
QString cbpFile = CMakeManager::findCbpFile(m_buildDir.toString());
QString cbpFile = CMakeManager::findCbpFile(buildDirectory().toString());
if (cbpFile.isEmpty())
return;
......@@ -238,7 +273,7 @@ void BuildDirManager::extractData()
// setFolderName
CMakeCbpParser cbpparser;
// Parsing
if (!cbpparser.parseCbpFile(m_kit, cbpFile, m_sourceDir.toString()))
if (!cbpparser.parseCbpFile(kit(), cbpFile, sourceDirectory().toString()))
return;
m_projectName = cbpparser.projectName();
......@@ -271,12 +306,12 @@ void BuildDirManager::startCMake(CMakeTool *tool, const QString &generator,
QTC_ASSERT(!m_future, return);
// Make sure m_buildDir exists:
const QString buildDirStr = m_buildDir.toString();
const QString buildDirStr = buildDirectory().toString();
QDir bDir = QDir(buildDirStr);
bDir.mkpath(buildDirStr);
m_parser = new CMakeParser;
QDir source = QDir(m_sourceDir.toString());
QDir source = QDir(sourceDirectory().toString());
connect(m_parser, &ProjectExplorer::IOutputParser::addTask, m_parser,
[source](const ProjectExplorer::Task &task) {
if (task.file.isEmpty() || task.file.toFileInfo().isAbsolute()) {
......@@ -290,11 +325,11 @@ void BuildDirManager::startCMake(CMakeTool *tool, const QString &generator,
// Always use the sourceDir: If we are triggered because the build directory is getting deleted
// then we are racing against CMakeCache.txt also getting deleted.
const QString srcDir = m_sourceDir.toString();
const QString srcDir = sourceDirectory().toString();
m_cmakeProcess = new Utils::QtcProcess(this);
m_cmakeProcess->setWorkingDirectory(buildDirStr);
m_cmakeProcess->setEnvironment(m_environment);
m_cmakeProcess->setEnvironment(m_buildConfiguration->environment());
connect(m_cmakeProcess, &QProcess::readyReadStandardOutput,
this, &BuildDirManager::processCMakeOutput);
......@@ -314,17 +349,17 @@ void BuildDirManager::startCMake(CMakeTool *tool, const QString &generator,
Core::MessageManager::write(tr("Running '%1 %2' in %3.")
.arg(tool->cmakeExecutable().toUserOutput())
.arg(args)
.arg(m_buildDir.toUserOutput()));
.arg(buildDirectory().toUserOutput()));
m_future = new QFutureInterface<void>();
m_future->setProgressRange(0, 1);
Core::ProgressManager::addTask(m_future->future(),
tr("Configuring \"%1\"").arg(projectName()),
tr("Configuring \"%1\"").arg(m_buildConfiguration->target()->project()->displayName()),
"CMake.Configure");
m_cmakeProcess->setCommand(tool->cmakeExecutable().toString(), args);
m_cmakeProcess->start();
emit parsingStarted();
emit configurationStarted();
}
void BuildDirManager::cmakeFinished(int code, QProcess::ExitStatus status)
......@@ -335,12 +370,7 @@ void BuildDirManager::cmakeFinished(int code, QProcess::ExitStatus status)
processCMakeOutput();
processCMakeError();
m_parser->flush();
delete m_parser;
m_parser = nullptr;
m_cmakeProcess->deleteLater();
m_cmakeProcess = nullptr;
stopProcess();
extractData(); // try even if cmake failed...
......@@ -363,6 +393,7 @@ void BuildDirManager::cmakeFinished(int code, QProcess::ExitStatus status)
delete m_future;
m_future = 0;
m_hasData = true;
emit dataAvailable();
}
......@@ -433,7 +464,7 @@ static CMakeConfigItem::Type fromByteArray(const QByteArray &type) {
CMakeConfig BuildDirManager::parseConfiguration() const
{
CMakeConfig result;
const QString cacheFile = QDir(m_buildDir.toString()).absoluteFilePath(QLatin1String("CMakeCache.txt"));
const QString cacheFile = QDir(buildDirectory().toString()).absoluteFilePath(QLatin1String("CMakeCache.txt"));
QFile cache(cacheFile);
if (!cache.open(QIODevice::ReadOnly | QIODevice::Text))
return CMakeConfig();
......@@ -470,7 +501,7 @@ CMakeConfig BuildDirManager::parseConfiguration() const
// Sanity checks:
if (key == "CMAKE_HOME_DIRECTORY") {
const Utils::FileName actualSourceDir = Utils::FileName::fromUserInput(QString::fromUtf8(value));
if (actualSourceDir != m_sourceDir)
if (actualSourceDir != sourceDirectory())
emit errorOccured(tr("Build directory contains a build of the wrong project (%1).")
.arg(actualSourceDir.toUserOutput()));
}
......
......@@ -57,39 +57,40 @@ class CMakeTool;
namespace Internal {
class CMakeBuildConfiguration;
class BuildDirManager : public QObject
{
Q_OBJECT
public:
BuildDirManager(const Utils::FileName &sourceDir, const ProjectExplorer::Kit *k,
const CMakeConfig &inputConfig, const Utils::Environment &env,
const Utils::FileName &buildDir);
BuildDirManager(const CMakeBuildConfiguration *bc);
~BuildDirManager() override;
const ProjectExplorer::Kit *kit() const { return m_kit; }
const Utils::FileName buildDirectory() const { return m_buildDir; }
const Utils::FileName sourceDirectory() const { return m_sourceDir; }
bool isBusy() const;
const ProjectExplorer::Kit *kit() const;
const Utils::FileName buildDirectory() const;
const Utils::FileName sourceDirectory() const;
const CMakeConfig cmakeConfiguration() const;
bool isParsing() const;
void parse();
void forceReparse();
void setInputConfiguration(const CMakeConfig &config);
void resetData();
bool isProjectFile(const Utils::FileName &fileName) const;
QString projectName() const;
QList<CMakeBuildTarget> buildTargets() const;
QList<ProjectExplorer::FileNode *> files() const;
QList<ProjectExplorer::FileNode *> files();
void clearFiles();
CMakeConfig configuration() const;
signals:
void parsingStarted() const;
void configurationStarted() const;
void dataAvailable() const;
void errorOccured(const QString &err) const;
private:
void stopProcess();
void extractData();
void startCMake(CMakeTool *tool, const QString &generator, const CMakeConfig &config);
......@@ -100,14 +101,9 @@ private:
CMakeConfig parseConfiguration() const;
const Utils::FileName m_sourceDir;
Utils::FileName m_buildDir;
Utils::FileName m_parsedSourceDir;
const ProjectExplorer::Kit *const m_kit;
Utils::Environment m_environment;
CMakeConfig m_inputConfig;
bool m_hasData = false;
QTemporaryDir *m_tempDir = nullptr;
const CMakeBuildConfiguration *m_buildConfiguration = nullptr;
Utils::QtcProcess *m_cmakeProcess = nullptr;
QSet<Utils::FileName> m_watchedFiles;
......
......@@ -25,6 +25,7 @@
#include "cmakebuildconfiguration.h"
#include "builddirmanager.h"
#include "cmakebuildinfo.h"
#include "cmakebuildstep.h"
#include "cmakekitinformation.h"
......@@ -75,10 +76,32 @@ static FileName shadowBuildDirectory(const FileName &projectFilePath, const Kit
CMakeBuildConfiguration::CMakeBuildConfiguration(ProjectExplorer::Target *parent) :
BuildConfiguration(parent, Core::Id(Constants::CMAKE_BC_ID))
{
CMakeProject *project = static_cast<CMakeProject *>(parent->project());
auto project = static_cast<CMakeProject *>(parent->project());
setBuildDirectory(shadowBuildDirectory(project->projectFilePath(),
parent->kit(),
displayName(), BuildConfiguration::Unknown));
m_buildDirManager = new BuildDirManager(this);
connect(m_buildDirManager, &BuildDirManager::dataAvailable,
this, &CMakeBuildConfiguration::dataAvailable);
connect(m_buildDirManager, &BuildDirManager::errorOccured,
this, &CMakeBuildConfiguration::setError);
connect(m_buildDirManager, &BuildDirManager::configurationStarted,
this, [this]() { m_completeConfigurationCache.clear(); emit parsingStarted(); });
connect(this, &CMakeBuildConfiguration::environmentChanged,
m_buildDirManager, &BuildDirManager::forceReparse);
connect(this, &CMakeBuildConfiguration::buildDirectoryChanged,
m_buildDirManager, &BuildDirManager::parse);
connect(target(), &Target::kitChanged, m_buildDirManager, &BuildDirManager::forceReparse);
connect(this, &CMakeBuildConfiguration::parsingStarted, project, &CMakeProject::handleParsingStarted);
connect(this, &CMakeBuildConfiguration::dataAvailable, project, &CMakeProject::parseCMakeOutput);
}
CMakeBuildConfiguration::~CMakeBuildConfiguration()
{
m_buildDirManager->deleteLater(); // Do not block while waiting for cmake...
}
bool CMakeBuildConfiguration::isEnabled() const
......@@ -139,6 +162,102 @@ bool CMakeBuildConfiguration::fromMap(const QVariantMap &map)
return true;
}
BuildDirManager *CMakeBuildConfiguration::buildDirManager() const
{
return m_buildDirManager;
}
bool CMakeBuildConfiguration::isParsing() const
{
return m_buildDirManager && m_buildDirManager->isParsing();
}
void CMakeBuildConfiguration::parse()
{
m_buildDirManager->parse();
}
void CMakeBuildConfiguration::resetData()
{
m_buildDirManager->resetData();
}
QList<ConfigModel::DataItem> CMakeBuildConfiguration::completeCMakeConfiguration() const
{
if (m_buildDirManager->isParsing())
return QList<ConfigModel::DataItem>();
if (m_completeConfigurationCache.isEmpty())
m_completeConfigurationCache = m_buildDirManager->configuration();
return Utils::transform(m_completeConfigurationCache, [](const CMakeConfigItem &i) {
ConfigModel::DataItem j;
j.key = QString::fromUtf8(i.key);
j.value = QString::fromUtf8(i.value);
j.description = QString::fromUtf8(i.documentation);
j.isAdvanced = i.isAdvanced;
switch (i.type) {
case CMakeConfigItem::FILEPATH:
j.type = ConfigModel::DataItem::FILE;
break;
case CMakeConfigItem::PATH:
j.type = ConfigModel::DataItem::DIRECTORY;
break;
case CMakeConfigItem::BOOL:
j.type = ConfigModel::DataItem::BOOLEAN;
break;
case CMakeConfigItem::STRING:
j.type = ConfigModel::DataItem::STRING;
break;
default:
j.type = ConfigModel::DataItem::UNKNOWN;
break;
}
return j;
});
}
void CMakeBuildConfiguration::setCurrentCMakeConfiguration(const QList<ConfigModel::DataItem> &items)
{
if (m_buildDirManager->isParsing())
return;
const CMakeConfig newConfig = Utils::transform(items, [](const ConfigModel::DataItem &i) {
CMakeConfigItem ni;
ni.key = i.key.toUtf8();
ni.value = i.value.toUtf8();
ni.documentation = i.description.toUtf8();
ni.isAdvanced = i.isAdvanced;
switch (i.type) {
case CMakeProjectManager::ConfigModel::DataItem::BOOLEAN:
ni.type = CMakeConfigItem::BOOL;
break;
case CMakeProjectManager::ConfigModel::DataItem::FILE:
ni.type = CMakeConfigItem::FILEPATH;
break;
case CMakeProjectManager::ConfigModel::DataItem::DIRECTORY:
ni.type = CMakeConfigItem::PATH;
break;
case CMakeProjectManager::ConfigModel::DataItem::STRING:
ni.type = CMakeConfigItem::STRING;
break;
case CMakeProjectManager::ConfigModel::DataItem::UNKNOWN:
default:
ni.type = CMakeConfigItem::INTERNAL;
break;
}
return ni;
});
// There is a buildDirManager, so there must also be an active BC:
const CMakeConfig config = cmakeConfiguration() + newConfig;
setCMakeConfiguration(config);
m_buildDirManager->forceReparse();
}
void CMakeBuildConfiguration::emitBuildTypeChanged()
{
emit buildTypeChanged();
......
......@@ -26,6 +26,7 @@
#pragma once
#include "cmakeconfigitem.h"
#include "configmodel.h"
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/abi.h>
......@@ -38,7 +39,9 @@ class CMakeProject;
namespace Internal {
class BuildDirManager;
class CMakeBuildConfigurationFactory;
class CMakeBuildSettingsWidget;
class CMakeBuildConfiguration : public ProjectExplorer::BuildConfiguration
{
......@@ -47,6 +50,7 @@ class CMakeBuildConfiguration : public ProjectExplorer::BuildConfiguration
public:
CMakeBuildConfiguration(ProjectExplorer::Target *parent);
~CMakeBuildConfiguration();
bool isEnabled() const override;
QString disabledReason() const override;
......@@ -60,24 +64,44 @@ public:
void emitBuildTypeChanged();
void setCMakeConfiguration(const CMakeConfig &config);
bool hasCMakeConfiguration() const;
CMakeConfig cmakeConfiguration() const;
void setError(const QString &message);
QString error() const;
BuildDirManager *buildDirManager() const;
bool isParsing() const;
void parse();
void resetData();
signals:
void errorOccured(const QString &message);
void parsingStarted();
void dataAvailable();
protected:
CMakeBuildConfiguration(ProjectExplorer::Target *parent, CMakeBuildConfiguration *source);
bool fromMap(const QVariantMap &map) override;
private:
QList<ConfigModel::DataItem> completeCMakeConfiguration() const;
void setCurrentCMakeConfiguration(const QList<ConfigModel::DataItem> &items);
void setError(const QString &message);
QString m_initialArguments;
CMakeConfig m_configuration;