forked from qt-creator/qt-creator
CMake: Remove servermode support
Task-number: QTCREATORBUG-23915 Change-Id: I2a58e1d6d95c28e25787722fa37448d86c4aebc9 Reviewed-by: Eike Ziller <eike.ziller@qt.io> Reviewed-by: David Schulz <david.schulz@qt.io> Reviewed-by: hjk <hjk@qt.io> Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
@@ -60,11 +60,8 @@
|
||||
|
||||
\section1 Adding CMake Tools
|
||||
|
||||
\QC supports CMake version 3.7, or later.
|
||||
|
||||
For best results use CMake version 3.14, or later, as \QC supports the
|
||||
\l {https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html}
|
||||
{file-based API}.
|
||||
\QC requires CMake's {https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html}
|
||||
{file-based API}. Please make sure to use CMake version 3.14, or later.
|
||||
|
||||
To specify paths to CMake executables:
|
||||
|
||||
|
@@ -40,6 +40,4 @@ add_qtc_plugin(CMakeProjectManager
|
||||
fileapiparser.cpp fileapiparser.h
|
||||
fileapireader.cpp fileapireader.h
|
||||
projecttreehelper.cpp projecttreehelper.h
|
||||
servermode.cpp servermode.h
|
||||
servermodereader.cpp servermodereader.h
|
||||
)
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "builddirreader.h"
|
||||
|
||||
#include "fileapireader.h"
|
||||
#include "servermodereader.h"
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
@@ -50,8 +49,6 @@ std::unique_ptr<BuildDirReader> BuildDirReader::createReader(const BuildDirParam
|
||||
switch (type.value()) {
|
||||
case CMakeTool::FileApi:
|
||||
return std::make_unique<FileApiReader>();
|
||||
case CMakeTool::ServerMode:
|
||||
return std::make_unique<ServerModeReader>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -254,10 +254,10 @@ Tasks CMakeKitAspect::validate(const Kit *k) const
|
||||
CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
|
||||
if (tool) {
|
||||
CMakeTool::Version version = tool->version();
|
||||
if (version.major < 3 || (version.major == 3 && version.minor < 7)) {
|
||||
if (version.major < 3 || (version.major == 3 && version.minor < 14)) {
|
||||
result << BuildSystemTask(Task::Warning,
|
||||
tr("CMake version %1 is unsupported. Please update to "
|
||||
"version 3.7 or later.")
|
||||
"version 3.14 (with file-api) or later.")
|
||||
.arg(QString::fromUtf8(version.fullVersion)));
|
||||
}
|
||||
}
|
||||
@@ -717,9 +717,8 @@ Tasks CMakeGeneratorKitAspect::validate(const Kit *k) const
|
||||
if (!it->supportsToolset && !info.toolset.isEmpty())
|
||||
addWarning(tr("Toolset is not supported by the selected CMake generator."));
|
||||
}
|
||||
if (!tool->hasServerMode() && !tool->hasFileApi() && info.extraGenerator != "CodeBlocks") {
|
||||
addWarning(tr("The selected CMake binary has no server-mode and the CMake "
|
||||
"generator does not generate a CodeBlocks file. "
|
||||
if (!tool->hasFileApi()) {
|
||||
addWarning(tr("The selected CMake binary does not support file-api. "
|
||||
"%1 will not be able to parse CMake projects.")
|
||||
.arg(Core::Constants::IDE_DISPLAY_NAME));
|
||||
}
|
||||
|
@@ -36,9 +36,7 @@ HEADERS = builddirmanager.h \
|
||||
fileapidataextractor.h \
|
||||
fileapiparser.h \
|
||||
fileapireader.h \
|
||||
projecttreehelper.h \
|
||||
servermode.h \
|
||||
servermodereader.h
|
||||
projecttreehelper.h
|
||||
|
||||
SOURCES = builddirmanager.cpp \
|
||||
builddirparameters.cpp \
|
||||
@@ -72,9 +70,7 @@ SOURCES = builddirmanager.cpp \
|
||||
fileapidataextractor.cpp \
|
||||
fileapiparser.cpp \
|
||||
fileapireader.cpp \
|
||||
projecttreehelper.cpp \
|
||||
servermode.cpp \
|
||||
servermodereader.cpp
|
||||
projecttreehelper.cpp
|
||||
|
||||
RESOURCES += cmakeproject.qrc
|
||||
|
||||
|
@@ -89,10 +89,6 @@ QtcPlugin {
|
||||
"fileapireader.cpp",
|
||||
"fileapireader.h",
|
||||
"projecttreehelper.cpp",
|
||||
"projecttreehelper.h",
|
||||
"servermode.cpp",
|
||||
"servermode.h",
|
||||
"servermodereader.cpp",
|
||||
"servermodereader.h"
|
||||
"projecttreehelper.h"
|
||||
]
|
||||
}
|
||||
|
@@ -54,9 +54,6 @@ private slots:
|
||||
|
||||
void testCMakeProjectImporterToolChain_data();
|
||||
void testCMakeProjectImporterToolChain();
|
||||
|
||||
void testServerModeReaderProgress_data();
|
||||
void testServerModeReaderProgress();
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
@@ -114,14 +114,13 @@ public:
|
||||
, m_isAutoRun(item->isAutoRun())
|
||||
, m_autoCreateBuildDirectory(item->autoCreateBuildDirectory())
|
||||
, m_autodetected(item->isAutoDetected())
|
||||
, m_isSupported(item->hasFileApi() || item->hasServerMode())
|
||||
, m_isSupported(item->hasFileApi())
|
||||
, m_changed(changed)
|
||||
{
|
||||
updateErrorFlags();
|
||||
m_tooltip = tr("Version: %1<br>Supports fileApi: %2<br>Supports server-mode: %3")
|
||||
m_tooltip = tr("Version: %1<br>Supports fileApi: %2")
|
||||
.arg(QString::fromUtf8(item->version().fullVersion))
|
||||
.arg(item->hasFileApi() ? tr("yes") : tr("no"))
|
||||
.arg(item->hasServerMode() ? tr("yes") : tr("no"));
|
||||
.arg(item->hasFileApi() ? tr("yes") : tr("no"));
|
||||
}
|
||||
|
||||
CMakeToolTreeItem(const QString &name,
|
||||
|
@@ -58,20 +58,11 @@ bool CMakeTool::Generator::matches(const QString &n, const QString &ex) const
|
||||
|
||||
namespace Internal {
|
||||
|
||||
const char READER_TYPE_SERVERMODE[] = "servermode";
|
||||
const char READER_TYPE_FILEAPI[] = "fileapi";
|
||||
|
||||
static bool ignoreFileApi()
|
||||
{
|
||||
static bool s_ignoreFileApi = qEnvironmentVariableIsSet("QTC_CMAKE_IGNORE_FILEAPI");
|
||||
return s_ignoreFileApi;
|
||||
}
|
||||
|
||||
static Utils::optional<CMakeTool::ReaderType> readerTypeFromString(const QString &input)
|
||||
{
|
||||
// Do not try to be clever here, just use whatever is in the string!
|
||||
if (input == READER_TYPE_SERVERMODE)
|
||||
return CMakeTool::ServerMode;
|
||||
if (input == READER_TYPE_FILEAPI)
|
||||
return CMakeTool::FileApi;
|
||||
return {};
|
||||
@@ -80,8 +71,6 @@ static Utils::optional<CMakeTool::ReaderType> readerTypeFromString(const QString
|
||||
static QString readerTypeToString(const CMakeTool::ReaderType &type)
|
||||
{
|
||||
switch (type) {
|
||||
case CMakeTool::ServerMode:
|
||||
return QString(READER_TYPE_SERVERMODE);
|
||||
case CMakeTool::FileApi:
|
||||
return QString(READER_TYPE_FILEAPI);
|
||||
}
|
||||
@@ -102,9 +91,7 @@ class IntrospectionData
|
||||
public:
|
||||
bool m_didAttemptToRun = false;
|
||||
bool m_didRun = true;
|
||||
bool m_hasServerMode = false;
|
||||
|
||||
bool m_queriedServerMode = false;
|
||||
bool m_triedCapabilities = false;
|
||||
|
||||
QList<CMakeTool::Generator> m_generators;
|
||||
@@ -193,13 +180,13 @@ void CMakeTool::setAutoCreateBuildDirectory(bool autoBuildDir)
|
||||
|
||||
bool CMakeTool::isValid() const
|
||||
{
|
||||
if (!m_id.isValid())
|
||||
if (!m_id.isValid() || !m_introspection)
|
||||
return false;
|
||||
|
||||
if (!m_introspection->m_didAttemptToRun)
|
||||
supportedGenerators();
|
||||
readInformation();
|
||||
|
||||
return m_introspection->m_didRun && readerType().has_value();
|
||||
return m_introspection->m_didRun && !m_introspection->m_fileApis.isEmpty();
|
||||
}
|
||||
|
||||
Utils::SynchronousProcessResponse CMakeTool::run(const QStringList &args, int timeoutS) const
|
||||
@@ -284,12 +271,14 @@ bool CMakeTool::autoCreateBuildDirectory() const
|
||||
|
||||
QList<CMakeTool::Generator> CMakeTool::supportedGenerators() const
|
||||
{
|
||||
readInformation(QueryType::GENERATORS);
|
||||
return m_introspection->m_generators;
|
||||
return isValid() ? m_introspection->m_generators : QList<CMakeTool::Generator>();
|
||||
}
|
||||
|
||||
TextEditor::Keywords CMakeTool::keywords()
|
||||
{
|
||||
if (!isValid())
|
||||
return {};
|
||||
|
||||
if (m_introspection->m_functions.isEmpty() && m_introspection->m_didRun) {
|
||||
Utils::SynchronousProcessResponse response;
|
||||
response = run({"--help-command-list"}, 5);
|
||||
@@ -317,28 +306,19 @@ TextEditor::Keywords CMakeTool::keywords()
|
||||
m_introspection->m_functionArgs);
|
||||
}
|
||||
|
||||
bool CMakeTool::hasServerMode() const
|
||||
{
|
||||
readInformation(QueryType::SERVER_MODE);
|
||||
return m_introspection->m_hasServerMode;
|
||||
}
|
||||
|
||||
bool CMakeTool::hasFileApi() const
|
||||
{
|
||||
readInformation(QueryType::SERVER_MODE);
|
||||
return !m_introspection->m_fileApis.isEmpty();
|
||||
return isValid() ? !m_introspection->m_fileApis.isEmpty() : false;
|
||||
}
|
||||
|
||||
QVector<std::pair<QString, int>> CMakeTool::supportedFileApiObjects() const
|
||||
{
|
||||
readInformation(QueryType::SERVER_MODE);
|
||||
return Utils::transform(m_introspection->m_fileApis, [](const Internal::FileApi &api) { return std::make_pair(api.kind, api.version.first); });
|
||||
return isValid() ? Utils::transform(m_introspection->m_fileApis, [](const Internal::FileApi &api) { return std::make_pair(api.kind, api.version.first); }) : QVector<std::pair<QString, int>>();
|
||||
}
|
||||
|
||||
CMakeTool::Version CMakeTool::version() const
|
||||
{
|
||||
readInformation(QueryType::VERSION);
|
||||
return m_introspection->m_version;
|
||||
return isValid() ? m_introspection->m_version : CMakeTool::Version();
|
||||
}
|
||||
|
||||
bool CMakeTool::isAutoDetected() const
|
||||
@@ -375,10 +355,8 @@ Utils::optional<CMakeTool::ReaderType> CMakeTool::readerType() const
|
||||
return m_readerType; // Allow overriding the auto-detected value via .user files
|
||||
|
||||
// Find best possible reader type:
|
||||
if (hasFileApi() && !Internal::ignoreFileApi())
|
||||
if (hasFileApi())
|
||||
return FileApi;
|
||||
if (hasServerMode())
|
||||
return ServerMode;
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -404,33 +382,16 @@ Utils::FilePath CMakeTool::searchQchFile(const Utils::FilePath &executable)
|
||||
return {};
|
||||
}
|
||||
|
||||
void CMakeTool::readInformation(CMakeTool::QueryType type) const
|
||||
void CMakeTool::readInformation() const
|
||||
{
|
||||
QTC_ASSERT(m_introspection, return );
|
||||
if (!m_introspection->m_didRun && m_introspection->m_didAttemptToRun)
|
||||
return;
|
||||
|
||||
m_introspection->m_didAttemptToRun = true;
|
||||
|
||||
if (!m_introspection->m_triedCapabilities) {
|
||||
fetchFromCapabilities();
|
||||
m_introspection->m_triedCapabilities = true;
|
||||
m_introspection->m_queriedServerMode = true; // Got added after "-E capabilities" support!
|
||||
} else {
|
||||
if ((type == QueryType::GENERATORS && !m_introspection->m_generators.isEmpty())
|
||||
|| (type == QueryType::SERVER_MODE && m_introspection->m_queriedServerMode)
|
||||
|| (type == QueryType::VERSION && !m_introspection->m_version.fullVersion.isEmpty()))
|
||||
return;
|
||||
|
||||
if (type == QueryType::GENERATORS) {
|
||||
fetchGeneratorsFromHelp();
|
||||
} else if (type == QueryType::SERVER_MODE) {
|
||||
// Nothing to do...
|
||||
} else if (type == QueryType::VERSION) {
|
||||
fetchVersionFromVersionOutput();
|
||||
} else {
|
||||
QTC_ASSERT(false, return );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QStringList parseDefinition(const QString &definition)
|
||||
@@ -524,96 +485,16 @@ QStringList CMakeTool::parseVariableOutput(const QString &output)
|
||||
return result;
|
||||
}
|
||||
|
||||
void CMakeTool::fetchGeneratorsFromHelp() const
|
||||
{
|
||||
Utils::SynchronousProcessResponse response = run({"--help"});
|
||||
m_introspection->m_didRun = m_introspection->m_didRun
|
||||
&& response.result == Utils::SynchronousProcessResponse::Finished;
|
||||
|
||||
if (response.result == Utils::SynchronousProcessResponse::Finished)
|
||||
parseGeneratorsFromHelp(response.stdOut().split('\n'));
|
||||
}
|
||||
|
||||
void CMakeTool::parseGeneratorsFromHelp(const QStringList &lines) const
|
||||
{
|
||||
bool inGeneratorSection = false;
|
||||
QHash<QString, QStringList> generatorInfo;
|
||||
foreach (const QString &line, lines) {
|
||||
if (line.isEmpty())
|
||||
continue;
|
||||
if (line == "Generators") {
|
||||
inGeneratorSection = true;
|
||||
continue;
|
||||
}
|
||||
if (!inGeneratorSection)
|
||||
continue;
|
||||
|
||||
if (line.startsWith(" ") && line.at(3) != ' ') {
|
||||
int pos = line.indexOf('=');
|
||||
if (pos < 0)
|
||||
pos = line.length();
|
||||
if (pos >= 0) {
|
||||
--pos;
|
||||
while (pos > 2 && line.at(pos).isSpace())
|
||||
--pos;
|
||||
}
|
||||
if (pos > 2) {
|
||||
const QString fullName = line.mid(2, pos - 1);
|
||||
const int dashPos = fullName.indexOf(" - ");
|
||||
QString generator;
|
||||
QString extra;
|
||||
if (dashPos < 0) {
|
||||
generator = fullName;
|
||||
} else {
|
||||
extra = fullName.mid(0, dashPos);
|
||||
generator = fullName.mid(dashPos + 3);
|
||||
}
|
||||
QStringList value = generatorInfo.value(generator);
|
||||
if (!extra.isEmpty())
|
||||
value.append(extra);
|
||||
generatorInfo.insert(generator, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate genertor list:
|
||||
for (auto it = generatorInfo.constBegin(); it != generatorInfo.constEnd(); ++it)
|
||||
m_introspection->m_generators.append(Generator(it.key(), it.value()));
|
||||
}
|
||||
|
||||
void CMakeTool::fetchVersionFromVersionOutput() const
|
||||
{
|
||||
Utils::SynchronousProcessResponse response = run({"--version"});
|
||||
|
||||
m_introspection->m_didRun = m_introspection->m_didRun
|
||||
&& response.result == Utils::SynchronousProcessResponse::Finished;
|
||||
|
||||
if (response.result == Utils::SynchronousProcessResponse::Finished)
|
||||
parseVersionFormVersionOutput(response.stdOut().split('\n'));
|
||||
}
|
||||
|
||||
void CMakeTool::parseVersionFormVersionOutput(const QStringList &lines) const
|
||||
{
|
||||
QRegularExpression versionLine("^cmake.* version ((\\d+).(\\d+).(\\d+).*)$");
|
||||
for (const QString &line : lines) {
|
||||
QRegularExpressionMatch match = versionLine.match(line);
|
||||
if (!match.hasMatch())
|
||||
continue;
|
||||
|
||||
m_introspection->m_version.major = match.captured(2).toInt();
|
||||
m_introspection->m_version.minor = match.captured(3).toInt();
|
||||
m_introspection->m_version.patch = match.captured(4).toInt();
|
||||
m_introspection->m_version.fullVersion = match.captured(1).toUtf8();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CMakeTool::fetchFromCapabilities() const
|
||||
{
|
||||
Utils::SynchronousProcessResponse response = run({"-E", "capabilities"});
|
||||
|
||||
if (response.result == Utils::SynchronousProcessResponse::Finished)
|
||||
if (response.result == Utils::SynchronousProcessResponse::Finished) {
|
||||
m_introspection->m_didRun = true;
|
||||
parseFromCapabilities(response.stdOut());
|
||||
} else {
|
||||
m_introspection->m_didRun = false;
|
||||
}
|
||||
}
|
||||
|
||||
static int getVersion(const QVariantMap &obj, const QString value)
|
||||
@@ -632,7 +513,6 @@ void CMakeTool::parseFromCapabilities(const QString &input) const
|
||||
return;
|
||||
|
||||
const QVariantMap data = doc.object().toVariantMap();
|
||||
m_introspection->m_hasServerMode = data.value("serverMode").toBool();
|
||||
const QVariantList generatorList = data.value("generators").toList();
|
||||
for (const QVariant &v : generatorList) {
|
||||
const QVariantMap gen = v.toMap();
|
||||
|
@@ -47,7 +47,7 @@ class CMAKE_EXPORT CMakeTool
|
||||
public:
|
||||
enum Detection { ManualDetection, AutoDetection };
|
||||
|
||||
enum ReaderType { ServerMode, FileApi };
|
||||
enum ReaderType { FileApi };
|
||||
|
||||
struct Version
|
||||
{
|
||||
@@ -98,7 +98,6 @@ public:
|
||||
bool autoCreateBuildDirectory() const;
|
||||
QList<Generator> supportedGenerators() const;
|
||||
TextEditor::Keywords keywords();
|
||||
bool hasServerMode() const;
|
||||
bool hasFileApi() const;
|
||||
QVector<std::pair<QString, int>> supportedFileApiObjects() const;
|
||||
Version version() const;
|
||||
@@ -115,21 +114,12 @@ public:
|
||||
static Utils::FilePath searchQchFile(const Utils::FilePath &executable);
|
||||
|
||||
private:
|
||||
enum class QueryType {
|
||||
GENERATORS,
|
||||
SERVER_MODE,
|
||||
VERSION
|
||||
};
|
||||
void readInformation(QueryType type) const;
|
||||
void readInformation() const;
|
||||
|
||||
Utils::SynchronousProcessResponse run(const QStringList &args, int timeoutS = 1) const;
|
||||
void parseFunctionDetailsOutput(const QString &output);
|
||||
QStringList parseVariableOutput(const QString &output);
|
||||
|
||||
void fetchGeneratorsFromHelp() const;
|
||||
void parseGeneratorsFromHelp(const QStringList &lines) const;
|
||||
void fetchVersionFromVersionOutput() const;
|
||||
void parseVersionFormVersionOutput(const QStringList &lines) const;
|
||||
void fetchFromCapabilities() const;
|
||||
void parseFromCapabilities(const QString &input) const;
|
||||
|
||||
|
@@ -97,13 +97,13 @@ void addCMakeInputs(FolderNode *root,
|
||||
addCMakeVFolder(cmakeVFolder.get(),
|
||||
buildDir,
|
||||
100,
|
||||
QCoreApplication::translate("CMakeProjectManager::Internal::ServerModeReader",
|
||||
QCoreApplication::translate("CMakeProjectManager::Internal::ProjectTreeHelper",
|
||||
"<Build Directory>"),
|
||||
removeKnownNodes(knownFiles, std::move(buildInputs)));
|
||||
addCMakeVFolder(cmakeVFolder.get(),
|
||||
Utils::FilePath(),
|
||||
10,
|
||||
QCoreApplication::translate("CMakeProjectManager::Internal::ServerModeReader",
|
||||
QCoreApplication::translate("CMakeProjectManager::Internal::ProjectTreeHelper",
|
||||
"<Other Locations>"),
|
||||
removeKnownNodes(knownFiles, std::move(rootInputs)));
|
||||
|
||||
@@ -187,7 +187,8 @@ void addHeaderNodes(ProjectNode *root,
|
||||
auto headerNode = std::make_unique<VirtualFolderNode>(root->filePath());
|
||||
headerNode->setPriority(Node::DefaultPriority - 5);
|
||||
headerNode->setDisplayName(
|
||||
QCoreApplication::translate("CMakeProjectManager::Internal::ServerModeReader", "<Headers>"));
|
||||
QCoreApplication::translate("CMakeProjectManager::Internal::ProjectTreeHelper",
|
||||
"<Headers>"));
|
||||
headerNode->setIcon(headerNodeIcon);
|
||||
|
||||
// Add scanned headers:
|
||||
|
@@ -1,488 +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 "servermode.h"
|
||||
|
||||
#include <coreplugin/reaper.h>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLocalSocket>
|
||||
#include <QUuid>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace CMakeProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
const char COOKIE_KEY[] = "cookie";
|
||||
const char IN_REPLY_TO_KEY[] = "inReplyTo";
|
||||
const char NAME_KEY[] = "name";
|
||||
const char TYPE_KEY[] = "type";
|
||||
|
||||
const char ERROR_TYPE[] = "error";
|
||||
const char HANDSHAKE_TYPE[] = "handshake";
|
||||
|
||||
const char START_MAGIC[] = "\n[== \"CMake Server\" ==[\n";
|
||||
const char END_MAGIC[] = "\n]== \"CMake Server\" ==]\n";
|
||||
|
||||
static Q_LOGGING_CATEGORY(cmakeServerMode, "qtc.cmake.serverMode", QtWarningMsg);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Helpers:
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
bool isValid(const QVariant &v)
|
||||
{
|
||||
if (v.isNull())
|
||||
return false;
|
||||
if (v.type() == QVariant::String) // Workaround signals sending an empty string cookie
|
||||
return !v.toString().isEmpty();
|
||||
return true;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// ServerMode:
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
|
||||
ServerMode::ServerMode(const Environment &env,
|
||||
const FilePath &sourceDirectory, const FilePath &buildDirectory,
|
||||
const FilePath &cmakeExecutable,
|
||||
const QString &generator, const QString &extraGenerator,
|
||||
const QString &platform, const QString &toolset,
|
||||
bool experimental, int major, int minor,
|
||||
QObject *parent) :
|
||||
QObject(parent),
|
||||
#if defined(Q_OS_UNIX)
|
||||
// Some unixes (e.g. Darwin) limit the length of a local socket to about 100 char (or less).
|
||||
// Since some unixes (e.g. Darwin) also point TMPDIR to /really/long/paths we need to create
|
||||
// our own socket in a place closer to '/'.
|
||||
m_socketDir("/tmp/cmake-"),
|
||||
#endif
|
||||
m_sourceDirectory(sourceDirectory), m_buildDirectory(buildDirectory),
|
||||
m_cmakeCommand(cmakeExecutable, {}),
|
||||
m_generator(generator), m_extraGenerator(extraGenerator),
|
||||
m_platform(platform), m_toolset(toolset),
|
||||
m_useExperimental(experimental), m_majorProtocol(major), m_minorProtocol(minor)
|
||||
{
|
||||
QTC_ASSERT(!m_sourceDirectory.isEmpty() && m_sourceDirectory.exists(), return);
|
||||
QTC_ASSERT(!m_buildDirectory.isEmpty() && m_buildDirectory.exists(), return);
|
||||
|
||||
m_connectionTimer.setInterval(100);
|
||||
connect(&m_connectionTimer, &QTimer::timeout, this, &ServerMode::connectToServer);
|
||||
|
||||
m_cmakeProcess = std::make_unique<QtcProcess>();
|
||||
|
||||
m_cmakeProcess->setEnvironment(env);
|
||||
m_cmakeProcess->setWorkingDirectory(buildDirectory.toString());
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
m_socketName = m_socketDir.path() + "/socket";
|
||||
#else
|
||||
m_socketName = QString::fromLatin1("\\\\.\\pipe\\") + QUuid::createUuid().toString();
|
||||
#endif
|
||||
|
||||
connect(m_cmakeProcess.get(), &QtcProcess::started, this, [this]() { m_connectionTimer.start(); });
|
||||
connect(m_cmakeProcess.get(),
|
||||
QOverload<int, QProcess::ExitStatus>::of(&QtcProcess::finished),
|
||||
this, &ServerMode::handleCMakeFinished);
|
||||
|
||||
m_cmakeCommand.addArgs({"-E", "server", "--pipe=" + m_socketName});
|
||||
if (m_useExperimental)
|
||||
m_cmakeCommand.addArg("--experimental");
|
||||
|
||||
qCInfo(cmakeServerMode)
|
||||
<< "Preparing cmake:" << m_cmakeCommand.toUserOutput()
|
||||
<< "in" << m_buildDirectory.toString();
|
||||
m_cmakeProcess->setCommand(m_cmakeCommand);
|
||||
|
||||
// Delay start:
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
emit message(tr("Running \"%1\" in %2.")
|
||||
.arg(m_cmakeCommand.toUserOutput())
|
||||
.arg(m_buildDirectory.toUserOutput()));
|
||||
|
||||
m_cmakeProcess->start();
|
||||
});
|
||||
}
|
||||
|
||||
ServerMode::~ServerMode()
|
||||
{
|
||||
if (m_cmakeProcess)
|
||||
m_cmakeProcess->disconnect();
|
||||
if (m_cmakeSocket) {
|
||||
m_cmakeSocket->disconnect();
|
||||
m_cmakeSocket->abort();
|
||||
delete(m_cmakeSocket);
|
||||
}
|
||||
m_cmakeSocket = nullptr;
|
||||
Core::Reaper::reap(m_cmakeProcess.release());
|
||||
|
||||
qCDebug(cmakeServerMode) << "Server-Mode closed.";
|
||||
}
|
||||
|
||||
void ServerMode::sendRequest(const QString &type, const QVariantMap &extra, const QVariant &cookie)
|
||||
{
|
||||
QTC_ASSERT(m_cmakeSocket, return);
|
||||
++m_requestCounter;
|
||||
|
||||
qCInfo(cmakeServerMode) << "Sending Request" << type << "(" << cookie << ")";
|
||||
|
||||
QVariantMap data = extra;
|
||||
data.insert(TYPE_KEY, type);
|
||||
const QVariant realCookie = cookie.isNull() ? QVariant(m_requestCounter) : cookie;
|
||||
data.insert(COOKIE_KEY, realCookie);
|
||||
m_expectedReplies.push_back({type, realCookie});
|
||||
|
||||
QJsonObject object = QJsonObject::fromVariantMap(data);
|
||||
QJsonDocument document;
|
||||
document.setObject(object);
|
||||
|
||||
const QByteArray rawData = START_MAGIC + document.toJson(QJsonDocument::Compact) + END_MAGIC;
|
||||
qCDebug(cmakeServerMode) << ">>>" << rawData;
|
||||
m_cmakeSocket->write(rawData);
|
||||
m_cmakeSocket->flush();
|
||||
}
|
||||
|
||||
bool ServerMode::isConnected()
|
||||
{
|
||||
return m_cmakeSocket && m_isConnected;
|
||||
}
|
||||
|
||||
void ServerMode::connectToServer()
|
||||
{
|
||||
QTC_ASSERT(m_cmakeProcess, return);
|
||||
if (m_cmakeSocket)
|
||||
return; // We connected in the meantime...
|
||||
|
||||
static int counter = 0;
|
||||
++counter;
|
||||
|
||||
if (counter > 50) {
|
||||
counter = 0;
|
||||
m_cmakeProcess->disconnect();
|
||||
qCInfo(cmakeServerMode) << "Timeout waiting for pipe" << m_socketName;
|
||||
reportError(tr("Running \"%1\" failed: Timeout waiting for pipe \"%2\".")
|
||||
.arg(m_cmakeCommand.toUserOutput())
|
||||
.arg(m_socketName));
|
||||
|
||||
Core::Reaper::reap(m_cmakeProcess.release());
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
QTC_ASSERT(!m_cmakeSocket, return);
|
||||
|
||||
auto socket = new QLocalSocket(m_cmakeProcess.get());
|
||||
connect(socket, &QLocalSocket::readyRead, this, &ServerMode::handleRawCMakeServerData);
|
||||
|
||||
constexpr void (QLocalSocket::*LocalSocketErrorFunction)(QLocalSocket::LocalSocketError)
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
= &QLocalSocket::error;
|
||||
#else
|
||||
= &QLocalSocket::errorOccurred;
|
||||
#endif
|
||||
|
||||
connect(socket, LocalSocketErrorFunction, this, [this, socket]() {
|
||||
reportError(socket->errorString());
|
||||
m_cmakeSocket = nullptr;
|
||||
socket->disconnect();
|
||||
socket->deleteLater();
|
||||
});
|
||||
connect(socket, &QLocalSocket::connected, this, [this, socket]() { m_cmakeSocket = socket; });
|
||||
connect(socket, &QLocalSocket::disconnected, this, [this, socket]() {
|
||||
if (m_cmakeSocket)
|
||||
emit disconnected();
|
||||
m_cmakeSocket = nullptr;
|
||||
socket->disconnect();
|
||||
socket->deleteLater();
|
||||
});
|
||||
|
||||
socket->connectToServer(m_socketName);
|
||||
m_connectionTimer.start();
|
||||
}
|
||||
|
||||
void ServerMode::handleCMakeFinished(int code, QProcess::ExitStatus status)
|
||||
{
|
||||
qCInfo(cmakeServerMode) << "CMake has finished" << code << status;
|
||||
QString msg;
|
||||
if (status != QProcess::NormalExit)
|
||||
msg = tr("CMake process \"%1\" crashed.").arg(m_cmakeCommand.toUserOutput());
|
||||
else if (code != 0)
|
||||
msg = tr("CMake process \"%1\" quit with exit code %2.").arg(m_cmakeCommand.toUserOutput()).arg(code);
|
||||
|
||||
if (!msg.isEmpty()) {
|
||||
reportError(msg);
|
||||
} else {
|
||||
emit message(tr("CMake process \"%1\" quit normally.").arg(m_cmakeCommand.toUserOutput()));
|
||||
}
|
||||
|
||||
if (m_cmakeSocket) {
|
||||
m_cmakeSocket->disconnect();
|
||||
delete m_cmakeSocket;
|
||||
m_cmakeSocket = nullptr;
|
||||
}
|
||||
|
||||
if (!HostOsInfo::isWindowsHost())
|
||||
QFile::remove(m_socketName);
|
||||
|
||||
emit disconnected();
|
||||
}
|
||||
|
||||
void ServerMode::handleRawCMakeServerData()
|
||||
{
|
||||
const static QByteArray startNeedle(START_MAGIC);
|
||||
const static QByteArray endNeedle(END_MAGIC);
|
||||
|
||||
if (!m_cmakeSocket) // might happen during shutdown
|
||||
return;
|
||||
|
||||
m_buffer.append(m_cmakeSocket->readAll());
|
||||
|
||||
while (true) {
|
||||
const int startPos = m_buffer.indexOf(startNeedle);
|
||||
if (startPos >= 0) {
|
||||
const int afterStartNeedle = startPos + startNeedle.count();
|
||||
const int endPos = m_buffer.indexOf(endNeedle, afterStartNeedle);
|
||||
if (endPos > afterStartNeedle) {
|
||||
// Process JSON, remove junk and JSON-part, continue to loop with shorter buffer
|
||||
parseBuffer(m_buffer.mid(afterStartNeedle, endPos - afterStartNeedle));
|
||||
m_buffer.remove(0, endPos + endNeedle.count());
|
||||
} else {
|
||||
// Remove junk up to the start needle and break out of the loop
|
||||
if (startPos > 0)
|
||||
m_buffer.remove(0, startPos);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Keep at last startNeedle.count() characters (as that might be a
|
||||
// partial startNeedle), break out of the loop
|
||||
if (m_buffer.count() > startNeedle.count())
|
||||
m_buffer.remove(0, m_buffer.count() - startNeedle.count());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerMode::parseBuffer(const QByteArray &buffer)
|
||||
{
|
||||
qCDebug(cmakeServerMode) << "<<<" << buffer;
|
||||
QJsonDocument document = QJsonDocument::fromJson(buffer);
|
||||
if (document.isNull()) {
|
||||
reportError(tr("Failed to parse JSON from CMake server."));
|
||||
return;
|
||||
}
|
||||
QJsonObject rootObject = document.object();
|
||||
if (rootObject.isEmpty()) {
|
||||
reportError(tr("JSON data from CMake server was not a JSON object."));
|
||||
return;
|
||||
}
|
||||
|
||||
parseJson(rootObject.toVariantMap());
|
||||
}
|
||||
|
||||
void ServerMode::parseJson(const QVariantMap &data)
|
||||
{
|
||||
QString type = data.value(TYPE_KEY).toString();
|
||||
if (type == "hello") {
|
||||
qCInfo(cmakeServerMode) << "Got \"hello\" message.";
|
||||
if (m_gotHello) {
|
||||
reportError(tr("Unexpected hello received from CMake server."));
|
||||
return;
|
||||
} else {
|
||||
handleHello(data);
|
||||
m_gotHello = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!m_gotHello && type != ERROR_TYPE) {
|
||||
reportError(tr("Unexpected type \"%1\" received while waiting for \"hello\".").arg(type));
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "reply") {
|
||||
if (m_expectedReplies.empty()) {
|
||||
reportError(tr("Received a reply even though no request is open."));
|
||||
return;
|
||||
}
|
||||
const QString replyTo = data.value(IN_REPLY_TO_KEY).toString();
|
||||
const QVariant cookie = data.value(COOKIE_KEY);
|
||||
qCInfo(cmakeServerMode) << "Got \"reply\" message." << replyTo << "(" << cookie << ")";
|
||||
|
||||
const auto expected = m_expectedReplies.begin();
|
||||
if (expected->type != replyTo) {
|
||||
reportError(tr("Received a reply to a request of type \"%1\", when a request of type \"%2\" was sent.")
|
||||
.arg(replyTo).arg(expected->type));
|
||||
return;
|
||||
}
|
||||
if (expected->cookie != cookie) {
|
||||
reportError(tr("Received a reply with cookie \"%1\", when \"%2\" was expected.")
|
||||
.arg(cookie.toString()).arg(expected->cookie.toString()));
|
||||
return;
|
||||
}
|
||||
|
||||
m_expectedReplies.erase(expected);
|
||||
if (replyTo != HANDSHAKE_TYPE)
|
||||
emit cmakeReply(data, replyTo, cookie);
|
||||
else {
|
||||
m_isConnected = true;
|
||||
emit connected();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type == "error") {
|
||||
if (m_expectedReplies.empty()) {
|
||||
reportError(tr("An error was reported even though no request is open."));
|
||||
return;
|
||||
}
|
||||
const QString replyTo = data.value(IN_REPLY_TO_KEY).toString();
|
||||
const QVariant cookie = data.value(COOKIE_KEY);
|
||||
qCInfo(cmakeServerMode) << "Got \"error\" message." << replyTo << "(" << cookie << ")";
|
||||
|
||||
const auto expected = m_expectedReplies.begin();
|
||||
if (expected->type != replyTo) {
|
||||
reportError(tr("Received an error in response to a request of type \"%1\", when a request of type \"%2\" was sent.")
|
||||
.arg(replyTo).arg(expected->type));
|
||||
return;
|
||||
}
|
||||
if (expected->cookie != cookie) {
|
||||
reportError(tr("Received an error with cookie \"%1\", when \"%2\" was expected.")
|
||||
.arg(cookie.toString()).arg(expected->cookie.toString()));
|
||||
return;
|
||||
}
|
||||
|
||||
m_expectedReplies.erase(expected);
|
||||
|
||||
emit cmakeError(data.value("errorMessage").toString(), replyTo, cookie);
|
||||
if (replyTo == HANDSHAKE_TYPE) {
|
||||
Core::Reaper::reap(m_cmakeProcess.release());
|
||||
m_cmakeSocket->disconnect();
|
||||
m_cmakeSocket->disconnectFromServer();
|
||||
m_cmakeSocket = nullptr;
|
||||
emit disconnected();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type == "message") {
|
||||
const QString replyTo = data.value(IN_REPLY_TO_KEY).toString();
|
||||
const QVariant cookie = data.value(COOKIE_KEY);
|
||||
qCInfo(cmakeServerMode) << "Got \"message\" message." << replyTo << "(" << cookie << ")";
|
||||
|
||||
const auto expected = m_expectedReplies.begin();
|
||||
if (expected->type != replyTo) {
|
||||
reportError(tr("Received a message in response to a request of type \"%1\", when a request of type \"%2\" was sent.")
|
||||
.arg(replyTo).arg(expected->type));
|
||||
return;
|
||||
}
|
||||
if (expected->cookie != cookie) {
|
||||
reportError(tr("Received a message with cookie \"%1\", when \"%2\" was expected.")
|
||||
.arg(cookie.toString()).arg(expected->cookie.toString()));
|
||||
return;
|
||||
}
|
||||
|
||||
emit cmakeMessage(data.value("message").toString(), replyTo, cookie);
|
||||
return;
|
||||
}
|
||||
if (type == "progress") {
|
||||
const QString replyTo = data.value(IN_REPLY_TO_KEY).toString();
|
||||
const QVariant cookie = data.value(COOKIE_KEY);
|
||||
qCInfo(cmakeServerMode) << "Got \"progress\" message." << replyTo << "(" << cookie << ")";
|
||||
|
||||
const auto expected = m_expectedReplies.begin();
|
||||
if (expected->type != replyTo) {
|
||||
reportError(tr("Received a progress report in response to a request of type \"%1\", when a request of type \"%2\" was sent.")
|
||||
.arg(replyTo).arg(expected->type));
|
||||
return;
|
||||
}
|
||||
if (expected->cookie != cookie) {
|
||||
reportError(tr("Received a progress report with cookie \"%1\", when \"%2\" was expected.")
|
||||
.arg(cookie.toString()).arg(expected->cookie.toString()));
|
||||
return;
|
||||
}
|
||||
|
||||
emit cmakeProgress(data.value("progressMinimum").toInt(),
|
||||
data.value("progressCurrent").toInt(),
|
||||
data.value("progressMaximum").toInt(), replyTo, cookie);
|
||||
return;
|
||||
}
|
||||
if (type == "signal") {
|
||||
const QString replyTo = data.value(IN_REPLY_TO_KEY).toString();
|
||||
const QString cookie = data.value(COOKIE_KEY).toString();
|
||||
const QString name = data.value(NAME_KEY).toString();
|
||||
qCInfo(cmakeServerMode) << "Got \"signal\" message." << name << replyTo << "(" << cookie << ")";
|
||||
|
||||
if (name.isEmpty()) {
|
||||
reportError(tr("Received a signal without a name."));
|
||||
return;
|
||||
}
|
||||
if (!replyTo.isEmpty() || isValid(cookie)) {
|
||||
reportError(tr("Received a signal in reply to a request."));
|
||||
return;
|
||||
}
|
||||
|
||||
emit cmakeSignal(name, data);
|
||||
return;
|
||||
}
|
||||
reportError("Got a message of an unknown type.");
|
||||
}
|
||||
|
||||
void ServerMode::handleHello(const QVariantMap &data)
|
||||
{
|
||||
Q_UNUSED(data)
|
||||
QVariantMap extra;
|
||||
QVariantMap version;
|
||||
version.insert("major", m_majorProtocol);
|
||||
if (m_minorProtocol >= 0)
|
||||
version.insert("minor", m_minorProtocol);
|
||||
extra.insert("protocolVersion", version);
|
||||
extra.insert("sourceDirectory", m_sourceDirectory.toString());
|
||||
extra.insert("buildDirectory", m_buildDirectory.toString());
|
||||
extra.insert("generator", m_generator);
|
||||
if (!m_platform.isEmpty())
|
||||
extra.insert("platform", m_platform);
|
||||
if (!m_toolset.isEmpty())
|
||||
extra.insert("toolset", m_toolset);
|
||||
if (!m_extraGenerator.isEmpty())
|
||||
extra.insert("extraGenerator", m_extraGenerator);
|
||||
if (!m_platform.isEmpty())
|
||||
extra.insert("platform", m_platform);
|
||||
if (!m_toolset.isEmpty())
|
||||
extra.insert("toolset", m_toolset);
|
||||
|
||||
sendRequest(HANDSHAKE_TYPE, extra);
|
||||
}
|
||||
|
||||
void ServerMode::reportError(const QString &msg)
|
||||
{
|
||||
qCWarning(cmakeServerMode) << "Report Error:" << msg;
|
||||
emit message(msg);
|
||||
emit errorOccurred(msg);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace CMakeProjectManager
|
@@ -1,127 +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 <utils/environment.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTimer>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include <memory>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QLocalSocket);
|
||||
|
||||
namespace Utils { class QtcProcess; }
|
||||
|
||||
namespace CMakeProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
class ServerMode final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ServerMode(const Utils::Environment &env,
|
||||
const Utils::FilePath &sourceDirectory, const Utils::FilePath &buildDirectory,
|
||||
const Utils::FilePath &cmakeExecutable,
|
||||
const QString &generator, const QString &extraGenerator,
|
||||
const QString &platform, const QString &toolset,
|
||||
bool experimental, int major, int minor = -1,
|
||||
QObject *parent = nullptr);
|
||||
~ServerMode() final;
|
||||
|
||||
void sendRequest(const QString &type, const QVariantMap &extra = QVariantMap(),
|
||||
const QVariant &cookie = QVariant());
|
||||
|
||||
bool isConnected();
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void disconnected();
|
||||
void message(const QString &msg);
|
||||
void errorOccurred(const QString &msg);
|
||||
|
||||
// Forward stuff from the server
|
||||
void cmakeReply(const QVariantMap &data, const QString &inResponseTo, const QVariant &cookie);
|
||||
void cmakeError(const QString &errorMessage, const QString &inResponseTo, const QVariant &cookie);
|
||||
void cmakeMessage(const QString &message, const QString &inResponseTo, const QVariant &cookie);
|
||||
void cmakeProgress(int min, int cur, int max, const QString &inResponseTo, const QVariant &cookie);
|
||||
void cmakeSignal(const QString &name, const QVariantMap &data);
|
||||
|
||||
private:
|
||||
void connectToServer();
|
||||
void handleCMakeFinished(int code, QProcess::ExitStatus status);
|
||||
|
||||
void handleRawCMakeServerData();
|
||||
void parseBuffer(const QByteArray &buffer);
|
||||
void parseJson(const QVariantMap &data);
|
||||
|
||||
void handleHello(const QVariantMap &data);
|
||||
|
||||
void reportError(const QString &msg);
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
QTemporaryDir m_socketDir;
|
||||
#endif
|
||||
std::unique_ptr<Utils::QtcProcess> m_cmakeProcess;
|
||||
QLocalSocket *m_cmakeSocket = nullptr;
|
||||
QTimer m_connectionTimer;
|
||||
|
||||
Utils::FilePath m_sourceDirectory;
|
||||
Utils::FilePath m_buildDirectory;
|
||||
Utils::CommandLine m_cmakeCommand;
|
||||
|
||||
QByteArray m_buffer;
|
||||
|
||||
struct ExpectedReply {
|
||||
QString type;
|
||||
QVariant cookie;
|
||||
};
|
||||
std::vector<ExpectedReply> m_expectedReplies;
|
||||
|
||||
const QString m_generator;
|
||||
const QString m_extraGenerator;
|
||||
const QString m_platform;
|
||||
const QString m_toolset;
|
||||
QString m_socketName;
|
||||
const bool m_useExperimental;
|
||||
bool m_gotHello = false;
|
||||
bool m_isConnected = false;
|
||||
const int m_majorProtocol = -1;
|
||||
const int m_minorProtocol = -1;
|
||||
|
||||
int m_requestCounter = 0;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace CMakeProjectManager
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(cmakeServerMode);
|
@@ -1,915 +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 "servermodereader.h"
|
||||
|
||||
#include "cmakeparser.h"
|
||||
#include "projecttreehelper.h"
|
||||
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <coreplugin/progressmanager/progressmanager.h>
|
||||
#include <projectexplorer/task.h>
|
||||
#include <projectexplorer/taskhub.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
|
||||
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 BACKTRACE_KEY[] = "backtrace";
|
||||
const char LINE_KEY[] = "line";
|
||||
const char NAME_KEY[] = "name";
|
||||
const char PATH_KEY[] = "path";
|
||||
const char SOURCE_DIRECTORY_KEY[] = "sourceDirectory";
|
||||
const char SOURCES_KEY[] = "sources";
|
||||
|
||||
const int MAX_PROGRESS = 1400;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// ServerModeReader:
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
ServerModeReader::ServerModeReader()
|
||||
{
|
||||
m_cmakeParser = new CMakeParser;
|
||||
m_parser.addLineParser(m_cmakeParser);
|
||||
}
|
||||
|
||||
ServerModeReader::~ServerModeReader()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void ServerModeReader::setParameters(const BuildDirParameters &p)
|
||||
{
|
||||
CMakeTool *cmake = p.cmakeTool();
|
||||
QTC_ASSERT(cmake, return);
|
||||
|
||||
m_parameters = p;
|
||||
m_cmakeParser->setSourceDirectory(m_parameters.sourceDirectory.toString());
|
||||
createNewServer();
|
||||
}
|
||||
|
||||
bool ServerModeReader::isCompatible(const BuildDirParameters &p)
|
||||
{
|
||||
CMakeTool *newCmake = p.cmakeTool();
|
||||
if (newCmake->readerType() != CMakeTool::FileApi)
|
||||
return false;
|
||||
|
||||
CMakeTool *oldCmake = m_parameters.cmakeTool();
|
||||
if (!newCmake || !oldCmake)
|
||||
return false;
|
||||
|
||||
// Server mode connection got lost, reset...
|
||||
if (!oldCmake && oldCmake->cmakeExecutable().isEmpty() && !m_cmakeServer)
|
||||
return false;
|
||||
|
||||
return newCmake->hasServerMode()
|
||||
&& newCmake->cmakeExecutable() == oldCmake->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.workDirectory == m_parameters.workDirectory;
|
||||
}
|
||||
|
||||
void ServerModeReader::resetData()
|
||||
{
|
||||
m_cmakeConfiguration.clear();
|
||||
// m_cmakeFiles: Keep these!
|
||||
m_cmakeInputsFileNodes.clear();
|
||||
qDeleteAll(m_projects); // Also deletes targets and filegroups that are its children!
|
||||
m_projects.clear();
|
||||
m_targets.clear();
|
||||
m_fileGroups.clear();
|
||||
}
|
||||
|
||||
void ServerModeReader::parse(bool forceCMakeRun, bool forceConfiguration)
|
||||
{
|
||||
emit configurationStarted();
|
||||
|
||||
QTC_ASSERT(m_cmakeServer, return);
|
||||
QVariantMap extra;
|
||||
|
||||
bool delayConfigurationRun = false;
|
||||
if (forceCMakeRun && m_cmakeServer->isConnected()) {
|
||||
createNewServer();
|
||||
delayConfigurationRun = true;
|
||||
}
|
||||
|
||||
if (forceConfiguration) {
|
||||
QStringList cacheArguments = transform(m_parameters.configuration,
|
||||
[this](const CMakeConfigItem &i) {
|
||||
return i.toArgument(m_parameters.expander);
|
||||
});
|
||||
Core::MessageManager::write(tr("Starting to parse CMake project, using: \"%1\".")
|
||||
.arg(cacheArguments.join("\", \"")));
|
||||
cacheArguments.prepend(QString()); // Work around a bug in CMake 3.7.0 and 3.7.1 where
|
||||
// the first argument gets lost!
|
||||
extra.insert("cacheArguments", QVariant(cacheArguments));
|
||||
} else {
|
||||
Core::MessageManager::write(tr("Starting to parse CMake project."));
|
||||
}
|
||||
|
||||
m_future.reset(new QFutureInterface<void>());
|
||||
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");
|
||||
|
||||
if (!delayConfigurationRun) {
|
||||
sendConfigureRequest(extra);
|
||||
} else {
|
||||
m_delayedConfigurationData = extra;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerModeReader::stop()
|
||||
{
|
||||
if (m_future) {
|
||||
m_future->reportCanceled();
|
||||
m_future->reportFinished();
|
||||
m_future.reset();
|
||||
}
|
||||
m_parser.flush();
|
||||
}
|
||||
|
||||
bool ServerModeReader::isParsing() const
|
||||
{
|
||||
return static_cast<bool>(m_future);
|
||||
}
|
||||
|
||||
QList<CMakeBuildTarget> ServerModeReader::takeBuildTargets(QString &errorMessage)
|
||||
{
|
||||
Q_UNUSED(errorMessage)
|
||||
QDir topSourceDir(m_parameters.sourceDirectory.toString());
|
||||
const QList<CMakeBuildTarget> result
|
||||
= transform(m_targets, [&topSourceDir](const Target *t) -> CMakeBuildTarget {
|
||||
CMakeBuildTarget ct;
|
||||
ct.title = t->name;
|
||||
ct.executable = t->artifacts.isEmpty() ? FilePath() : t->artifacts.at(0);
|
||||
TargetType type = UtilityType;
|
||||
if (t->type == "EXECUTABLE")
|
||||
type = ExecutableType;
|
||||
else if (t->type == "STATIC_LIBRARY")
|
||||
type = StaticLibraryType;
|
||||
else if (t->type == "OBJECT_LIBRARY")
|
||||
type = ObjectLibraryType;
|
||||
else if (t->type == "MODULE_LIBRARY" || t->type == "SHARED_LIBRARY"
|
||||
|| t->type == "INTERFACE_LIBRARY")
|
||||
type = DynamicLibraryType;
|
||||
else
|
||||
type = UtilityType;
|
||||
ct.targetType = type;
|
||||
if (t->artifacts.isEmpty()) {
|
||||
ct.workingDirectory = t->buildDirectory;
|
||||
} else {
|
||||
ct.workingDirectory = Utils::FilePath::fromString(
|
||||
t->artifacts.at(0).toFileInfo().absolutePath());
|
||||
}
|
||||
ct.sourceDirectory = FilePath::fromString(
|
||||
QDir::cleanPath(topSourceDir.absoluteFilePath(t->sourceDirectory.toString())));
|
||||
return ct;
|
||||
});
|
||||
m_targets.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
CMakeConfig ServerModeReader::takeParsedConfiguration(QString &errorMessage)
|
||||
{
|
||||
Q_UNUSED(errorMessage)
|
||||
CMakeConfig config = m_cmakeConfiguration;
|
||||
m_cmakeConfiguration.clear();
|
||||
return config;
|
||||
}
|
||||
|
||||
std::unique_ptr<CMakeProjectNode> ServerModeReader::generateProjectTree(const QList<const FileNode *> &allFiles,
|
||||
QString &errorMessage)
|
||||
{
|
||||
Q_UNUSED(errorMessage)
|
||||
auto root = std::make_unique<CMakeProjectNode>(m_parameters.sourceDirectory);
|
||||
|
||||
// Split up cmake inputs into useful chunks:
|
||||
std::vector<std::unique_ptr<FileNode>> cmakeFilesSource;
|
||||
std::vector<std::unique_ptr<FileNode>> cmakeFilesBuild;
|
||||
std::vector<std::unique_ptr<FileNode>> cmakeFilesOther;
|
||||
std::vector<std::unique_ptr<FileNode>> cmakeLists;
|
||||
|
||||
for (std::unique_ptr<FileNode> &fn : m_cmakeInputsFileNodes) {
|
||||
const FilePath path = fn->filePath();
|
||||
if (path.fileName().compare("CMakeLists.txt", HostOsInfo::fileNameCaseSensitivity()) == 0)
|
||||
cmakeLists.emplace_back(std::move(fn));
|
||||
else if (path.isChildOf(m_parameters.workDirectory))
|
||||
cmakeFilesBuild.emplace_back(std::move(fn));
|
||||
else if (path.isChildOf(m_parameters.sourceDirectory))
|
||||
cmakeFilesSource.emplace_back(std::move(fn));
|
||||
else
|
||||
cmakeFilesOther.emplace_back(std::move(fn));
|
||||
}
|
||||
m_cmakeInputsFileNodes.clear(); // Clean out, they are not going to be used anymore!
|
||||
|
||||
const Project *topLevel = Utils::findOrDefault(m_projects, [this](const Project *p) {
|
||||
return m_parameters.sourceDirectory == p->sourceDirectory;
|
||||
});
|
||||
if (topLevel)
|
||||
root->setDisplayName(topLevel->name);
|
||||
|
||||
QHash<Utils::FilePath, ProjectNode *> cmakeListsNodes = addCMakeLists(root.get(),
|
||||
std::move(cmakeLists));
|
||||
QSet<FilePath> knownHeaders;
|
||||
addProjects(cmakeListsNodes, m_projects, knownHeaders);
|
||||
|
||||
addHeaderNodes(root.get(), knownHeaders, allFiles);
|
||||
|
||||
if (cmakeFilesSource.size() > 0 || cmakeFilesBuild.size() > 0 || cmakeFilesOther.size() > 0)
|
||||
addCMakeInputs(root.get(),
|
||||
m_parameters.sourceDirectory,
|
||||
m_parameters.workDirectory,
|
||||
std::move(cmakeFilesSource),
|
||||
std::move(cmakeFilesBuild),
|
||||
std::move(cmakeFilesOther));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
RawProjectParts ServerModeReader::createRawProjectParts(QString &errorMessage)
|
||||
{
|
||||
Q_UNUSED(errorMessage)
|
||||
RawProjectParts rpps;
|
||||
|
||||
int counter = 0;
|
||||
for (const FileGroup *fg : qAsConst(m_fileGroups)) {
|
||||
// CMake users worked around Creator's inability of listing header files by creating
|
||||
// custom targets with all the header files. This target breaks the code model, so
|
||||
// keep quiet about it:-)
|
||||
if (fg->macros.isEmpty()
|
||||
&& fg->includePaths.isEmpty()
|
||||
&& !fg->isGenerated
|
||||
&& Utils::allOf(fg->sources, [](const Utils::FilePath &source) {
|
||||
return Node::fileTypeForFileName(source) == FileType::Header;
|
||||
})) {
|
||||
qWarning() << "Not reporting all-header file group of target" << fg->target << "to code model.";
|
||||
continue;
|
||||
}
|
||||
|
||||
++counter;
|
||||
const QStringList flags = QtcProcess::splitArgs(fg->compileFlags);
|
||||
const QStringList includes = transform(fg->includePaths, [](const IncludePath *ip) { return ip->path.toString(); });
|
||||
|
||||
RawProjectPart rpp;
|
||||
rpp.setProjectFileLocation(fg->target->sourceDirectory.toString() + "/CMakeLists.txt");
|
||||
rpp.setBuildSystemTarget(fg->target->name);
|
||||
rpp.setDisplayName(fg->target->name + QString::number(counter));
|
||||
rpp.setMacros(fg->macros);
|
||||
rpp.setIncludePaths(includes);
|
||||
|
||||
RawProjectPartFlags cProjectFlags;
|
||||
cProjectFlags.commandLineFlags = flags;
|
||||
rpp.setFlagsForC(cProjectFlags);
|
||||
|
||||
RawProjectPartFlags cxxProjectFlags;
|
||||
cxxProjectFlags.commandLineFlags = flags;
|
||||
rpp.setFlagsForCxx(cxxProjectFlags);
|
||||
|
||||
rpp.setFiles(transform(fg->sources, &FilePath::toString));
|
||||
|
||||
const bool isExecutable = fg->target->type == "EXECUTABLE";
|
||||
rpp.setBuildTargetType(isExecutable ? ProjectExplorer::BuildTargetType::Executable
|
||||
: ProjectExplorer::BuildTargetType::Library);
|
||||
rpps.append(rpp);
|
||||
}
|
||||
|
||||
return rpps;
|
||||
}
|
||||
|
||||
void ServerModeReader::createNewServer()
|
||||
{
|
||||
QTC_ASSERT(m_parameters.cmakeTool(), return);
|
||||
m_cmakeServer
|
||||
= std::make_unique<ServerMode>(
|
||||
m_parameters.environment,
|
||||
m_parameters.sourceDirectory, m_parameters.workDirectory,
|
||||
m_parameters.cmakeTool()->cmakeExecutable(),
|
||||
m_parameters.generator,
|
||||
m_parameters.extraGenerator,
|
||||
m_parameters.platform, m_parameters.toolset,
|
||||
true, 1);
|
||||
|
||||
connect(m_cmakeServer.get(), &ServerMode::errorOccurred,
|
||||
this, &ServerModeReader::errorOccurred);
|
||||
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::cmakeSignal,
|
||||
this, &ServerModeReader::handleSignal);
|
||||
connect(m_cmakeServer.get(), &ServerMode::cmakeMessage, [this](const QString &m) {
|
||||
const QStringList lines = m.split('\n');
|
||||
for (const QString &l : lines) {
|
||||
m_parser.appendMessage(l, StdErrFormat);
|
||||
Core::MessageManager::write(l);
|
||||
}
|
||||
});
|
||||
connect(m_cmakeServer.get(), &ServerMode::message,
|
||||
this, [](const QString &m) { Core::MessageManager::write(m); });
|
||||
connect(m_cmakeServer.get(),
|
||||
&ServerMode::connected,
|
||||
this,
|
||||
&ServerModeReader::handleServerConnected,
|
||||
Qt::QueuedConnection); // Delay
|
||||
connect(m_cmakeServer.get(), &ServerMode::disconnected,
|
||||
this, [this]() {
|
||||
stop();
|
||||
Core::MessageManager::write(tr("Parsing of CMake project failed: Connection to CMake server lost."));
|
||||
m_cmakeServer.reset();
|
||||
}, Qt::QueuedConnection); // Delay
|
||||
|
||||
}
|
||||
|
||||
void ServerModeReader::handleReply(const QVariantMap &data, const QString &inReplyTo)
|
||||
{
|
||||
if (!m_delayedErrorMessage.isEmpty()) {
|
||||
// Handle reply to cache after error:
|
||||
if (inReplyTo == CACHE_TYPE)
|
||||
extractCacheData(data);
|
||||
reportError();
|
||||
} else {
|
||||
// No error yet:
|
||||
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();
|
||||
m_future.reset();
|
||||
}
|
||||
Core::MessageManager::write(tr("CMake Project was parsed successfully."));
|
||||
emit dataAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerModeReader::handleError(const QString &message)
|
||||
{
|
||||
TaskHub::addTask(BuildSystemTask(Task::Error, message));
|
||||
|
||||
if (!m_delayedErrorMessage.isEmpty()) {
|
||||
reportError();
|
||||
return;
|
||||
}
|
||||
|
||||
m_delayedErrorMessage = message;
|
||||
|
||||
// Always try to read CMakeCache, even after an error!
|
||||
m_cmakeServer->sendRequest(CACHE_TYPE);
|
||||
if (m_future)
|
||||
m_future->setProgressValue(1300);
|
||||
}
|
||||
|
||||
void ServerModeReader::handleProgress(int min, int cur, int max, const QString &inReplyTo)
|
||||
{
|
||||
Q_UNUSED(inReplyTo)
|
||||
|
||||
if (!m_future)
|
||||
return;
|
||||
const int progress = calculateProgress(m_progressStepMinimum, min, cur, max, m_progressStepMaximum);
|
||||
m_future->setProgressValue(progress);
|
||||
}
|
||||
|
||||
void ServerModeReader::handleSignal(const QString &signal, const QVariantMap &data)
|
||||
{
|
||||
Q_UNUSED(signal)
|
||||
Q_UNUSED(data)
|
||||
// We do not need to act on fileChanged signals nor on dirty signals!
|
||||
}
|
||||
|
||||
void ServerModeReader::handleServerConnected()
|
||||
{
|
||||
if (m_delayedConfigurationData) {
|
||||
sendConfigureRequest(*m_delayedConfigurationData);
|
||||
m_delayedConfigurationData.reset();
|
||||
} else {
|
||||
emit isReadyNow();
|
||||
}
|
||||
}
|
||||
|
||||
void ServerModeReader::sendConfigureRequest(const QVariantMap &extra)
|
||||
{
|
||||
m_delayedErrorMessage.clear();
|
||||
m_cmakeServer->sendRequest(CONFIGURE_TYPE, extra);
|
||||
}
|
||||
|
||||
void ServerModeReader::reportError()
|
||||
{
|
||||
stop();
|
||||
Core::MessageManager::write(tr("CMake Project parsing failed."));
|
||||
emit errorOccurred(m_delayedErrorMessage);
|
||||
|
||||
if (m_future)
|
||||
m_future->reportCanceled();
|
||||
|
||||
m_delayedErrorMessage.clear();
|
||||
}
|
||||
|
||||
int ServerModeReader::calculateProgress(const int minRange, const int min, const int cur, const int max, const int maxRange)
|
||||
{
|
||||
if (minRange == maxRange || min == max)
|
||||
return minRange;
|
||||
const int clampedCur = std::min(std::max(cur, min), max);
|
||||
return minRange + ((clampedCur - min) / (max - min)) * (maxRange - minRange);
|
||||
}
|
||||
|
||||
void ServerModeReader::extractCodeModelData(const QVariantMap &data)
|
||||
{
|
||||
const QVariantList configs = data.value("configurations").toList();
|
||||
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)
|
||||
QSet<QString> knownTargets; // To filter duplicate target names:-/
|
||||
const QVariantList projects = data.value("projects").toList();
|
||||
for (const QVariant &p : projects) {
|
||||
const QVariantMap pData = p.toMap();
|
||||
m_projects.append(extractProjectData(pData, knownTargets));
|
||||
}
|
||||
}
|
||||
|
||||
ServerModeReader::Project *ServerModeReader::extractProjectData(const QVariantMap &data,
|
||||
QSet<QString> &knownTargets)
|
||||
{
|
||||
auto project = new Project;
|
||||
project->name = data.value(NAME_KEY).toString();
|
||||
project->sourceDirectory = FilePath::fromString(data.value(SOURCE_DIRECTORY_KEY).toString());
|
||||
|
||||
const QVariantList targets = data.value("targets").toList();
|
||||
for (const QVariant &t : targets) {
|
||||
const QVariantMap tData = t.toMap();
|
||||
Target *tp = extractTargetData(tData, project, knownTargets);
|
||||
if (tp)
|
||||
project->targets.append(tp);
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
ServerModeReader::Target *ServerModeReader::extractTargetData(const QVariantMap &data, Project *p,
|
||||
QSet<QString> &knownTargets)
|
||||
{
|
||||
const QString targetName = data.value(NAME_KEY).toString();
|
||||
|
||||
// Remove duplicate targets: CMake unfortunately does duplicate targets for all projects that
|
||||
// contain them. Keep at least till cmake 3.9 is deprecated.
|
||||
const int count = knownTargets.count();
|
||||
knownTargets.insert(targetName);
|
||||
if (knownTargets.count() == count)
|
||||
return nullptr;
|
||||
|
||||
auto target = new Target;
|
||||
target->project = p;
|
||||
target->name = targetName;
|
||||
target->sourceDirectory = FilePath::fromString(data.value(SOURCE_DIRECTORY_KEY).toString());
|
||||
target->buildDirectory = FilePath::fromString(data.value("buildDirectory").toString());
|
||||
|
||||
target->crossReferences = extractCrossReferences(data.value("crossReferences").toMap());
|
||||
|
||||
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 FilePath::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));
|
||||
}
|
||||
|
||||
fixTarget(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->macros = Utils::transform<QVector>(data.value("defines").toStringList(), [](const QString &s) {
|
||||
return ProjectExplorer::Macro::fromKeyValue(s);
|
||||
});
|
||||
fileGroup->includePaths = transform(data.value("includePath").toList(),
|
||||
[](const QVariant &i) -> IncludePath* {
|
||||
const QVariantMap iData = i.toMap();
|
||||
auto result = new IncludePath;
|
||||
result->path = FilePath::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 FilePath::fromString(QDir::cleanPath(srcDir.absoluteFilePath(s)));
|
||||
});
|
||||
|
||||
m_fileGroups.append(fileGroup);
|
||||
return fileGroup;
|
||||
}
|
||||
|
||||
QList<ServerModeReader::CrossReference *> ServerModeReader::extractCrossReferences(const QVariantMap &data)
|
||||
{
|
||||
QList<CrossReference *> crossReferences;
|
||||
|
||||
if (data.isEmpty())
|
||||
return crossReferences;
|
||||
|
||||
auto cr = std::make_unique<CrossReference>();
|
||||
cr->backtrace = extractBacktrace(data.value(BACKTRACE_KEY, QVariantList()).toList());
|
||||
QTC_ASSERT(!cr->backtrace.isEmpty(), return {});
|
||||
crossReferences.append(cr.release());
|
||||
|
||||
const QVariantList related = data.value("relatedStatements", QVariantList()).toList();
|
||||
for (const QVariant &relatedData : related) {
|
||||
auto cr = std::make_unique<CrossReference>();
|
||||
|
||||
// extract information:
|
||||
const QVariantMap map = relatedData.toMap();
|
||||
const QString typeString = map.value("type", QString()).toString();
|
||||
if (typeString.isEmpty())
|
||||
cr->type = CrossReference::TARGET;
|
||||
else if (typeString == "target_link_libraries")
|
||||
cr->type = CrossReference::LIBRARIES;
|
||||
else if (typeString == "target_compile_defines")
|
||||
cr->type = CrossReference::DEFINES;
|
||||
else if (typeString == "target_include_directories")
|
||||
cr->type = CrossReference::INCLUDES;
|
||||
else
|
||||
cr->type = CrossReference::UNKNOWN;
|
||||
cr->backtrace = extractBacktrace(map.value(BACKTRACE_KEY, QVariantList()).toList());
|
||||
|
||||
// sanity check:
|
||||
if (cr->backtrace.isEmpty())
|
||||
continue;
|
||||
|
||||
// store information:
|
||||
crossReferences.append(cr.release());
|
||||
}
|
||||
return crossReferences;
|
||||
}
|
||||
|
||||
ServerModeReader::BacktraceItem *ServerModeReader::extractBacktraceItem(const QVariantMap &data)
|
||||
{
|
||||
QTC_ASSERT(!data.isEmpty(), return nullptr);
|
||||
auto item = std::make_unique<BacktraceItem>();
|
||||
|
||||
item->line = data.value(LINE_KEY, -1).toInt();
|
||||
item->name = data.value(NAME_KEY, QString()).toString();
|
||||
item->path = data.value(PATH_KEY, QString()).toString();
|
||||
|
||||
QTC_ASSERT(!item->path.isEmpty(), return nullptr);
|
||||
return item.release();
|
||||
}
|
||||
|
||||
QList<ServerModeReader::BacktraceItem *> ServerModeReader::extractBacktrace(const QVariantList &data)
|
||||
{
|
||||
QList<BacktraceItem *> btResult;
|
||||
for (const QVariant &bt : data) {
|
||||
BacktraceItem *btItem = extractBacktraceItem(bt.toMap());
|
||||
QTC_ASSERT(btItem, continue);
|
||||
|
||||
btResult.append(btItem);
|
||||
}
|
||||
return btResult;
|
||||
}
|
||||
|
||||
void ServerModeReader::extractCMakeInputsData(const QVariantMap &data)
|
||||
{
|
||||
const FilePath src = FilePath::fromString(data.value(SOURCE_DIRECTORY_KEY).toString());
|
||||
QTC_ASSERT(src == m_parameters.sourceDirectory, return);
|
||||
QDir srcDir(src.toString());
|
||||
|
||||
m_cmakeFiles.clear();
|
||||
|
||||
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(); // generated file
|
||||
const bool isCMake = section.value("isCMake").toBool(); // part of the cmake installation
|
||||
|
||||
for (const QString &s : sources) {
|
||||
const FilePath sfn = FilePath::fromString(QDir::cleanPath(srcDir.absoluteFilePath(s)));
|
||||
const int oldCount = m_cmakeFiles.count();
|
||||
m_cmakeFiles.insert(sfn);
|
||||
if (oldCount < m_cmakeFiles.count()) {
|
||||
const bool isCMakeListsFile = sfn.toString().endsWith("/CMakeLists.txt");
|
||||
|
||||
if (isCMake && !isCMakeListsFile)
|
||||
// Skip files that cmake considers to be part of the installation -- but include
|
||||
// CMakeLists.txt files. This unbreaks cmake binaries running from their own
|
||||
// build directory.
|
||||
continue;
|
||||
|
||||
auto node = std::make_unique<FileNode>(sfn, FileType::Project);
|
||||
node->setIsGenerated(isTemporary && !isCMakeListsFile); // CMakeLists.txt are never
|
||||
// generated, independent
|
||||
// what cmake thinks:-)
|
||||
|
||||
m_cmakeInputsFileNodes.emplace_back(std::move(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::typeStringToType(eData.value("type").toByteArray());
|
||||
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_cmakeConfiguration = config;
|
||||
}
|
||||
|
||||
void ServerModeReader::fixTarget(ServerModeReader::Target *target) const
|
||||
{
|
||||
QHash<QString, const FileGroup *> languageFallbacks;
|
||||
|
||||
for (const FileGroup *group : qAsConst(target->fileGroups)) {
|
||||
if (group->includePaths.isEmpty() && group->compileFlags.isEmpty()
|
||||
&& group->macros.isEmpty())
|
||||
continue;
|
||||
|
||||
const FileGroup *fallback = languageFallbacks.value(group->language);
|
||||
if (!fallback || fallback->sources.count() < group->sources.count())
|
||||
languageFallbacks.insert(group->language, group);
|
||||
}
|
||||
|
||||
if (!languageFallbacks.value(""))
|
||||
return; // No empty language groups found, no need to proceed.
|
||||
|
||||
const FileGroup *fallback = languageFallbacks.value("CXX");
|
||||
if (!fallback)
|
||||
fallback = languageFallbacks.value("C");
|
||||
if (!fallback)
|
||||
fallback = languageFallbacks.value("");
|
||||
|
||||
if (!fallback)
|
||||
return;
|
||||
|
||||
for (auto it = target->fileGroups.begin(); it != target->fileGroups.end(); ++it) {
|
||||
if (!(*it)->language.isEmpty())
|
||||
continue;
|
||||
(*it)->language = fallback->language.isEmpty() ? "CXX" : fallback->language;
|
||||
|
||||
if (*it == fallback
|
||||
|| !(*it)->includePaths.isEmpty() || !(*it)->macros.isEmpty()
|
||||
|| !(*it)->compileFlags.isEmpty())
|
||||
continue;
|
||||
|
||||
for (const IncludePath *ip : fallback->includePaths)
|
||||
(*it)->includePaths.append(new IncludePath(*ip));
|
||||
(*it)->macros = fallback->macros;
|
||||
(*it)->compileFlags = fallback->compileFlags;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerModeReader::addProjects(const QHash<Utils::FilePath, ProjectNode *> &cmakeListsNodes,
|
||||
const QList<Project *> &projects,
|
||||
QSet<FilePath> &knownHeaders)
|
||||
{
|
||||
for (const Project *p : projects) {
|
||||
createProjectNode(cmakeListsNodes, p->sourceDirectory, p->name);
|
||||
addTargets(cmakeListsNodes, p->targets, knownHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerModeReader::addTargets(
|
||||
const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
|
||||
const QList<Target *> &targets,
|
||||
QSet<Utils::FilePath> &knownHeaders)
|
||||
{
|
||||
for (const Target *t : targets) {
|
||||
CMakeTargetNode *tNode = createTargetNode(cmakeListsNodes, t->sourceDirectory, t->name);
|
||||
QTC_ASSERT(tNode, qDebug() << "No target node for" << t->sourceDirectory << t->name; continue);
|
||||
tNode->setTargetInformation(t->artifacts, t->type);
|
||||
tNode->setBuildDirectory(t->buildDirectory);
|
||||
QVector<FolderNode::LocationInfo> info;
|
||||
// Set up a default target path:
|
||||
FilePath targetPath = t->sourceDirectory.pathAppended("CMakeLists.txt");
|
||||
for (CrossReference *cr : qAsConst(t->crossReferences)) {
|
||||
BacktraceItem *bt = cr->backtrace.isEmpty() ? nullptr : cr->backtrace.at(0);
|
||||
if (bt) {
|
||||
const QString btName = bt->name.toLower();
|
||||
const FilePath path = Utils::FilePath::fromUserInput(bt->path);
|
||||
QString dn;
|
||||
if (cr->type != CrossReference::TARGET) {
|
||||
if (path == targetPath) {
|
||||
if (bt->line >= 0)
|
||||
dn = tr("%1 in line %2").arg(btName).arg(bt->line);
|
||||
else
|
||||
dn = tr("%1").arg(btName);
|
||||
} else {
|
||||
if (bt->line >= 0)
|
||||
dn = tr("%1 in %2:%3").arg(btName, path.toUserOutput()).arg(bt->line);
|
||||
else
|
||||
dn = tr("%1 in %2").arg(btName, path.toUserOutput());
|
||||
}
|
||||
} else {
|
||||
dn = tr("Target Definition");
|
||||
targetPath = path;
|
||||
}
|
||||
info.append(FolderNode::LocationInfo(dn, path, bt->line));
|
||||
}
|
||||
}
|
||||
tNode->setLocationInfo(info);
|
||||
addFileGroups(tNode, t->sourceDirectory, t->buildDirectory, t->fileGroups, knownHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerModeReader::addFileGroups(ProjectNode *targetRoot,
|
||||
const Utils::FilePath &sourceDirectory,
|
||||
const Utils::FilePath &buildDirectory,
|
||||
const QList<ServerModeReader::FileGroup *> &fileGroups,
|
||||
QSet<Utils::FilePath> &knownHeaders)
|
||||
{
|
||||
std::vector<std::unique_ptr<FileNode>> toList;
|
||||
QSet<Utils::FilePath> alreadyListed;
|
||||
// Files already added by other configurations:
|
||||
targetRoot->forEachGenericNode([&alreadyListed](const Node *n) {
|
||||
alreadyListed.insert(n->filePath());
|
||||
});
|
||||
|
||||
for (const FileGroup *f : fileGroups) {
|
||||
const QList<FilePath> newSources = Utils::filtered(f->sources, [&alreadyListed](const Utils::FilePath &fn) {
|
||||
const int count = alreadyListed.count();
|
||||
alreadyListed.insert(fn);
|
||||
return count != alreadyListed.count();
|
||||
});
|
||||
std::vector<std::unique_ptr<FileNode>> newFileNodes = Utils::transform<std::vector>(
|
||||
newSources, [f, &knownHeaders](const Utils::FilePath &fn) {
|
||||
auto node = std::make_unique<FileNode>(fn, Node::fileTypeForFileName(fn));
|
||||
node->setIsGenerated(f->isGenerated);
|
||||
if (node->fileType() == FileType::Header)
|
||||
knownHeaders.insert(node->filePath());
|
||||
return node;
|
||||
});
|
||||
std::move(std::begin(newFileNodes), std::end(newFileNodes), std::back_inserter(toList));
|
||||
}
|
||||
|
||||
// Split up files in groups (based on location):
|
||||
const bool inSourceBuild = (m_parameters.workDirectory == m_parameters.sourceDirectory);
|
||||
std::vector<std::unique_ptr<FileNode>> sourceFileNodes;
|
||||
std::vector<std::unique_ptr<FileNode>> buildFileNodes;
|
||||
std::vector<std::unique_ptr<FileNode>> otherFileNodes;
|
||||
for (std::unique_ptr<FileNode> &fn : toList) {
|
||||
if (fn->filePath().isChildOf(m_parameters.workDirectory) && !inSourceBuild)
|
||||
buildFileNodes.emplace_back(std::move(fn));
|
||||
else if (fn->filePath().isChildOf(m_parameters.sourceDirectory))
|
||||
sourceFileNodes.emplace_back(std::move(fn));
|
||||
else
|
||||
otherFileNodes.emplace_back(std::move(fn));
|
||||
}
|
||||
|
||||
addCMakeVFolder(targetRoot, sourceDirectory, 1000, QString(), std::move(sourceFileNodes));
|
||||
addCMakeVFolder(targetRoot, buildDirectory, 100, tr("<Build Directory>"), std::move(buildFileNodes));
|
||||
addCMakeVFolder(targetRoot, Utils::FilePath(), 10, tr("<Other Locations>"), std::move(otherFileNodes));
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace CMakeProjectManager
|
||||
|
||||
#if defined(WITH_TESTS)
|
||||
|
||||
#include "cmakeprojectplugin.h"
|
||||
#include <QTest>
|
||||
|
||||
namespace CMakeProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
void CMakeProjectPlugin::testServerModeReaderProgress_data()
|
||||
{
|
||||
QTest::addColumn<int>("minRange");
|
||||
QTest::addColumn<int>("min");
|
||||
QTest::addColumn<int>("cur");
|
||||
QTest::addColumn<int>("max");
|
||||
QTest::addColumn<int>("maxRange");
|
||||
QTest::addColumn<int>("expected");
|
||||
|
||||
QTest::newRow("empty range") << 100 << 10 << 11 << 20 << 100 << 100;
|
||||
QTest::newRow("one range (low)") << 0 << 10 << 11 << 20 << 1 << 0;
|
||||
QTest::newRow("one range (high)") << 20 << 10 << 19 << 20 << 20 << 20;
|
||||
QTest::newRow("large range") << 30 << 10 << 11 << 20 << 100000 << 30;
|
||||
|
||||
QTest::newRow("empty progress") << -5 << 10 << 10 << 10 << 99995 << -5;
|
||||
QTest::newRow("one progress (low)") << 42 << 10 << 10 << 11 << 100042 << 42;
|
||||
QTest::newRow("one progress (high)") << 0 << 10 << 11 << 11 << 100000 << 100000;
|
||||
QTest::newRow("large progress") << 0 << 10 << 10 << 11 << 100000 << 0;
|
||||
|
||||
QTest::newRow("cur too low") << 0 << 10 << 9 << 100 << 100000 << 0;
|
||||
QTest::newRow("cur too high") << 0 << 10 << 101 << 100 << 100000 << 100000;
|
||||
QTest::newRow("cur much too low") << 0 << 10 << -1000 << 100 << 100000 << 0;
|
||||
QTest::newRow("cur much too high") << 0 << 10 << 1110000 << 100 << 100000 << 100000;
|
||||
}
|
||||
|
||||
void CMakeProjectPlugin::testServerModeReaderProgress()
|
||||
{
|
||||
QFETCH(int, minRange);
|
||||
QFETCH(int, min);
|
||||
QFETCH(int, cur);
|
||||
QFETCH(int, max);
|
||||
QFETCH(int, maxRange);
|
||||
QFETCH(int, expected);
|
||||
|
||||
ServerModeReader reader;
|
||||
const int r = reader.calculateProgress(minRange, min, cur, max, maxRange);
|
||||
|
||||
QCOMPARE(r, expected);
|
||||
|
||||
QVERIFY(r <= maxRange);
|
||||
QVERIFY(r >= minRange);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace CMakeProjectManager
|
||||
|
||||
#endif
|
@@ -1,199 +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 "builddirreader.h"
|
||||
#include "cmakeconfigitem.h"
|
||||
#include "servermode.h"
|
||||
|
||||
#include <projectexplorer/ioutputparser.h>
|
||||
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ProjectExplorer { class ProjectNode; }
|
||||
namespace Utils { class OutputFormatter; }
|
||||
|
||||
namespace CMakeProjectManager {
|
||||
|
||||
class CMakeParser;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
class ServerModeReader final : public BuildDirReader
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ServerModeReader();
|
||||
~ServerModeReader() final;
|
||||
|
||||
void setParameters(const BuildDirParameters &p) final;
|
||||
|
||||
bool isCompatible(const BuildDirParameters &p) final;
|
||||
void resetData() final;
|
||||
void parse(bool forceCMakeRun, bool forceConfiguration) final;
|
||||
void stop() final;
|
||||
|
||||
bool isParsing() const final;
|
||||
|
||||
QSet<Utils::FilePath> projectFilesToWatch() const final { return {}; };
|
||||
QList<CMakeBuildTarget> takeBuildTargets(QString &errorMessage) final;
|
||||
CMakeConfig takeParsedConfiguration(QString &errorMessage) final;
|
||||
std::unique_ptr<CMakeProjectNode> generateProjectTree(
|
||||
const QList<const ProjectExplorer::FileNode *> &allFiles, QString &errorMessage) final;
|
||||
ProjectExplorer::RawProjectParts createRawProjectParts(QString &errorMessage) final;
|
||||
|
||||
private:
|
||||
void createNewServer();
|
||||
void handleReply(const QVariantMap &data, const QString &inReplyTo);
|
||||
void handleError(const QString &message);
|
||||
void handleProgress(int min, int cur, int max, const QString &inReplyTo);
|
||||
void handleSignal(const QString &signal, const QVariantMap &data);
|
||||
void handleServerConnected();
|
||||
|
||||
void sendConfigureRequest(const QVariantMap &extra);
|
||||
|
||||
void reportError();
|
||||
|
||||
int calculateProgress(const int minRange, const int min,
|
||||
const int cur,
|
||||
const int max, const int maxRange);
|
||||
|
||||
struct Target;
|
||||
struct Project;
|
||||
|
||||
struct IncludePath {
|
||||
Utils::FilePath path;
|
||||
bool isSystem;
|
||||
};
|
||||
|
||||
struct FileGroup {
|
||||
~FileGroup() { qDeleteAll(includePaths); includePaths.clear(); }
|
||||
|
||||
Target *target = nullptr;
|
||||
QString compileFlags;
|
||||
ProjectExplorer::Macros macros;
|
||||
QList<IncludePath *> includePaths;
|
||||
QString language;
|
||||
QList<Utils::FilePath> sources;
|
||||
bool isGenerated;
|
||||
};
|
||||
|
||||
struct BacktraceItem {
|
||||
int line = -1;
|
||||
QString path;
|
||||
QString name;
|
||||
};
|
||||
|
||||
struct CrossReference {
|
||||
~CrossReference() { qDeleteAll(backtrace); backtrace.clear(); }
|
||||
QList<BacktraceItem *> backtrace;
|
||||
enum Type { TARGET, LIBRARIES, DEFINES, INCLUDES, UNKNOWN };
|
||||
Type type;
|
||||
};
|
||||
|
||||
struct Target {
|
||||
~Target() {
|
||||
qDeleteAll(fileGroups);
|
||||
fileGroups.clear();
|
||||
qDeleteAll(crossReferences);
|
||||
crossReferences.clear();
|
||||
}
|
||||
|
||||
Project *project = nullptr;
|
||||
QString name;
|
||||
QString type;
|
||||
QList<Utils::FilePath> artifacts;
|
||||
Utils::FilePath sourceDirectory;
|
||||
Utils::FilePath buildDirectory;
|
||||
QList<FileGroup *> fileGroups;
|
||||
QList<CrossReference *> crossReferences;
|
||||
};
|
||||
|
||||
struct Project {
|
||||
~Project() { qDeleteAll(targets); targets.clear(); }
|
||||
QString name;
|
||||
Utils::FilePath sourceDirectory;
|
||||
QList<Target *> targets;
|
||||
};
|
||||
|
||||
void extractCodeModelData(const QVariantMap &data);
|
||||
void extractConfigurationData(const QVariantMap &data);
|
||||
Project *extractProjectData(const QVariantMap &data, QSet<QString> &knownTargets);
|
||||
Target *extractTargetData(const QVariantMap &data, Project *p, QSet<QString> &knownTargets);
|
||||
FileGroup *extractFileGroupData(const QVariantMap &data, const QDir &srcDir, Target *t);
|
||||
QList<CrossReference *> extractCrossReferences(const QVariantMap &data);
|
||||
QList<BacktraceItem *> extractBacktrace(const QVariantList &data);
|
||||
BacktraceItem *extractBacktraceItem(const QVariantMap &data);
|
||||
void extractCMakeInputsData(const QVariantMap &data);
|
||||
void extractCacheData(const QVariantMap &data);
|
||||
|
||||
void fixTarget(Target *target) const;
|
||||
|
||||
void addProjects(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
|
||||
const QList<Project *> &projects,
|
||||
QSet<Utils::FilePath> &knownHeaders);
|
||||
void addTargets(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
|
||||
const QList<Target *> &targets,
|
||||
QSet<Utils::FilePath> &knownHeaders);
|
||||
void addFileGroups(ProjectExplorer::ProjectNode *targetRoot,
|
||||
const Utils::FilePath &sourceDirectory,
|
||||
const Utils::FilePath &buildDirectory,
|
||||
const QList<FileGroup *> &fileGroups,
|
||||
QSet<Utils::FilePath> &knownHeaders);
|
||||
|
||||
std::unique_ptr<ServerMode> m_cmakeServer;
|
||||
std::unique_ptr<QFutureInterface<void>> m_future;
|
||||
|
||||
int m_progressStepMinimum = 0;
|
||||
int m_progressStepMaximum = 1000;
|
||||
|
||||
Utils::optional<QVariantMap> m_delayedConfigurationData;
|
||||
|
||||
QString m_delayedErrorMessage;
|
||||
|
||||
CMakeConfig m_cmakeConfiguration;
|
||||
|
||||
QSet<Utils::FilePath> m_cmakeFiles;
|
||||
std::vector<std::unique_ptr<ProjectExplorer::FileNode>> m_cmakeInputsFileNodes;
|
||||
|
||||
QList<Project *> m_projects;
|
||||
QList<Target *> m_targets;
|
||||
QList<FileGroup *> m_fileGroups;
|
||||
|
||||
CMakeParser *m_cmakeParser = nullptr;
|
||||
Utils::OutputFormatter m_parser;
|
||||
|
||||
#if defined(WITH_TESTS)
|
||||
friend class CMakeProjectPlugin;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace CMakeProjectManager
|
Reference in New Issue
Block a user