diff --git a/src/backends/python/pythonserver.cpp b/src/backends/python/pythonserver.cpp index a2586222..fff804f8 100644 --- a/src/backends/python/pythonserver.cpp +++ b/src/backends/python/pythonserver.cpp @@ -1,228 +1,228 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2015 Minh Ngo */ #include "pythonserver.h" +#include -#include -#include #include -PythonServer::PythonServer(QObject* parent) : QObject(parent), m_pModule(nullptr) -{ -} +using namespace std; namespace { - QString pyObjectToQString(PyObject* obj) + string pyObjectToQString(PyObject* obj) { #if PY_MAJOR_VERSION == 3 - return QString::fromUtf8(PyUnicode_AsUTF8(obj)); + return string(PyUnicode_AsUTF8(obj)); #elif PY_MAJOR_VERSION == 2 - return QString::fromLocal8Bit(PyString_AsString(obj)); + return string(PyString_AsString(obj)); #else #warning Unknown Python version #endif } } void PythonServer::login() { Py_InspectFlag = 1; Py_Initialize(); m_pModule = PyImport_AddModule("__main__"); - filePath = QStringLiteral("python_cantor_worksheet"); + filePath = "python_cantor_worksheet"; } void PythonServer::interrupt() { PyErr_SetInterrupt(); } -void PythonServer::runPythonCommand(const QString& command) const +void PythonServer::runPythonCommand(const string& command) const { PyObject* py_dict = PyModule_GetDict(m_pModule); const char* prepareCommand = "import sys;\n"\ "class CatchOutPythonBackend:\n"\ " def __init__(self):\n"\ " self.value = ''\n"\ " def write(self, txt):\n"\ " self.value += txt\n"\ "outputPythonBackend = CatchOutPythonBackend()\n"\ "errorPythonBackend = CatchOutPythonBackend()\n"\ "sys.stdout = outputPythonBackend\n"\ "sys.stderr = errorPythonBackend\n"; PyRun_SimpleString(prepareCommand); #if PY_MAJOR_VERSION == 3 - PyObject* compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input); + PyObject* compile = Py_CompileString(command.c_str(), filePath.c_str(), Py_single_input); // There are two reasons for the error: // 1) This code is not single expression, so we can't compile this with flag Py_single_input // 2) There are errors in the code if (PyErr_Occurred()) { PyErr_Clear(); // Try to recompile code as sequence of expressions - compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input); + compile = Py_CompileString(command.c_str(), filePath.c_str(), Py_file_input); if (PyErr_Occurred()) { // We now know, that we have a syntax error, so print the traceback and exit PyErr_PrintEx(0); return; } } PyEval_EvalCode(compile, py_dict, py_dict); #elif PY_MAJOR_VERSION == 2 // Python 2.X don't check, that input string contains only one expression. // So for checking this, we compile string as file and as single expression and compare bytecode // FIXME? - PyObject* codeFile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input); + PyObject* codeFile = Py_CompileString(command.c_str(), filePath.c_str(), Py_file_input); if (PyErr_Occurred()) { PyErr_PrintEx(0); return; } - PyObject* codeSingle = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input); + PyObject* codeSingle = Py_CompileString(command.c_str(), filePath.c_str(), Py_single_input); if (PyErr_Occurred()) { // We have error with Py_single_input, but haven't error with Py_file_input // So, the code can't be compiled as singel input -> use file input right away PyErr_Clear(); PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict); } else { PyObject* bytecode1 = ((PyCodeObject*)codeSingle)->co_code; PyObject* bytecode2 = ((PyCodeObject*)codeFile)->co_code; if (PyObject_Length(bytecode1) >= PyObject_Length(bytecode2)) { PyEval_EvalCode((PyCodeObject*)codeSingle, py_dict, py_dict); } else { PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict); } } #else #warning Unknown Python version #endif if (PyErr_Occurred()) PyErr_PrintEx(0); } -QString PythonServer::getError() const +string PythonServer::getError() const { PyObject *errorPython = PyObject_GetAttrString(m_pModule, "errorPythonBackend"); PyObject *error = PyObject_GetAttrString(errorPython, "value"); return pyObjectToQString(error); } -QString PythonServer::getOutput() const +string PythonServer::getOutput() const { PyObject *outputPython = PyObject_GetAttrString(m_pModule, "outputPythonBackend"); PyObject *output = PyObject_GetAttrString(outputPython, "value"); return pyObjectToQString(output); } -void PythonServer::setFilePath(const QString& path) +void PythonServer::setFilePath(const string& path, const string& dir) { - PyRun_SimpleString(("import sys; sys.argv = ['" + path.toStdString() + "']").c_str()); - if (path.isEmpty()) // New session, not from file + PyRun_SimpleString(("import sys; sys.argv = ['" + path + "']").c_str()); + if (path.length() == 0) // New session, not from file { PyRun_SimpleString("import sys; sys.path.insert(0, '')"); } else { this->filePath = path; - QString dir = QFileInfo(path).absoluteDir().absolutePath(); - PyRun_SimpleString(("import sys; sys.path.insert(0, '" + dir.toStdString() + "')").c_str()); - PyRun_SimpleString(("__file__ = '"+path.toStdString()+"'").c_str()); + PyRun_SimpleString(("import sys; sys.path.insert(0, '" + dir + "')").c_str()); + PyRun_SimpleString(("__file__ = '"+path+"'").c_str()); } } -QString PythonServer::variables(bool parseValue) const +string PythonServer::variables(bool parseValue) const { PyRun_SimpleString( "try: \n" " import numpy \n" " __cantor_numpy_internal__ = numpy.get_printoptions()['threshold'] \n" " numpy.set_printoptions(threshold=100000000) \n" #if PY_MAJOR_VERSION == 3 "except ModuleNotFoundError: \n" #elif PY_MAJOR_VERSION == 2 "except ImportError: \n" #endif " pass \n" ); PyRun_SimpleString("__tmp_globals__ = globals()"); PyObject* globals = PyObject_GetAttrString(m_pModule,"__tmp_globals__"); PyObject *key, *value; Py_ssize_t pos = 0; - QStringList vars; + vector vars; while (PyDict_Next(globals, &pos, &key, &value)) { - const QString& keyString = pyObjectToQString(key); - if (keyString.startsWith(QLatin1String("__"))) + const string& keyString = pyObjectToQString(key); + if (keyString.substr(0, 2) == string("__")) continue; - if (keyString == QLatin1String("CatchOutPythonBackend") - || keyString == QLatin1String("errorPythonBackend") - || keyString == QLatin1String("outputPythonBackend")) + if (keyString == string("CatchOutPythonBackend") + || keyString == string("errorPythonBackend") + || keyString == string("outputPythonBackend")) continue; if (PyModule_Check(value)) continue; if (PyFunction_Check(value)) continue; if (PyType_Check(value)) continue; - QString valueString; + string valueString; if (parseValue) valueString = pyObjectToQString(PyObject_Repr(value)); - - vars.append(keyString + QChar(17) + valueString); + vars.push_back(keyString + char(17) + valueString); } PyRun_SimpleString( "try: \n" " import numpy \n" " numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n" " del __cantor_numpy_internal__ \n" #if PY_MAJOR_VERSION == 3 "except ModuleNotFoundError: \n" #elif PY_MAJOR_VERSION == 2 "except ImportError: \n" #endif " pass \n" ); - return vars.join(QChar(18))+QChar(18); + + string result; + for (const string& s : vars) + result += s + char(18); + result += char(18); + return result; } diff --git a/src/backends/python/pythonserver.h b/src/backends/python/pythonserver.h index b720f1f1..76919cc3 100644 --- a/src/backends/python/pythonserver.h +++ b/src/backends/python/pythonserver.h @@ -1,49 +1,47 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2015 Minh Ngo */ #ifndef _PYTHONSERVER_H #define _PYTHONSERVER_H -#include -#include +#include struct _object; using PyObject = _object; -class PythonServer : public QObject +class PythonServer { - Q_OBJECT public: - explicit PythonServer(QObject* parent = nullptr); + explicit PythonServer() = default; - public Q_SLOTS: + public: void login(); void interrupt(); - void setFilePath(const QString& path); - void runPythonCommand(const QString& command) const; - QString getOutput() const; - QString getError() const; - QString variables(bool parseValue) const; + void setFilePath(const std::string& path, const std::string& dir); + void runPythonCommand(const std::string& command) const; + std::string getOutput() const; + std::string getError() const; + std::string variables(bool parseValue) const; private: - PyObject* m_pModule; - QString filePath; + PyObject* m_pModule{nullptr}; + std::string filePath; }; #endif diff --git a/src/backends/python/pythonservermain.cpp b/src/backends/python/pythonservermain.cpp index 4f05d2fe..99aab81c 100644 --- a/src/backends/python/pythonservermain.cpp +++ b/src/backends/python/pythonservermain.cpp @@ -1,142 +1,140 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2015 Minh Ngo */ #include #include - -#include -#include -#include -#include +#include +#include #include "pythonserver.h" -const QChar recordSep(30); -const QChar unitSep(31); +using namespace std; + const char messageEnd = 29; +const char recordSep = 30; +const char unitSep = 31; PythonServer server; bool isInterrupted = false; -QTimer inputTimer; -QMetaObject::Connection connection; - -QString inputBuffer; - -QLatin1String LOGIN("login"); -QLatin1String EXIT("exit"); -QLatin1String CODE("code"); -QLatin1String FILEPATH("setFilePath"); -QLatin1String MODEL("model"); - -void routeInput() { - QByteArray bytes; - char c; - while (std::cin.get(c)) - { - if (messageEnd == c) - break; - else - bytes.append(c); - } - inputBuffer.append(QString::fromLocal8Bit(bytes)); - if (inputBuffer.isEmpty()) - return; - - const QStringList& records = inputBuffer.split(recordSep); - inputBuffer.clear(); - if (records.size() == 2) - { - if (records[0] == EXIT) - { - QObject::disconnect(connection); - QObject::connect(&inputTimer, &QTimer::timeout, QCoreApplication::instance(), &QCoreApplication::quit); - } - else if (records[0] == LOGIN) - { - server.login(); - } - else if (records[0] == CODE) - { - server.runPythonCommand(records[1]); - - if (!isInterrupted) - { - const QString& result = - server.getOutput() - + unitSep - + server.getError() - + QLatin1Char(messageEnd); - - const QByteArray bytes = result.toLocal8Bit(); - std::cout << bytes.data(); - } - else - { - // No replay when interrupted - isInterrupted = false; - } - } - else if (records[0] == FILEPATH) - { - server.setFilePath(records[1]); - } - else if (records[0] == MODEL) - { - bool ok; - bool val = records[1].toInt(&ok); - - QString result; - if (ok) - result = server.variables(val) + unitSep; - else - result = unitSep + QLatin1String("Invalid argument %1 for 'model' command", val); - result += QLatin1Char(messageEnd); - - const QByteArray bytes = result.toLocal8Bit(); - std::cout << bytes.data(); - } - std::cout.flush(); - } -} +string LOGIN("login"); +string EXIT("exit"); +string CODE("code"); +string FILEPATH("setFilePath"); +string MODEL("model"); void signal_handler(int signal) { if (signal == SIGINT) { isInterrupted = true; server.interrupt(); } } +vector split(string s, char delimiter) +{ + vector results; + + size_t pos = 0; + std::string token; + while ((pos = s.find(delimiter)) != std::string::npos) { + token = s.substr(0, pos); + results.push_back(token); + s.erase(0, pos + 1); + } + results.push_back(s); + + return results; +} -int main(int argc, char *argv[]) +int main() { std::signal(SIGINT, signal_handler); - QCoreApplication app(argc, argv); - - connection = QObject::connect(&inputTimer, &QTimer::timeout, routeInput); - inputTimer.setInterval(100); - inputTimer.start(); std::cout << "ready" << std::endl; - return app.exec(); + std::string input; + while (getline(std::cin, input, messageEnd)) + { + const vector& records = split(input, recordSep); + + if (records.size() == 2) + { + if (records[0] == EXIT) + { + //Exit from cycle and finish program + break; + } + else if (records[0] == LOGIN) + { + server.login(); + } + if (records[0] == FILEPATH) + { + vector args = split(records[1], unitSep); + if (args.size() == 2) + server.setFilePath(args[1], args[2]); + } + else if (records[0] == CODE) + { + server.runPythonCommand(records[1]); + + if (!isInterrupted) + { + const string& result = + server.getOutput() + + unitSep + + server.getError() + + messageEnd; + + std::cout << result.c_str(); + } + else + { + // No replay when interrupted + isInterrupted = false; + } + } + else if (records[0] == MODEL) + { + bool ok, val; + try { + val = (bool)stoi(records[1]); + ok = true; + } catch (std::invalid_argument e) { + ok = false; + }; + + string result; + if (ok) + result = server.variables(val) + unitSep; + else + result = unitSep + string("Invalid argument for 'model' command"); + result += messageEnd; + + std::cout << result.c_str(); + } + std::cout.flush(); + } + } + + return 0; } diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp index 3dac322f..b814599e 100644 --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -1,258 +1,270 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2012 Filipe Saraiva Copyright (C) 2015 Minh Ngo */ #include #include "pythonsession.h" #include "pythonexpression.h" #include "pythonvariablemodel.h" #include "pythonhighlighter.h" #include "pythoncompletionobject.h" #include "pythonkeywords.h" #include "pythonutils.h" #include #include #include #include +#include #include #include #ifndef Q_OS_WIN #include #endif const QChar recordSep(30); const QChar unitSep(31); const QChar messageEnd = 29; PythonSession::PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName) : Session(backend) , m_process(nullptr) , serverName(serverName) , m_pythonVersion(pythonVersion) { setVariableModel(new PythonVariableModel(this)); } PythonSession::~PythonSession() { if (m_process) { disconnect(m_process, &QProcess::errorOccurred, this, &PythonSession::reportServerProcessError); m_process->kill(); m_process->deleteLater(); m_process = nullptr; } } void PythonSession::login() { qDebug()<<"login"; emit loginStarted(); if (m_process) m_process->deleteLater(); m_process = new QProcess(this); m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); m_process->start(QStandardPaths::findExecutable(serverName)); m_process->waitForStarted(); m_process->waitForReadyRead(); QTextStream stream(m_process->readAllStandardOutput()); const QString& readyStatus = QString::fromLatin1("ready"); while (m_process->state() == QProcess::Running) { const QString& rl = stream.readLine(); if (rl == readyStatus) break; } connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonSession::readOutput); connect(m_process, &QProcess::errorOccurred, this, &PythonSession::reportServerProcessError); sendCommand(QLatin1String("login")); - sendCommand(QLatin1String("setFilePath"), QStringList(worksheetPath)); + QString dir; + if (!worksheetPath.isEmpty()) + dir = QFileInfo(worksheetPath).absoluteDir().absolutePath(); + sendCommand(QLatin1String("setFilePath"), QStringList() << worksheetPath << dir); const QStringList& scripts = autorunScripts(); if(!scripts.isEmpty()){ QString autorunScripts = scripts.join(QLatin1String("\n")); evaluateExpression(autorunScripts, Cantor::Expression::DeleteOnFinish, true); variableModel()->update(); } changeStatus(Session::Done); emit loginDone(); } void PythonSession::logout() { if (!m_process) return; sendCommand(QLatin1String("exit")); if(!m_process->waitForFinished(1000)) { disconnect(m_process, &QProcess::errorOccurred, this, &PythonSession::reportServerProcessError); m_process->kill(); qDebug()<<"cantor_python server still running, process kill enforced"; } m_process->deleteLater(); m_process = nullptr; qDebug()<<"logout"; Session::logout(); } void PythonSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process && m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } for (Cantor::Expression* expression : expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); m_output.clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } Cantor::Expression* PythonSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; PythonExpression* expr = new PythonExpression(this, internal); changeStatus(Cantor::Session::Running); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } QSyntaxHighlighter* PythonSession::syntaxHighlighter(QObject* parent) { return new PythonHighlighter(parent, this, m_pythonVersion); } Cantor::CompletionObject* PythonSession::completionFor(const QString& command, int index) { return new PythonCompletionObject(command, index, this); } void PythonSession::runFirstExpression() { if (expressionQueue().isEmpty()) return; Cantor::Expression* expr = expressionQueue().first(); const QString command = expr->internalCommand(); qDebug() << "run first expression" << command; expr->setStatus(Cantor::Expression::Computing); if (expr->isInternal() && command.startsWith(QLatin1String("%variables "))) { const QString arg = command.section(QLatin1String(" "), 1); sendCommand(QLatin1String("model"), QStringList(arg)); } else sendCommand(QLatin1String("code"), QStringList(expr->internalCommand())); } void PythonSession::sendCommand(const QString& command, const QStringList arguments) const { qDebug() << "send command: " << command << arguments; const QString& message = command + recordSep + arguments.join(unitSep) + messageEnd; m_process->write(message.toLocal8Bit()); } void PythonSession::readOutput() { while (m_process->bytesAvailable() > 0) - m_output.append(QString::fromLocal8Bit(m_process->readAll())); + { + const QByteArray& bytes = m_process->readAll(); + if (m_pythonVersion == 3) + m_output.append(QString::fromUtf8(bytes)); + else if (m_pythonVersion == 2) + m_output.append(QString::fromLocal8Bit(bytes)); + else + qCritical() << "Unsupported Python version" << m_pythonVersion; + } qDebug() << "m_output: " << m_output; if (!m_output.contains(messageEnd)) return; const QStringList packages = m_output.split(messageEnd, QString::SkipEmptyParts); if (m_output.endsWith(messageEnd)) m_output.clear(); else m_output = m_output.section(messageEnd, -1); for (const QString& message: packages) { if (expressionQueue().isEmpty()) break; const QString& output = message.section(unitSep, 0, 0); const QString& error = message.section(unitSep, 1, 1); if(error.isEmpty()){ static_cast(expressionQueue().first())->parseOutput(output); } else { static_cast(expressionQueue().first())->parseError(error); } finishFirstExpression(true); } } void PythonSession::setWorksheetPath(const QString& path) { worksheetPath = path; } void PythonSession::reportServerProcessError(QProcess::ProcessError serverError) { switch(serverError) { case QProcess::Crashed: emit error(i18n("Cantor Python server stopped working.")); break; case QProcess::FailedToStart: emit error(i18n("Failed to start Cantor python server.")); break; default: emit error(i18n("Communication with Cantor python server failed for unknown reasons.")); break; } logout(); } diff --git a/src/backends/python2/CMakeLists.txt b/src/backends/python2/CMakeLists.txt index 7bfa1f38..109f4715 100644 --- a/src/backends/python2/CMakeLists.txt +++ b/src/backends/python2/CMakeLists.txt @@ -1,36 +1,36 @@ set( Python2Backend_SRCS python2backend.cpp python2session.cpp ) set(Python2Server_SRCS ../python/pythonservermain.cpp ../python/pythonserver.cpp ) kconfig_add_kcfg_files(Python2Backend_SRCS settings.kcfgc) if(MSVC) # ssize_t is typedef'd in both kdewin and python headers, this prevents using the kdewin one add_definitions(-D_SSIZE_T_DEFINED) endif(MSVC) include_directories(${PYTHON_LIBRARIES_DIR}) include_directories(${PYTHON_INCLUDE_DIR}) add_backend(python2backend ${Python2Backend_SRCS}) target_link_libraries(cantor_python2backend ${PYTHON_LIBRARIES} cantor_pythonbackend) add_executable(cantor_python2server ${Python2Server_SRCS}) set_target_properties(cantor_python2server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false) -target_link_libraries(cantor_python2server ${PYTHON_LIBRARIES} Qt5::Widgets) +target_link_libraries(cantor_python2server ${PYTHON_LIBRARIES}) if(BUILD_TESTING) add_executable(testpython2 testpython2.cpp settings.cpp) target_link_libraries(testpython2 ${QT_QTTEST_LIBRARY} cantorlibs cantortest) add_test(NAME testpython2 COMMAND testpython2) endif() install(FILES cantor_python2.knsrc DESTINATION ${KDE_INSTALL_CONFDIR}) install(FILES python2backend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) install(TARGETS cantor_python2server ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/backends/python3/CMakeLists.txt b/src/backends/python3/CMakeLists.txt index 52b3b6ab..245401a5 100644 --- a/src/backends/python3/CMakeLists.txt +++ b/src/backends/python3/CMakeLists.txt @@ -1,35 +1,35 @@ set(Python3Backend_SRCS python3backend.cpp python3session.cpp ) set(Python3Server_SRCS ../python/pythonservermain.cpp ../python/pythonserver.cpp ) include_directories(${PYTHONLIBS3_INCLUDE_DIRS}) kconfig_add_kcfg_files(Python3Backend_SRCS settings.kcfgc) add_backend(python3backend ${Python3Backend_SRCS}) target_link_libraries(cantor_python3backend cantor_pythonbackend) add_executable(cantor_python3server ${Python3Server_SRCS}) set_target_properties(cantor_python3server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false) -target_link_libraries(cantor_python3server ${PYTHONLIBS3_LIBRARIES} Qt5::Widgets) +target_link_libraries(cantor_python3server ${PYTHONLIBS3_LIBRARIES}) if(BUILD_TESTING) add_executable(testpython3 testpython3.cpp settings.cpp) add_test(NAME testpython3 COMMAND testpython3) target_link_libraries(testpython3 Qt5::Test KF5::ConfigCore KF5::ConfigGui cantorlibs cantortest ) endif(BUILD_TESTING) install(FILES cantor_python3.knsrc DESTINATION ${KDE_INSTALL_CONFDIR}) install(FILES python3backend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) install(TARGETS cantor_python3server ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})