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:
Tobias Hunger
2020-04-03 11:16:48 +02:00
parent 4486f78e49
commit 494b9f5287
15 changed files with 38 additions and 1917 deletions

View File

@@ -60,11 +60,8 @@
\section1 Adding CMake Tools \section1 Adding CMake Tools
\QC supports CMake version 3.7, or later. \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.
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}.
To specify paths to CMake executables: To specify paths to CMake executables:

View File

@@ -40,6 +40,4 @@ add_qtc_plugin(CMakeProjectManager
fileapiparser.cpp fileapiparser.h fileapiparser.cpp fileapiparser.h
fileapireader.cpp fileapireader.h fileapireader.cpp fileapireader.h
projecttreehelper.cpp projecttreehelper.h projecttreehelper.cpp projecttreehelper.h
servermode.cpp servermode.h
servermodereader.cpp servermodereader.h
) )

View File

@@ -26,7 +26,6 @@
#include "builddirreader.h" #include "builddirreader.h"
#include "fileapireader.h" #include "fileapireader.h"
#include "servermodereader.h"
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -50,8 +49,6 @@ std::unique_ptr<BuildDirReader> BuildDirReader::createReader(const BuildDirParam
switch (type.value()) { switch (type.value()) {
case CMakeTool::FileApi: case CMakeTool::FileApi:
return std::make_unique<FileApiReader>(); return std::make_unique<FileApiReader>();
case CMakeTool::ServerMode:
return std::make_unique<ServerModeReader>();
} }
} }

View File

@@ -254,10 +254,10 @@ Tasks CMakeKitAspect::validate(const Kit *k) const
CMakeTool *tool = CMakeKitAspect::cmakeTool(k); CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
if (tool) { if (tool) {
CMakeTool::Version version = tool->version(); 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, result << BuildSystemTask(Task::Warning,
tr("CMake version %1 is unsupported. Please update to " 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))); .arg(QString::fromUtf8(version.fullVersion)));
} }
} }
@@ -717,9 +717,8 @@ Tasks CMakeGeneratorKitAspect::validate(const Kit *k) const
if (!it->supportsToolset && !info.toolset.isEmpty()) if (!it->supportsToolset && !info.toolset.isEmpty())
addWarning(tr("Toolset is not supported by the selected CMake generator.")); addWarning(tr("Toolset is not supported by the selected CMake generator."));
} }
if (!tool->hasServerMode() && !tool->hasFileApi() && info.extraGenerator != "CodeBlocks") { if (!tool->hasFileApi()) {
addWarning(tr("The selected CMake binary has no server-mode and the CMake " addWarning(tr("The selected CMake binary does not support file-api. "
"generator does not generate a CodeBlocks file. "
"%1 will not be able to parse CMake projects.") "%1 will not be able to parse CMake projects.")
.arg(Core::Constants::IDE_DISPLAY_NAME)); .arg(Core::Constants::IDE_DISPLAY_NAME));
} }

View File

@@ -36,9 +36,7 @@ HEADERS = builddirmanager.h \
fileapidataextractor.h \ fileapidataextractor.h \
fileapiparser.h \ fileapiparser.h \
fileapireader.h \ fileapireader.h \
projecttreehelper.h \ projecttreehelper.h
servermode.h \
servermodereader.h
SOURCES = builddirmanager.cpp \ SOURCES = builddirmanager.cpp \
builddirparameters.cpp \ builddirparameters.cpp \
@@ -72,9 +70,7 @@ SOURCES = builddirmanager.cpp \
fileapidataextractor.cpp \ fileapidataextractor.cpp \
fileapiparser.cpp \ fileapiparser.cpp \
fileapireader.cpp \ fileapireader.cpp \
projecttreehelper.cpp \ projecttreehelper.cpp
servermode.cpp \
servermodereader.cpp
RESOURCES += cmakeproject.qrc RESOURCES += cmakeproject.qrc

View File

@@ -89,10 +89,6 @@ QtcPlugin {
"fileapireader.cpp", "fileapireader.cpp",
"fileapireader.h", "fileapireader.h",
"projecttreehelper.cpp", "projecttreehelper.cpp",
"projecttreehelper.h", "projecttreehelper.h"
"servermode.cpp",
"servermode.h",
"servermodereader.cpp",
"servermodereader.h"
] ]
} }

View File

@@ -54,9 +54,6 @@ private slots:
void testCMakeProjectImporterToolChain_data(); void testCMakeProjectImporterToolChain_data();
void testCMakeProjectImporterToolChain(); void testCMakeProjectImporterToolChain();
void testServerModeReaderProgress_data();
void testServerModeReaderProgress();
#endif #endif
private: private:

View File

@@ -114,14 +114,13 @@ public:
, m_isAutoRun(item->isAutoRun()) , m_isAutoRun(item->isAutoRun())
, m_autoCreateBuildDirectory(item->autoCreateBuildDirectory()) , m_autoCreateBuildDirectory(item->autoCreateBuildDirectory())
, m_autodetected(item->isAutoDetected()) , m_autodetected(item->isAutoDetected())
, m_isSupported(item->hasFileApi() || item->hasServerMode()) , m_isSupported(item->hasFileApi())
, m_changed(changed) , m_changed(changed)
{ {
updateErrorFlags(); 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(QString::fromUtf8(item->version().fullVersion))
.arg(item->hasFileApi() ? tr("yes") : tr("no")) .arg(item->hasFileApi() ? tr("yes") : tr("no"));
.arg(item->hasServerMode() ? tr("yes") : tr("no"));
} }
CMakeToolTreeItem(const QString &name, CMakeToolTreeItem(const QString &name,

View File

@@ -58,20 +58,11 @@ bool CMakeTool::Generator::matches(const QString &n, const QString &ex) const
namespace Internal { namespace Internal {
const char READER_TYPE_SERVERMODE[] = "servermode";
const char READER_TYPE_FILEAPI[] = "fileapi"; 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) static Utils::optional<CMakeTool::ReaderType> readerTypeFromString(const QString &input)
{ {
// Do not try to be clever here, just use whatever is in the string! // 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) if (input == READER_TYPE_FILEAPI)
return CMakeTool::FileApi; return CMakeTool::FileApi;
return {}; return {};
@@ -80,8 +71,6 @@ static Utils::optional<CMakeTool::ReaderType> readerTypeFromString(const QString
static QString readerTypeToString(const CMakeTool::ReaderType &type) static QString readerTypeToString(const CMakeTool::ReaderType &type)
{ {
switch (type) { switch (type) {
case CMakeTool::ServerMode:
return QString(READER_TYPE_SERVERMODE);
case CMakeTool::FileApi: case CMakeTool::FileApi:
return QString(READER_TYPE_FILEAPI); return QString(READER_TYPE_FILEAPI);
} }
@@ -102,9 +91,7 @@ class IntrospectionData
public: public:
bool m_didAttemptToRun = false; bool m_didAttemptToRun = false;
bool m_didRun = true; bool m_didRun = true;
bool m_hasServerMode = false;
bool m_queriedServerMode = false;
bool m_triedCapabilities = false; bool m_triedCapabilities = false;
QList<CMakeTool::Generator> m_generators; QList<CMakeTool::Generator> m_generators;
@@ -193,13 +180,13 @@ void CMakeTool::setAutoCreateBuildDirectory(bool autoBuildDir)
bool CMakeTool::isValid() const bool CMakeTool::isValid() const
{ {
if (!m_id.isValid()) if (!m_id.isValid() || !m_introspection)
return false; return false;
if (!m_introspection->m_didAttemptToRun) 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 Utils::SynchronousProcessResponse CMakeTool::run(const QStringList &args, int timeoutS) const
@@ -284,12 +271,14 @@ bool CMakeTool::autoCreateBuildDirectory() const
QList<CMakeTool::Generator> CMakeTool::supportedGenerators() const QList<CMakeTool::Generator> CMakeTool::supportedGenerators() const
{ {
readInformation(QueryType::GENERATORS); return isValid() ? m_introspection->m_generators : QList<CMakeTool::Generator>();
return m_introspection->m_generators;
} }
TextEditor::Keywords CMakeTool::keywords() TextEditor::Keywords CMakeTool::keywords()
{ {
if (!isValid())
return {};
if (m_introspection->m_functions.isEmpty() && m_introspection->m_didRun) { if (m_introspection->m_functions.isEmpty() && m_introspection->m_didRun) {
Utils::SynchronousProcessResponse response; Utils::SynchronousProcessResponse response;
response = run({"--help-command-list"}, 5); response = run({"--help-command-list"}, 5);
@@ -317,28 +306,19 @@ TextEditor::Keywords CMakeTool::keywords()
m_introspection->m_functionArgs); m_introspection->m_functionArgs);
} }
bool CMakeTool::hasServerMode() const
{
readInformation(QueryType::SERVER_MODE);
return m_introspection->m_hasServerMode;
}
bool CMakeTool::hasFileApi() const bool CMakeTool::hasFileApi() const
{ {
readInformation(QueryType::SERVER_MODE); return isValid() ? !m_introspection->m_fileApis.isEmpty() : false;
return !m_introspection->m_fileApis.isEmpty();
} }
QVector<std::pair<QString, int>> CMakeTool::supportedFileApiObjects() const QVector<std::pair<QString, int>> CMakeTool::supportedFileApiObjects() const
{ {
readInformation(QueryType::SERVER_MODE); 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>>();
return Utils::transform(m_introspection->m_fileApis, [](const Internal::FileApi &api) { return std::make_pair(api.kind, api.version.first); });
} }
CMakeTool::Version CMakeTool::version() const CMakeTool::Version CMakeTool::version() const
{ {
readInformation(QueryType::VERSION); return isValid() ? m_introspection->m_version : CMakeTool::Version();
return m_introspection->m_version;
} }
bool CMakeTool::isAutoDetected() const 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 return m_readerType; // Allow overriding the auto-detected value via .user files
// Find best possible reader type: // Find best possible reader type:
if (hasFileApi() && !Internal::ignoreFileApi()) if (hasFileApi())
return FileApi; return FileApi;
if (hasServerMode())
return ServerMode;
return {}; return {};
} }
@@ -404,33 +382,16 @@ Utils::FilePath CMakeTool::searchQchFile(const Utils::FilePath &executable)
return {}; 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) if (!m_introspection->m_didRun && m_introspection->m_didAttemptToRun)
return; return;
m_introspection->m_didAttemptToRun = true; m_introspection->m_didAttemptToRun = true;
if (!m_introspection->m_triedCapabilities) {
fetchFromCapabilities(); fetchFromCapabilities();
m_introspection->m_triedCapabilities = true; 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) static QStringList parseDefinition(const QString &definition)
@@ -524,96 +485,16 @@ QStringList CMakeTool::parseVariableOutput(const QString &output)
return result; 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 void CMakeTool::fetchFromCapabilities() const
{ {
Utils::SynchronousProcessResponse response = run({"-E", "capabilities"}); 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()); parseFromCapabilities(response.stdOut());
} else {
m_introspection->m_didRun = false;
}
} }
static int getVersion(const QVariantMap &obj, const QString value) static int getVersion(const QVariantMap &obj, const QString value)
@@ -632,7 +513,6 @@ void CMakeTool::parseFromCapabilities(const QString &input) const
return; return;
const QVariantMap data = doc.object().toVariantMap(); const QVariantMap data = doc.object().toVariantMap();
m_introspection->m_hasServerMode = data.value("serverMode").toBool();
const QVariantList generatorList = data.value("generators").toList(); const QVariantList generatorList = data.value("generators").toList();
for (const QVariant &v : generatorList) { for (const QVariant &v : generatorList) {
const QVariantMap gen = v.toMap(); const QVariantMap gen = v.toMap();

View File

@@ -47,7 +47,7 @@ class CMAKE_EXPORT CMakeTool
public: public:
enum Detection { ManualDetection, AutoDetection }; enum Detection { ManualDetection, AutoDetection };
enum ReaderType { ServerMode, FileApi }; enum ReaderType { FileApi };
struct Version struct Version
{ {
@@ -98,7 +98,6 @@ public:
bool autoCreateBuildDirectory() const; bool autoCreateBuildDirectory() const;
QList<Generator> supportedGenerators() const; QList<Generator> supportedGenerators() const;
TextEditor::Keywords keywords(); TextEditor::Keywords keywords();
bool hasServerMode() const;
bool hasFileApi() const; bool hasFileApi() const;
QVector<std::pair<QString, int>> supportedFileApiObjects() const; QVector<std::pair<QString, int>> supportedFileApiObjects() const;
Version version() const; Version version() const;
@@ -115,21 +114,12 @@ public:
static Utils::FilePath searchQchFile(const Utils::FilePath &executable); static Utils::FilePath searchQchFile(const Utils::FilePath &executable);
private: private:
enum class QueryType { void readInformation() const;
GENERATORS,
SERVER_MODE,
VERSION
};
void readInformation(QueryType type) const;
Utils::SynchronousProcessResponse run(const QStringList &args, int timeoutS = 1) const; Utils::SynchronousProcessResponse run(const QStringList &args, int timeoutS = 1) const;
void parseFunctionDetailsOutput(const QString &output); void parseFunctionDetailsOutput(const QString &output);
QStringList parseVariableOutput(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 fetchFromCapabilities() const;
void parseFromCapabilities(const QString &input) const; void parseFromCapabilities(const QString &input) const;

View File

@@ -97,13 +97,13 @@ void addCMakeInputs(FolderNode *root,
addCMakeVFolder(cmakeVFolder.get(), addCMakeVFolder(cmakeVFolder.get(),
buildDir, buildDir,
100, 100,
QCoreApplication::translate("CMakeProjectManager::Internal::ServerModeReader", QCoreApplication::translate("CMakeProjectManager::Internal::ProjectTreeHelper",
"<Build Directory>"), "<Build Directory>"),
removeKnownNodes(knownFiles, std::move(buildInputs))); removeKnownNodes(knownFiles, std::move(buildInputs)));
addCMakeVFolder(cmakeVFolder.get(), addCMakeVFolder(cmakeVFolder.get(),
Utils::FilePath(), Utils::FilePath(),
10, 10,
QCoreApplication::translate("CMakeProjectManager::Internal::ServerModeReader", QCoreApplication::translate("CMakeProjectManager::Internal::ProjectTreeHelper",
"<Other Locations>"), "<Other Locations>"),
removeKnownNodes(knownFiles, std::move(rootInputs))); removeKnownNodes(knownFiles, std::move(rootInputs)));
@@ -187,7 +187,8 @@ void addHeaderNodes(ProjectNode *root,
auto headerNode = std::make_unique<VirtualFolderNode>(root->filePath()); auto headerNode = std::make_unique<VirtualFolderNode>(root->filePath());
headerNode->setPriority(Node::DefaultPriority - 5); headerNode->setPriority(Node::DefaultPriority - 5);
headerNode->setDisplayName( headerNode->setDisplayName(
QCoreApplication::translate("CMakeProjectManager::Internal::ServerModeReader", "<Headers>")); QCoreApplication::translate("CMakeProjectManager::Internal::ProjectTreeHelper",
"<Headers>"));
headerNode->setIcon(headerNodeIcon); headerNode->setIcon(headerNodeIcon);
// Add scanned headers: // Add scanned headers:

View File

@@ -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

View File

@@ -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);

View File

@@ -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 &section = 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

View File

@@ -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