diff --git a/src/plugins/cmakeprojectmanager/builddirreader.h b/src/plugins/cmakeprojectmanager/builddirreader.h index ce08966f17b..8d47d5d288a 100644 --- a/src/plugins/cmakeprojectmanager/builddirreader.h +++ b/src/plugins/cmakeprojectmanager/builddirreader.h @@ -86,13 +86,14 @@ public: }; static BuildDirReader *createReader(const BuildDirReader::Parameters &p); - void setParameters(const Parameters &p); + virtual void setParameters(const Parameters &p); virtual bool isCompatible(const Parameters &p) = 0; virtual void resetData() = 0; virtual void parse(bool force) = 0; virtual void stop() = 0; + virtual bool isReady() const { return true; } virtual bool isParsing() const = 0; virtual bool hasData() const = 0; @@ -102,6 +103,7 @@ public: virtual QSet updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder) = 0; signals: + void isReadyNow() const; void configurationStarted() const; void dataAvailable() const; void dirty() const; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro index 678cdbf1bc0..658564b177c 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro @@ -31,6 +31,7 @@ HEADERS = builddirmanager.h \ configmodel.h \ configmodelitemdelegate.h \ servermode.h \ + servermodereader.h \ tealeafreader.h SOURCES = builddirmanager.cpp \ @@ -60,6 +61,7 @@ SOURCES = builddirmanager.cpp \ configmodel.cpp \ configmodelitemdelegate.cpp \ servermode.cpp \ + servermodereader.cpp \ tealeafreader.cpp RESOURCES += cmakeproject.qrc diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 3bef712ddc2..6bf3e624b78 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -76,6 +76,8 @@ QtcPlugin { "configmodelitemdelegate.h", "servermode.cpp", "servermode.h", + "servermodereader.cpp", + "servermodereader.h", "tealeafreader.cpp", "tealeafreader.h" ] diff --git a/src/plugins/cmakeprojectmanager/servermodereader.cpp b/src/plugins/cmakeprojectmanager/servermodereader.cpp new file mode 100644 index 00000000000..f79973bfc1d --- /dev/null +++ b/src/plugins/cmakeprojectmanager/servermodereader.cpp @@ -0,0 +1,459 @@ +/**************************************************************************** +** +** 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 "servermodereader.h" + +#include "cmakebuildconfiguration.h" +#include "cmakeprojectconstants.h" +#include "cmakeprojectmanager.h" +#include "cmakeprojectnodes.h" +#include "servermode.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace CMakeProjectManager { +namespace Internal { + +const char CACHE_TYPE[] = "cache"; +const char CODEMODEL_TYPE[] = "codemodel"; +const char CONFIGURE_TYPE[] = "configure"; +const char CMAKE_INPUTS_TYPE[] = "cmakeInputs"; +const char COMPUTE_TYPE[] = "compute"; + +const char NAME_KEY[] = "name"; +const char SOURCE_DIRECTORY_KEY[] = "sourceDirectory"; +const char SOURCES_KEY[] = "sources"; + +const int MAX_PROGRESS = 1400; + +// ---------------------------------------------------------------------- +// Helpers: +// ---------------------------------------------------------------------- + +QString socketName(const BuildDirReader::Parameters &p) +{ + return p.buildDirectory.toString() + "/socket"; +} + +// -------------------------------------------------------------------- +// ServerModeReader: +// -------------------------------------------------------------------- + +ServerModeReader::ServerModeReader() +{ + connect(Core::EditorManager::instance(), &Core::EditorManager::aboutToSave, + this, [this](const Core::IDocument *document) { + if (m_cmakeFiles.contains(document->filePath())) + emit dirty(); + }); +} + +ServerModeReader::~ServerModeReader() +{ + stop(); +} + +void ServerModeReader::setParameters(const BuildDirReader::Parameters &p) +{ + BuildDirReader::setParameters(p); + if (!m_cmakeServer) { + m_cmakeServer.reset(new ServerMode(p.environment, + p.sourceDirectory, p.buildDirectory, p.cmakeExecutable, + p.generator, p.extraGenerator, p.platform, p.toolset, + true, 1)); + connect(m_cmakeServer.get(), &ServerMode::errorOccured, + this, &ServerModeReader::errorOccured); + connect(m_cmakeServer.get(), &ServerMode::cmakeReply, + this, &ServerModeReader::handleReply); + connect(m_cmakeServer.get(), &ServerMode::cmakeError, + this, &ServerModeReader::handleError); + connect(m_cmakeServer.get(), &ServerMode::cmakeProgress, + this, &ServerModeReader::handleProgress); + connect(m_cmakeServer.get(), &ServerMode::cmakeMessage, + this, [this](const QString &m) { Core::MessageManager::write(m); }); + connect(m_cmakeServer.get(), &ServerMode::message, + this, [](const QString &m) { Core::MessageManager::write(m); }); + connect(m_cmakeServer.get(), &ServerMode::connected, + this, &ServerModeReader::isReadyNow); + connect(m_cmakeServer.get(), &ServerMode::disconnected, + this, [this]() { m_cmakeServer.reset(); }); + } +} + +bool ServerModeReader::isCompatible(const BuildDirReader::Parameters &p) +{ + // Server mode connection got lost, reset... + if (!m_parameters.cmakeExecutable.isEmpty() && !m_cmakeServer) + return false; + + return p.cmakeHasServerMode + && p.cmakeExecutable == m_parameters.cmakeExecutable + && p.environment == m_parameters.environment + && p.generator == m_parameters.generator + && p.extraGenerator == m_parameters.extraGenerator + && p.platform == m_parameters.platform + && p.toolset == m_parameters.toolset + && p.sourceDirectory == m_parameters.sourceDirectory + && p.buildDirectory == m_parameters.buildDirectory; +} + +void ServerModeReader::resetData() +{ + m_hasData = false; +} + +void ServerModeReader::parse(bool force) +{ + QTC_ASSERT(m_cmakeServer, return); + QVariantMap extra; + if (force) + extra.insert("cacheArguments", QVariant(transform(m_parameters.configuration, + [this](const CMakeConfigItem &i) { + return i.toArgument(m_parameters.expander); + }))); + + m_future = new QFutureInterface(); + m_future->setProgressRange(0, MAX_PROGRESS); + m_progressStepMinimum = 0; + m_progressStepMaximum = 1000; + Core::ProgressManager::addTask(m_future->future(), + tr("Configuring \"%1\"").arg(m_parameters.projectName), + "CMake.Configure"); + + m_cmakeServer->sendRequest(CONFIGURE_TYPE, extra); +} + +void ServerModeReader::stop() +{ + if (m_future) { + m_future->reportCanceled(); + m_future->reportFinished(); + delete m_future; + m_future = nullptr; + } +} + +bool ServerModeReader::isReady() const +{ + return m_cmakeServer->isConnected(); +} + +bool ServerModeReader::isParsing() const +{ + return false; +} + +bool ServerModeReader::hasData() const +{ + return m_hasData; +} + +QList ServerModeReader::buildTargets() const +{ + return transform(m_targets, [](const Target *t) -> CMakeBuildTarget { + CMakeBuildTarget ct; + ct.title = t->name; + ct.executable = t->artifacts.isEmpty() ? FileName() : t->artifacts.at(0); + TargetType type = UtilityType; + if (t->type == "STATIC_LIBRARY") + type = StaticLibraryType; + else if (t->type == "MODULE_LIBRARY" + || t->type == "SHARED_LIBRARY" + || t->type == "INTERFACE_LIBRARY" + || t->type == "OBJECT_LIBRARY") + type = DynamicLibraryType; + else + type = UtilityType; + ct.targetType = type; + ct.workingDirectory = t->buildDirectory; + ct.sourceDirectory = t->sourceDirectory; + return ct; + }); +} + +CMakeConfig ServerModeReader::parsedConfiguration() const +{ + return m_cmakeCache; +} + +void ServerModeReader::generateProjectTree(CMakeProjectNode *root) +{ + QSet knownFiles; + for (auto it = m_cmakeInputsFileNodes.constBegin(); it != m_cmakeInputsFileNodes.constEnd(); ++it) + knownFiles.insert((*it)->filePath()); + + QList fileGroupNodes = m_cmakeInputsFileNodes; + for (const FileGroup *fg : qAsConst(m_fileGroups)) { + for (const FileName &s : fg->sources) { + const int oldCount = knownFiles.count(); + knownFiles.insert(s); + if (oldCount != knownFiles.count()) + fileGroupNodes.append(new FileNode(s, SourceType, fg->isGenerated)); + } + } + root->buildTree(fileGroupNodes); +} + +QSet ServerModeReader::updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder) +{ + QSet languages; + int counter = 0; + for (const FileGroup *fg : qAsConst(m_fileGroups)) { + ++counter; + const QString defineArg + = transform(fg->defines, [](const QString &s) -> QString { return QString::fromLatin1("#define ") + s; }).join('\n'); + const QStringList flags = QtcProcess::splitArgs(fg->compileFlags); + const QStringList includes = transform(fg->includePaths, [](const IncludePath *ip) { return ip->path.toString(); }); + + ppBuilder.setProjectFile(fg->target->sourceDirectory.toString()); + ppBuilder.setDisplayName(fg->target->name + QString::number(counter)); + ppBuilder.setDefines(defineArg.toUtf8()); + ppBuilder.setIncludePaths(includes); + ppBuilder.setCFlags(flags); + ppBuilder.setCxxFlags(flags); + + + languages.unite(QSet::fromList(ppBuilder.createProjectPartsForFiles(transform(fg->sources, &FileName::toString)))); + } + return languages; +} + +void ServerModeReader::handleReply(const QVariantMap &data, const QString &inReplyTo) +{ + Q_UNUSED(data); + if (inReplyTo == CONFIGURE_TYPE) { + m_cmakeServer->sendRequest(COMPUTE_TYPE); + if (m_future) + m_future->setProgressValue(1000); + m_progressStepMinimum = m_progressStepMaximum; + m_progressStepMaximum = 1100; + } else if (inReplyTo == COMPUTE_TYPE) { + m_cmakeServer->sendRequest(CODEMODEL_TYPE); + if (m_future) + m_future->setProgressValue(1100); + m_progressStepMinimum = m_progressStepMaximum; + m_progressStepMaximum = 1200; + } else if (inReplyTo == CODEMODEL_TYPE) { + extractCodeModelData(data); + m_cmakeServer->sendRequest(CMAKE_INPUTS_TYPE); + if (m_future) + m_future->setProgressValue(1200); + m_progressStepMinimum = m_progressStepMaximum; + m_progressStepMaximum = 1300; + } else if (inReplyTo == CMAKE_INPUTS_TYPE) { + extractCMakeInputsData(data); + m_cmakeServer->sendRequest(CACHE_TYPE); + if (m_future) + m_future->setProgressValue(1300); + m_progressStepMinimum = m_progressStepMaximum; + m_progressStepMaximum = 1400; + } else if (inReplyTo == CACHE_TYPE) { + extractCacheData(data); + if (m_future) { + m_future->setProgressValue(MAX_PROGRESS); + m_future->reportFinished(); + delete m_future; + m_future = nullptr; + } + m_hasData = true; + emit dataAvailable(); + } +} + +void ServerModeReader::handleError(const QString &message) +{ + TaskHub::addTask(Task::Error, message, ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM, + Utils::FileName(), -1); + stop(); +} + +void ServerModeReader::handleProgress(int min, int cur, int max, const QString &inReplyTo) +{ + Q_UNUSED(inReplyTo); + + if (!m_future) + return; + int progress = m_progressStepMinimum + + (((max - min) / (cur - min)) * (m_progressStepMaximum - m_progressStepMinimum)); + m_future->setProgressValue(progress); +} + +void ServerModeReader::extractCodeModelData(const QVariantMap &data) +{ + const QVariantList configs = data.value("configurations").toList(); + QTC_CHECK(configs.count() == 1); // FIXME: Support several configurations! + for (const QVariant &c : configs) { + const QVariantMap &cData = c.toMap(); + extractConfigurationData(cData); + } +} + +void ServerModeReader::extractConfigurationData(const QVariantMap &data) +{ + const QString name = data.value(NAME_KEY).toString(); + Q_UNUSED(name); + const QVariantList projects = data.value("projects").toList(); + for (const QVariant &p : projects) { + const QVariantMap pData = p.toMap(); + m_projects.append(extractProjectData(pData)); + } +} + +ServerModeReader::Project *ServerModeReader::extractProjectData(const QVariantMap &data) +{ + auto project = new Project; + project->name = data.value(NAME_KEY).toString(); + project->sourceDirectory = FileName::fromString(data.value(SOURCE_DIRECTORY_KEY).toString()); + + const QVariantList targets = data.value("targets").toList(); + for (const QVariant &t : targets) { + const QVariantMap tData = t.toMap(); + project->targets.append(extractTargetData(tData, project)); + } + return project; +} + +ServerModeReader::Target *ServerModeReader::extractTargetData(const QVariantMap &data, Project *p) +{ + auto target = new Target; + target->project = p; + target->name = data.value(NAME_KEY).toString(); + target->sourceDirectory = FileName::fromString(data.value(SOURCE_DIRECTORY_KEY).toString()); + target->buildDirectory = FileName::fromString(data.value("buildDirectory").toString()); + + QDir srcDir(target->sourceDirectory.toString()); + + target->type = data.value("type").toString(); + const QStringList artifacts = data.value("artifacts").toStringList(); + target->artifacts = transform(artifacts, [&srcDir](const QString &a) { return FileName::fromString(srcDir.absoluteFilePath(a)); }); + + const QVariantList fileGroups = data.value("fileGroups").toList(); + for (const QVariant &fg : fileGroups) { + const QVariantMap fgData = fg.toMap(); + target->fileGroups.append(extractFileGroupData(fgData, srcDir, target)); + } + + m_targets.append(target); + return target; +} + +ServerModeReader::FileGroup *ServerModeReader::extractFileGroupData(const QVariantMap &data, + const QDir &srcDir, + Target *t) +{ + auto fileGroup = new FileGroup; + fileGroup->target = t; + fileGroup->compileFlags = data.value("compileFlags").toString(); + fileGroup->defines = data.value("defines").toStringList(); + fileGroup->includePaths = transform(data.value("includePath").toList(), + [](const QVariant &i) -> IncludePath* { + const QVariantMap iData = i.toMap(); + auto result = new IncludePath; + result->path = FileName::fromString(iData.value("path").toString()); + result->isSystem = iData.value("isSystem", false).toBool(); + return result; + }); + fileGroup->isGenerated = data.value("isGenerated", false).toBool(); + fileGroup->sources = transform(data.value(SOURCES_KEY).toStringList(), + [&srcDir](const QString &s) { + return FileName::fromString(srcDir.absoluteFilePath(s)); + }); + + m_fileGroups.append(fileGroup); + return fileGroup; +} + +void ServerModeReader::extractCMakeInputsData(const QVariantMap &data) +{ + const FileName src = FileName::fromString(data.value(SOURCE_DIRECTORY_KEY).toString()); + QTC_ASSERT(src == m_parameters.sourceDirectory, return); + QDir srcDir(src.toString()); + + const QVariantList buildFiles = data.value("buildFiles").toList(); + for (const QVariant &bf : buildFiles) { + const QVariantMap §ion = bf.toMap(); + const QStringList sources = section.value(SOURCES_KEY).toStringList(); + + const bool isTemporary = section.value("isTemporary").toBool(); + const bool isCMake = section.value("isCMake").toBool(); + + for (const QString &s : sources) { + const FileName sfn = FileName::fromString(srcDir.absoluteFilePath(s)); + const int oldCount = m_cmakeFiles.count(); + m_cmakeFiles.insert(sfn); + if (!isCMake && oldCount < m_cmakeFiles.count()) + m_cmakeInputsFileNodes.append(new FileNode(sfn, ProjectFileType, isTemporary)); + } + } +} + +void ServerModeReader::extractCacheData(const QVariantMap &data) +{ + CMakeConfig config; + const QVariantList entries = data.value("cache").toList(); + for (const QVariant &e : entries) { + const QVariantMap eData = e.toMap(); + CMakeConfigItem item; + item.key = eData.value("key").toByteArray(); + item.value = eData.value("value").toByteArray(); + item.type = CMakeConfigItem::STRING; + const QByteArray typeId = eData.value("type").toByteArray(); + if (typeId == "FILEPATH") + item.type = CMakeConfigItem::STRING; + else if (typeId == "PATH") + item.type = CMakeConfigItem::PATH; + else if (typeId == "BOOL") + item.type = CMakeConfigItem::BOOL; + else if (typeId == "INTERNAL") + item.type = CMakeConfigItem::INTERNAL; + else if (typeId == "STRING") + item.type = CMakeConfigItem::STRING; + else if (typeId == "STATIC") + item.type = CMakeConfigItem::STATIC; + const QVariantMap properties = eData.value("properties").toMap(); + item.isAdvanced = properties.value("ADVANCED", false).toBool(); + item.documentation = properties.value("HELPSTRING").toByteArray(); + item.values = CMakeConfigItem::cmakeSplitValue(properties.value("STRINGS").toString(), true); + config.append(item); + } + m_cmakeCache = config; +} + +} // namespace Internal +} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/servermodereader.h b/src/plugins/cmakeprojectmanager/servermodereader.h new file mode 100644 index 00000000000..45d847d6b62 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/servermodereader.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** 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 "builddirreader.h" +#include "servermode.h" + +#include + +#include +#include +#include + +#include + +QT_FORWARD_DECLARE_CLASS(QLocalSocket); + +namespace Utils { class QtcProcess; } + +namespace CMakeProjectManager { +namespace Internal { + +class ServerModeReader : public BuildDirReader +{ + Q_OBJECT + +public: + ServerModeReader(); + ~ServerModeReader() final; + + void setParameters(const Parameters &p) final; + + bool isCompatible(const Parameters &p) final; + void resetData() final; + void parse(bool force) final; + void stop() final; + + bool isReady() const final; + bool isParsing() const final; + bool hasData() const final; + + QList buildTargets() const final; + CMakeConfig parsedConfiguration() const final; + void generateProjectTree(CMakeProjectNode *root) final; + QSet updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder) final; + +private: + void handleReply(const QVariantMap &data, const QString &inReplyTo); + void handleError(const QString &message); + void handleProgress(int min, int cur, int max, const QString &inReplyTo); + + struct Target; + struct Project; + + struct IncludePath { + Utils::FileName path; + bool isSystem; + }; + + struct FileGroup { + ~FileGroup() { qDeleteAll(includePaths); includePaths.clear(); } + + Target *target = nullptr; + QString compileFlags; + QStringList defines; + QList includePaths; + QString language; + QList sources; + bool isGenerated; + }; + + struct Target { + ~Target() { qDeleteAll(fileGroups); fileGroups.clear(); } + + Project *project = nullptr; + QString name; + QString type; + QList artifacts; + Utils::FileName sourceDirectory; + Utils::FileName buildDirectory; + QList fileGroups; + }; + + struct Project { + ~Project() { qDeleteAll(targets); targets.clear(); } + QString name; + Utils::FileName sourceDirectory; + QList targets; + }; + + void extractCodeModelData(const QVariantMap &data); + void extractConfigurationData(const QVariantMap &data); + Project *extractProjectData(const QVariantMap &data); + Target *extractTargetData(const QVariantMap &data, Project *p); + FileGroup *extractFileGroupData(const QVariantMap &data, const QDir &srcDir, Target *t); + void extractCMakeInputsData(const QVariantMap &data); + void extractCacheData(const QVariantMap &data); + + bool m_hasData = false; + + std::unique_ptr m_cmakeServer; + QFutureInterface *m_future = nullptr; + + int m_progressStepMinimum; + int m_progressStepMaximum; + + CMakeConfig m_cmakeCache; + + QSet m_cmakeFiles; + QList m_cmakeInputsFileNodes; + + QList m_projects; + QList m_targets; + QList m_fileGroups; +}; + +} // namespace Internal +} // namespace CMakeProjectManager