forked from qt-creator/qt-creator
		
	CMake: Implement helper to talk to CMake server-mode
Implement a helper class that can be used to talk to CMake's server-mode. Change-Id: I1df4af665991a5e0a3acb301ffd28008dd4fe86f Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
		@@ -30,6 +30,7 @@ HEADERS = builddirmanager.h \
 | 
			
		||||
    cmakeautocompleter.h \
 | 
			
		||||
    configmodel.h \
 | 
			
		||||
    configmodelitemdelegate.h \
 | 
			
		||||
    servermode.h \
 | 
			
		||||
    tealeafreader.h
 | 
			
		||||
 | 
			
		||||
SOURCES = builddirmanager.cpp \
 | 
			
		||||
@@ -58,6 +59,7 @@ SOURCES = builddirmanager.cpp \
 | 
			
		||||
    cmakeautocompleter.cpp \
 | 
			
		||||
    configmodel.cpp \
 | 
			
		||||
    configmodelitemdelegate.cpp \
 | 
			
		||||
    servermode.cpp \
 | 
			
		||||
    tealeafreader.cpp
 | 
			
		||||
 | 
			
		||||
RESOURCES += cmakeproject.qrc
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,8 @@ QtcPlugin {
 | 
			
		||||
        "configmodel.h",
 | 
			
		||||
        "configmodelitemdelegate.cpp",
 | 
			
		||||
        "configmodelitemdelegate.h",
 | 
			
		||||
        "servermode.cpp",
 | 
			
		||||
        "servermode.h",
 | 
			
		||||
        "tealeafreader.cpp",
 | 
			
		||||
        "tealeafreader.h"
 | 
			
		||||
    ]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										447
									
								
								src/plugins/cmakeprojectmanager/servermode.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								src/plugins/cmakeprojectmanager/servermode.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,447 @@
 | 
			
		||||
/****************************************************************************
 | 
			
		||||
**
 | 
			
		||||
** 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/algorithm.h>
 | 
			
		||||
#include <utils/qtcassert.h>
 | 
			
		||||
#include <utils/qtcprocess.h>
 | 
			
		||||
 | 
			
		||||
#include <QByteArray>
 | 
			
		||||
#include <QJsonDocument>
 | 
			
		||||
#include <QJsonObject>
 | 
			
		||||
#include <QLocalSocket>
 | 
			
		||||
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
// ----------------------------------------------------------------------
 | 
			
		||||
// Helpers:
 | 
			
		||||
// ----------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
QString socketName(const Utils::FileName &buildDirectory)
 | 
			
		||||
{
 | 
			
		||||
    return buildDirectory.toString() + "/socket";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --------------------------------------------------------------------
 | 
			
		||||
// ServerMode:
 | 
			
		||||
// --------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ServerMode::ServerMode(const Environment &env,
 | 
			
		||||
                       const FileName &sourceDirectory, const FileName &buildDirectory,
 | 
			
		||||
                       const FileName &cmakeExecutable,
 | 
			
		||||
                       const QString &generator, const QString &extraGenerator,
 | 
			
		||||
                       const QString &platform, const QString &toolset,
 | 
			
		||||
                       bool experimental, int major, int minor,
 | 
			
		||||
                       QObject *parent) :
 | 
			
		||||
    QObject(parent),
 | 
			
		||||
    m_sourceDirectory(sourceDirectory), m_buildDirectory(buildDirectory),
 | 
			
		||||
    m_cmakeExecutable(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.reset(new QtcProcess);
 | 
			
		||||
 | 
			
		||||
    m_cmakeProcess->setEnvironment(env);
 | 
			
		||||
    m_cmakeProcess->setWorkingDirectory(buildDirectory.toString());
 | 
			
		||||
    const QStringList args = QStringList({ "-E", "server", "--pipe=" + socketName(buildDirectory) });
 | 
			
		||||
 | 
			
		||||
    connect(m_cmakeProcess.get(), &QtcProcess::started, this, [this]() { m_connectionTimer.start(); });
 | 
			
		||||
    connect(m_cmakeProcess.get(),
 | 
			
		||||
            static_cast<void(QtcProcess::*)(int, QProcess::ExitStatus)>(&QtcProcess::finished),
 | 
			
		||||
            this, &ServerMode::handleCMakeFinished);
 | 
			
		||||
 | 
			
		||||
    QString argumentString;
 | 
			
		||||
    QtcProcess::addArgs(&argumentString, args);
 | 
			
		||||
    if (m_useExperimental)
 | 
			
		||||
        QtcProcess::addArg(&argumentString, "--experimental");
 | 
			
		||||
 | 
			
		||||
    m_cmakeProcess->setCommand(cmakeExecutable.toString(), argumentString);
 | 
			
		||||
 | 
			
		||||
    // Delay start:
 | 
			
		||||
    QTimer::singleShot(0, [argumentString, this] {
 | 
			
		||||
        emit message(tr("Running \"%1 %2\" in %3.")
 | 
			
		||||
                     .arg(m_cmakeExecutable.toUserOutput())
 | 
			
		||||
                     .arg(argumentString)
 | 
			
		||||
                     .arg(m_buildDirectory.toUserOutput()));
 | 
			
		||||
 | 
			
		||||
        m_cmakeProcess->start();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerMode::~ServerMode()
 | 
			
		||||
{
 | 
			
		||||
    if (m_cmakeProcess)
 | 
			
		||||
        m_cmakeProcess->disconnect();
 | 
			
		||||
    if (m_cmakeSocket) {
 | 
			
		||||
        m_cmakeSocket->disconnect();
 | 
			
		||||
        m_cmakeSocket->disconnectFromServer();
 | 
			
		||||
        delete(m_cmakeSocket);
 | 
			
		||||
    }
 | 
			
		||||
    m_cmakeSocket = nullptr;
 | 
			
		||||
    Core::Reaper::reap(m_cmakeProcess.release());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerMode::sendRequest(const QString &type, const QVariantMap &extra, const QVariant &cookie)
 | 
			
		||||
{
 | 
			
		||||
    QTC_ASSERT(m_cmakeSocket, return);
 | 
			
		||||
    ++m_requestCounter;
 | 
			
		||||
 | 
			
		||||
    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() + END_MAGIC;
 | 
			
		||||
    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...
 | 
			
		||||
 | 
			
		||||
    const QString socketString = socketName(m_buildDirectory);
 | 
			
		||||
 | 
			
		||||
    static int counter = 0;
 | 
			
		||||
    ++counter;
 | 
			
		||||
 | 
			
		||||
    if (counter > 50) {
 | 
			
		||||
        counter = 0;
 | 
			
		||||
        m_cmakeProcess->disconnect();
 | 
			
		||||
        reportError(tr("Running \"%1\" failed: Timeout waiting for pipe \"%2\".")
 | 
			
		||||
                    .arg(m_cmakeExecutable.toUserOutput())
 | 
			
		||||
                    .arg(socketString));
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
    connect(socket, static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
 | 
			
		||||
            [this, socket]() {
 | 
			
		||||
        reportError(socket->errorString());
 | 
			
		||||
        socket->disconnect();
 | 
			
		||||
        socket->deleteLater();
 | 
			
		||||
    });
 | 
			
		||||
    connect(socket, &QLocalSocket::connected, [this, socket]() { m_cmakeSocket = socket; });
 | 
			
		||||
    connect(socket, &QLocalSocket::disconnected, [this, socket]() {
 | 
			
		||||
        m_cmakeSocket = nullptr;
 | 
			
		||||
        socket->disconnect();
 | 
			
		||||
        socket->deleteLater();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    socket->connectToServer(socketString);
 | 
			
		||||
    m_connectionTimer.start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerMode::handleCMakeFinished(int code, QProcess::ExitStatus status)
 | 
			
		||||
{
 | 
			
		||||
    QString msg;
 | 
			
		||||
    if (status != QProcess::NormalExit)
 | 
			
		||||
        msg = tr("CMake process \"%1\" crashed.").arg(m_cmakeExecutable.toUserOutput());
 | 
			
		||||
    else if (code != 0)
 | 
			
		||||
        msg = tr("CMake process \"%1\" quit with exit code %2.").arg(m_cmakeExecutable.toUserOutput()).arg(code);
 | 
			
		||||
 | 
			
		||||
    if (!msg.isEmpty()) {
 | 
			
		||||
        reportError(msg);
 | 
			
		||||
    } else {
 | 
			
		||||
        emit message(tr("CMake process \"%1\" quit normally.").arg(m_cmakeExecutable.toUserOutput()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_cmakeSocket) {
 | 
			
		||||
        m_cmakeSocket->disconnect();
 | 
			
		||||
        delete m_cmakeSocket;
 | 
			
		||||
        m_cmakeSocket = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QFile::remove(socketName(m_buildDirectory));
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
{
 | 
			
		||||
    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") {
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        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->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);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        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 QVariant cookie = data.value(COOKIE_KEY);
 | 
			
		||||
        const QString name = data.value(NAME_KEY).toString();
 | 
			
		||||
 | 
			
		||||
        if (name.isEmpty()) {
 | 
			
		||||
            reportError(tr("Received a signal without a name."));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!replyTo.isEmpty() || cookie.isValid()) {
 | 
			
		||||
            reportError(tr("Received a signal in reply to a request."));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        emit cmakeSignal(name, data);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
{
 | 
			
		||||
    emit message(msg);
 | 
			
		||||
    emit errorOccured(msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Internal
 | 
			
		||||
} // namespace CMakeProjectManager
 | 
			
		||||
							
								
								
									
										117
									
								
								src/plugins/cmakeprojectmanager/servermode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/plugins/cmakeprojectmanager/servermode.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
/****************************************************************************
 | 
			
		||||
**
 | 
			
		||||
** 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/qtcprocess.h>
 | 
			
		||||
 | 
			
		||||
#include <QTemporaryDir>
 | 
			
		||||
#include <QTimer>
 | 
			
		||||
#include <QVariantMap>
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
QT_FORWARD_DECLARE_CLASS(QLocalSocket);
 | 
			
		||||
 | 
			
		||||
namespace Utils { class QtcProcess; }
 | 
			
		||||
 | 
			
		||||
namespace CMakeProjectManager {
 | 
			
		||||
namespace Internal {
 | 
			
		||||
 | 
			
		||||
class ServerMode : public QObject
 | 
			
		||||
{
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    ServerMode(const Utils::Environment &env,
 | 
			
		||||
               const Utils::FileName &sourceDirectory, const Utils::FileName &buildDirectory,
 | 
			
		||||
               const Utils::FileName &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 errorOccured(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);
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<Utils::QtcProcess> m_cmakeProcess;
 | 
			
		||||
    QLocalSocket *m_cmakeSocket = nullptr;
 | 
			
		||||
    QTimer m_connectionTimer;
 | 
			
		||||
 | 
			
		||||
    Utils::FileName m_sourceDirectory;
 | 
			
		||||
    Utils::FileName m_buildDirectory;
 | 
			
		||||
    Utils::FileName m_cmakeExecutable;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    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
 | 
			
		||||
		Reference in New Issue
	
	Block a user