Add basic structure of the server
This commit is contained in:
parent
80483a07e0
commit
bd7653433f
94
libnymea-remoteproxy/engine.cpp
Normal file
94
libnymea-remoteproxy/engine.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
#include "engine.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
Engine *Engine::s_instance = nullptr;
|
||||
|
||||
Engine *Engine::instance()
|
||||
{
|
||||
if (!s_instance) {
|
||||
qCDebug(dcEngine()) << "Create server engine";
|
||||
s_instance = new Engine();
|
||||
}
|
||||
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
bool Engine::exists()
|
||||
{
|
||||
return s_instance != nullptr;
|
||||
}
|
||||
|
||||
void Engine::destroy()
|
||||
{
|
||||
qCDebug(dcEngine()) << "Destroy server engine";
|
||||
if (s_instance) {
|
||||
delete s_instance;
|
||||
}
|
||||
|
||||
s_instance = nullptr;
|
||||
}
|
||||
|
||||
void Engine::start()
|
||||
{
|
||||
qCDebug(dcEngine()) << "Start server engine";
|
||||
QUrl proxyUrl;
|
||||
proxyUrl.setScheme("wss");
|
||||
proxyUrl.setHost("0.0.0.0");
|
||||
proxyUrl.setPort(static_cast<int>(m_port));
|
||||
qCDebug(dcApplication()) << "Authentication server" << m_authenticationServerUrl.toString();
|
||||
qCDebug(dcApplication()) << "Start server" << proxyUrl.toString();
|
||||
|
||||
// TODO: init stuff
|
||||
|
||||
setRunning(true);
|
||||
}
|
||||
|
||||
void Engine::stop()
|
||||
{
|
||||
qCDebug(dcEngine()) << "Stop server engine";
|
||||
|
||||
// TODO: deinit stuff
|
||||
|
||||
setRunning(false);
|
||||
}
|
||||
|
||||
bool Engine::running() const
|
||||
{
|
||||
return m_running;
|
||||
}
|
||||
|
||||
void Engine::setHost(const QHostAddress &hostAddress)
|
||||
{
|
||||
m_hostAddress = hostAddress;
|
||||
}
|
||||
|
||||
void Engine::setAuthenticationServerUrl(const QUrl &url)
|
||||
{
|
||||
m_authenticationServerUrl = url;
|
||||
}
|
||||
|
||||
void Engine::setPort(const quint16 &port)
|
||||
{
|
||||
m_port = port;
|
||||
}
|
||||
|
||||
void Engine::setSslConfiguration(const QSslConfiguration &configuration)
|
||||
{
|
||||
m_sslConfiguration = configuration;
|
||||
}
|
||||
|
||||
Engine::Engine(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Engine::setRunning(bool running)
|
||||
{
|
||||
if (m_running == running)
|
||||
return;
|
||||
|
||||
qCDebug(dcEngine()) << "Engine is" << (running ? "now running." : "not running any more.");
|
||||
m_running = running;
|
||||
emit runningChanged(m_running);
|
||||
}
|
||||
46
libnymea-remoteproxy/engine.h
Normal file
46
libnymea-remoteproxy/engine.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef ENGINE_H
|
||||
#define ENGINE_H
|
||||
|
||||
#include <QUrl>
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
#include <QSslConfiguration>
|
||||
|
||||
class Engine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static Engine *instance();
|
||||
static bool exists();
|
||||
void destroy();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool running() const;
|
||||
|
||||
void setHost(const QHostAddress &hostAddress);
|
||||
void setAuthenticationServerUrl(const QUrl &url);
|
||||
void setPort(const quint16 &port);
|
||||
void setSslConfiguration(const QSslConfiguration &configuration);
|
||||
|
||||
private:
|
||||
explicit Engine(QObject *parent = nullptr);
|
||||
static Engine *s_instance;
|
||||
|
||||
bool m_running = false;
|
||||
|
||||
quint16 m_port = 0;
|
||||
QHostAddress m_hostAddress;
|
||||
QSslConfiguration m_sslConfiguration;
|
||||
|
||||
QUrl m_authenticationServerUrl;
|
||||
|
||||
void setRunning(bool running);
|
||||
|
||||
signals:
|
||||
void runningChanged(bool running);
|
||||
|
||||
};
|
||||
|
||||
#endif // ENGINE_H
|
||||
12
libnymea-remoteproxy/libnymea-remoteproxy.pro
Normal file
12
libnymea-remoteproxy/libnymea-remoteproxy.pro
Normal file
@ -0,0 +1,12 @@
|
||||
include(../nymea-remoteproxy.pri)
|
||||
|
||||
TEMPLATE = lib
|
||||
TARGET = nymea-remoteproxy
|
||||
|
||||
HEADERS += \
|
||||
engine.h \
|
||||
loggingcategories.h
|
||||
|
||||
SOURCES += \
|
||||
engine.cpp \
|
||||
loggingcategories.cpp
|
||||
10
libnymea-remoteproxy/loggingcategories.cpp
Normal file
10
libnymea-remoteproxy/loggingcategories.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
#include "loggingcategories.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(dcApplication, "Application")
|
||||
Q_LOGGING_CATEGORY(dcEngine, "Engine")
|
||||
Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc")
|
||||
Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer")
|
||||
Q_LOGGING_CATEGORY(dcAuthenticator, "Authenticator")
|
||||
Q_LOGGING_CATEGORY(dcDebug, "Debug")
|
||||
Q_LOGGING_CATEGORY(dcConnectionManager, "ConnectionManager")
|
||||
|
||||
15
libnymea-remoteproxy/loggingcategories.h
Normal file
15
libnymea-remoteproxy/loggingcategories.h
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef LOGGINGCATEGORIES_H
|
||||
#define LOGGINGCATEGORIES_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcApplication)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcEngine)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcAuthenticator)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcConnectionManager)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcDebug)
|
||||
|
||||
#endif // LOGGINGCATEGORIES_H
|
||||
8
main.cpp
8
main.cpp
@ -1,8 +0,0 @@
|
||||
#include <QCoreApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
10
nymea-remoteproxy.pri
Normal file
10
nymea-remoteproxy.pri
Normal file
@ -0,0 +1,10 @@
|
||||
QT *= network
|
||||
QT -= gui
|
||||
|
||||
CONFIG += c++11 console
|
||||
|
||||
QMAKE_CXXFLAGS *= -Werror -std=c++11 -g
|
||||
QMAKE_LFLAGS *= -std=c++11
|
||||
|
||||
top_srcdir=$$PWD
|
||||
top_builddir=$$shadowed($$PWD)
|
||||
@ -1,23 +1,8 @@
|
||||
QT -= gui
|
||||
include(nymea-remoteproxy.pri)
|
||||
|
||||
CONFIG += c++11 console
|
||||
CONFIG -= app_bundle
|
||||
TEMPLATE=subdirs
|
||||
SUBDIRS += server libnymea-remoteproxy tests
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any feature of Qt which as been marked deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
server.depends = libnymea-remoteproxy
|
||||
tests.depends = libnymea-remoteproxy
|
||||
|
||||
# You can also make your code fail to compile if you use deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += \
|
||||
main.cpp
|
||||
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
216
server/main.cpp
Normal file
216
server/main.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#include <QtDebug>
|
||||
#include <QSslKey>
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <QTextStream>
|
||||
#include <QMessageLogger>
|
||||
#include <QSslCertificate>
|
||||
#include <QCoreApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QLoggingCategory>
|
||||
#include <QSslConfiguration>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "engine.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
|
||||
static QHash<QString, bool> s_loggingFilters;
|
||||
|
||||
static QFile s_logFile;
|
||||
static bool s_loggingEnabled = false;
|
||||
|
||||
static const char *const normal = "\033[0m";
|
||||
static const char *const warning = "\e[33m";
|
||||
static const char *const error = "\e[31m";
|
||||
|
||||
static void loggingCategoryFilter(QLoggingCategory *category)
|
||||
{
|
||||
if (s_loggingFilters.contains(category->categoryName())) {
|
||||
bool debugEnabled = s_loggingFilters.value(category->categoryName());
|
||||
category->setEnabled(QtDebugMsg, debugEnabled);
|
||||
category->setEnabled(QtWarningMsg, debugEnabled || s_loggingFilters.value("Warnings"));
|
||||
} else {
|
||||
category->setEnabled(QtDebugMsg, true);
|
||||
category->setEnabled(QtWarningMsg, s_loggingFilters.value("Warnings"));
|
||||
}
|
||||
}
|
||||
|
||||
static void consoleLogHandler(QtMsgType type, const QMessageLogContext& context, const QString& message)
|
||||
{
|
||||
QString messageString;
|
||||
QString timeString = QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz");
|
||||
switch (type) {
|
||||
case QtInfoMsg:
|
||||
messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
|
||||
fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data());
|
||||
break;
|
||||
case QtDebugMsg:
|
||||
messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
|
||||
fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data());
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
messageString = QString(" W %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
|
||||
fprintf(stdout, "%s W | %s: %s%s\n", warning, context.category, message.toUtf8().data(), normal);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
messageString = QString(" C %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
|
||||
fprintf(stdout, "%s C | %s: %s%s\n", error, context.category, message.toUtf8().data(), normal);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
messageString = QString(" F %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
|
||||
fprintf(stdout, "%s F | %s: %s%s\n", error, context.category, message.toUtf8().data(), normal);
|
||||
break;
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
if (s_logFile.isOpen()) {
|
||||
QTextStream textStream(&s_logFile);
|
||||
textStream << messageString << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qInstallMessageHandler(consoleLogHandler);
|
||||
|
||||
QCoreApplication application(argc, argv);
|
||||
application.setApplicationName("nymea-remoteproxy");
|
||||
application.setOrganizationName("guh");
|
||||
application.setApplicationVersion("0.0.1");
|
||||
|
||||
s_loggingFilters.insert("Application", true);
|
||||
s_loggingFilters.insert("Engine", true);
|
||||
s_loggingFilters.insert("JsonRpc", true);
|
||||
s_loggingFilters.insert("WebSocketServer", true);
|
||||
s_loggingFilters.insert("Authenticator", true);
|
||||
s_loggingFilters.insert("ConnectionManager", true);
|
||||
s_loggingFilters.insert("Debug", false);
|
||||
|
||||
// command line parser
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.setApplicationDescription(QString("\nThe nymea remote proxy server.\n\n"
|
||||
"Copyright %1 2018 Simon Stürz <simon.stuerz@guh.io>").arg(QChar(0xA9)));
|
||||
|
||||
QCommandLineOption logfileOption(QStringList() << "l" << "logging", "Write log file to the given logfile.", "logfile", "/var/log/nymea-remoteproxy.log");
|
||||
parser.addOption(logfileOption);
|
||||
|
||||
QCommandLineOption serverOption(QStringList() << "s" << "server", "The authentication server url.", "url", "http://localhost:8000");
|
||||
parser.addOption(serverOption);
|
||||
|
||||
QCommandLineOption portOption(QStringList() << "p" << "port", "The proxy server port.", "port", "1212");
|
||||
parser.addOption(portOption);
|
||||
|
||||
QCommandLineOption certOption(QStringList() << "c" <<"certificate", "The path to the SSL certificate.", "certificate");
|
||||
parser.addOption(certOption);
|
||||
|
||||
QCommandLineOption certKeyOption(QStringList() << "k" << "certificate-key", "The path to the SSL certificate key (KEY).", "certificate-key");
|
||||
parser.addOption(certKeyOption);
|
||||
|
||||
QCommandLineOption verboseOption(QStringList() << "v" << "verbose", "Print the whole traffic.");
|
||||
parser.addOption(verboseOption);
|
||||
|
||||
parser.process(application);
|
||||
|
||||
if (parser.isSet(verboseOption))
|
||||
s_loggingFilters["Debug"] = true;
|
||||
|
||||
QLoggingCategory::installFilter(loggingCategoryFilter);
|
||||
|
||||
// Open the logfile, if any specified
|
||||
if (parser.isSet(logfileOption)) {
|
||||
QFileInfo fi(parser.value(logfileOption));
|
||||
QDir logDir(fi.absolutePath());
|
||||
if (!logDir.exists() && !logDir.mkpath(logDir.absolutePath())) {
|
||||
qCCritical(dcApplication()) << "Error opening log file" << parser.value(logfileOption);
|
||||
return 1;
|
||||
}
|
||||
s_logFile.setFileName(parser.value(logfileOption));
|
||||
if (!s_logFile.open(QFile::WriteOnly | QFile::Append)) {
|
||||
qCCritical(dcApplication()) << "Error opening log file" << parser.value(logfileOption);
|
||||
return 1;
|
||||
}
|
||||
s_loggingEnabled = true;
|
||||
}
|
||||
|
||||
qCDebug(dcApplication()) << "==============================================";
|
||||
qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion();
|
||||
qCDebug(dcApplication()) << "==============================================";
|
||||
|
||||
if (s_loggingEnabled)
|
||||
qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName();
|
||||
|
||||
// Authentication server url
|
||||
QUrl serverUrl("http://localhost:8000");
|
||||
if (parser.isSet(serverOption))
|
||||
serverUrl = QUrl(parser.value(serverOption));
|
||||
|
||||
if (!serverUrl.isValid()) {
|
||||
qCCritical(dcApplication()) << "Invalid authentication server url:" << parser.value(serverOption);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Port
|
||||
uint port = 1212;
|
||||
if (parser.isSet(portOption)) {
|
||||
bool ok = false;
|
||||
port = parser.value(portOption).toUInt(&ok);
|
||||
if (!ok) {
|
||||
qCCritical(dcApplication()) << "Invalud port value:" << parser.value(portOption);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (port > 65535) {
|
||||
qCCritical(dcApplication()) << "Port value is out of range:" << parser.value(portOption);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// SSL certificate
|
||||
QSslConfiguration sslConfiguration;
|
||||
if (parser.isSet(certOption)) {
|
||||
// Load certificate
|
||||
QFile certFile(parser.value(certOption));
|
||||
if (!certFile.open(QIODevice::ReadOnly)) {
|
||||
qCCritical(dcApplication()) << "Could not open certificate file:" << parser.value(certOption) << certFile.errorString();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
QSslCertificate certificate(&certFile, QSsl::Pem);
|
||||
qCDebug(dcApplication()) << "Loaded successfully certificate" << parser.value(certOption);
|
||||
certFile.close();
|
||||
|
||||
// Create SSL configuration
|
||||
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
sslConfiguration.setLocalCertificate(certificate);
|
||||
sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater);
|
||||
}
|
||||
|
||||
// SSL key
|
||||
if (parser.isSet(certKeyOption)) {
|
||||
QFile certKeyFile(parser.value(certKeyOption));
|
||||
if (!certKeyFile.open(QIODevice::ReadOnly)) {
|
||||
qCCritical(dcApplication()) << "ERROR: Could not open certificate key file:" << parser.value(certKeyOption) << certKeyFile.errorString();
|
||||
exit(-1);
|
||||
}
|
||||
QSslKey sslKey(&certKeyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
|
||||
qCDebug(dcApplication()) << "Loaded successfully certificate key" << parser.value(certKeyOption);
|
||||
certKeyFile.close();
|
||||
sslConfiguration.setPrivateKey(sslKey);
|
||||
}
|
||||
|
||||
Engine::instance()->setAuthenticationServerUrl(serverUrl);
|
||||
Engine::instance()->setPort(static_cast<quint16>(port));
|
||||
Engine::instance()->setSslConfiguration(sslConfiguration);
|
||||
Engine::instance()->start();
|
||||
|
||||
return application.exec();
|
||||
}
|
||||
13
server/server.pro
Normal file
13
server/server.pro
Normal file
@ -0,0 +1,13 @@
|
||||
include(../nymea-remoteproxy.pri)
|
||||
|
||||
TARGET = nymea-remoteproxy
|
||||
TEMPLATE = app
|
||||
|
||||
INCLUDEPATH += ../libnymea-remoteproxy
|
||||
|
||||
LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy
|
||||
|
||||
SOURCES += main.cpp
|
||||
|
||||
target.path = /usr/bin
|
||||
INSTALLS += target
|
||||
59
tests/nymea-remoteproxy-tests.cpp
Normal file
59
tests/nymea-remoteproxy-tests.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
#include "nymea-remoteproxy-tests.h"
|
||||
|
||||
#include "engine.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
RemoteProxyTests::RemoteProxyTests(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RemoteProxyTests::cleanUpEngine()
|
||||
{
|
||||
if (Engine::exists()) {
|
||||
Engine::instance()->destroy();
|
||||
QVERIFY(!Engine::exists());
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteProxyTests::restartEngine()
|
||||
{
|
||||
cleanUpEngine();
|
||||
|
||||
QVERIFY(Engine::instance() != nullptr);
|
||||
QVERIFY(Engine::exists());
|
||||
}
|
||||
|
||||
void RemoteProxyTests::initTestCase()
|
||||
{
|
||||
qCDebug(dcApplication()) << "Init test case.";
|
||||
|
||||
restartEngine();
|
||||
}
|
||||
|
||||
void RemoteProxyTests::cleanupTestCase()
|
||||
{
|
||||
qCDebug(dcApplication()) << "Clean up test case.";
|
||||
|
||||
cleanUpEngine();
|
||||
}
|
||||
|
||||
void RemoteProxyTests::startStopServer()
|
||||
{
|
||||
cleanUpEngine();
|
||||
|
||||
QVERIFY(Engine::instance() != nullptr);
|
||||
QVERIFY(Engine::exists());
|
||||
|
||||
Engine::instance()->start();
|
||||
QVERIFY(Engine::instance()->running());
|
||||
|
||||
Engine::instance()->stop();
|
||||
QVERIFY(!Engine::instance()->running());
|
||||
|
||||
Engine::instance()->destroy();
|
||||
QVERIFY(!Engine::exists());
|
||||
}
|
||||
|
||||
QTEST_MAIN(RemoteProxyTests)
|
||||
30
tests/nymea-remoteproxy-tests.h
Normal file
30
tests/nymea-remoteproxy-tests.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef NYMEA_REMOTEPROXY_TESTS_H
|
||||
#define NYMEA_REMOTEPROXY_TESTS_H
|
||||
|
||||
#include <QtTest>
|
||||
#include <QObject>
|
||||
|
||||
class RemoteProxyTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RemoteProxyTests(QObject *parent = nullptr);
|
||||
|
||||
private:
|
||||
void cleanUpEngine();
|
||||
void restartEngine();
|
||||
|
||||
protected slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
|
||||
private slots:
|
||||
void startStopServer();
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // NYMEA_REMOTEPROXY_TESTS_H
|
||||
15
tests/tests.pro
Normal file
15
tests/tests.pro
Normal file
@ -0,0 +1,15 @@
|
||||
include(../nymea-remoteproxy.pri)
|
||||
|
||||
CONFIG += testcase
|
||||
QT += testlib
|
||||
|
||||
TARGET = nymea-remoteproxy-tests
|
||||
|
||||
INCLUDEPATH += ../libnymea-remoteproxy
|
||||
LIBS += -L$$top_builddir/libnymea-remoteproxy/ -lnymea-remoteproxy
|
||||
|
||||
HEADERS += nymea-remoteproxy-tests.h
|
||||
SOURCES += nymea-remoteproxy-tests.cpp
|
||||
|
||||
target.path = /usr/bin
|
||||
INSTALLS += target
|
||||
Reference in New Issue
Block a user