diff options
32 files changed, 563 insertions, 381 deletions
diff --git a/doc/configuration.qdoc b/doc/configuration.qdoc index f10c3ea0..a3cb5ace 100644 --- a/doc/configuration.qdoc +++ b/doc/configuration.qdoc @@ -348,11 +348,6 @@ all your imports paths and file references relative to your main config file. \li object \li Specifies which actions to take, if the application-manager is crashing. See \l{Crash Action Specification} {below} for more information. -\row - \li \b - - \br \e debugWrappers - \li object - \li Defines all available debug wrappers. See \l{Debugging} for more details. \omit telnetPort and telnetAddress: these are configuring the Pelagicore proprietary telnet component diff --git a/doc/controller.qdoc b/doc/controller.qdoc index 0911b677..68cc68f2 100644 --- a/doc/controller.qdoc +++ b/doc/controller.qdoc @@ -55,10 +55,10 @@ The following commands are available: \c document-url parameter is optional. \row \li \span {style="white-space: nowrap"} {\c debug-application} - \li \c{<debug-wrapper-name> <application-id> [document-url]} + \li \c{<debug-wrapper-specification> <application-id> [document-url]} \li Starts the application identified by \c application-id within the application-manager using - a debug-wrapper. The given \c debug-wrapper-name must exist in the application-manager's - configuration. The \c document-url parameter is optional. + a debug-wrapper: see \l{DebugWrappers}{the debugging documentation} for more information on + this specification string. The \c document-url parameter is optional. \row \li \span {style="white-space: nowrap"} {\c list-applications} \li (none) diff --git a/doc/debugging.qdoc b/doc/debugging.qdoc index 60f2c572..3f667358 100644 --- a/doc/debugging.qdoc +++ b/doc/debugging.qdoc @@ -86,7 +86,7 @@ application-manager is running in: or the System-UI and debugging individual applications: The application-manager and System-UI can be debugged in the same way as described for a single-process setup above. Debugging applications is a bit more tricky though, since they have to be started by the application-manager: - this is accomplished by defining so called \l DebugWrappers which describe how to start an + this is accomplished by running the app through a debug wrapper which describe how to start an application using your favorite debugging tool. \endlist @@ -97,82 +97,25 @@ Please note that although the concept is called "debug" wrappers, these wrappers actual debugging tasks. They are also useful for various other tasks that involve running the program under test through a wrapper, e.g. profiling tools. -\section2 Defining Debug Wrappers \target DebugWrappers - -Debug wrappers for applications in multi-process mode are defined in a \c config.yaml file provided -on the command line, as a list under the \c debugWrappers key. A typical example would be the -definition of a \c gdbserver wrapper: - -\badcode -debugWrappers: -- name: gdbserver - parameters: # <name>: <default value> - port: 5555 - # %program% and %arguments% are internal variables - command: [ '/usr/bin/gdbserver', ':%port%', '%program%', '%arguments%' ] - supportedRuntimes: [ native, qml ] - supportedContainers: [ process ] -\endcode - -Each debug wrapper is a YAML map, which supports the following fields: - -\table -\header - \li Name - \li Type - \li Description -\row - \li \c name - \li string - \li A short unique identifier string, given to the application-manager to tell it which debug - wrapper to use when starting an application in debug mode. -\row - \li \c parameters - \li object - \li A standard YAML map defining possible runtime parameters and their default values. The key - is the unique identifier that can be referenced in the \c command definition. The value is - the default value if the parameter is not explicitly specified at runtime (can also be just - \c ~ - the YAML null value). -\row - \li \c command - \li list<string> - \li The heart of the debug wrapper, where you define the exact command line to execute: this is - a string list and each item of this list will be converted to a single command-line parameter. - No automatic splitting at white-space will be done! Any text between \b % characters will be - replaced if this text can be resolved as a parameter name. There are two internal parameters: - \b %program% and \b %arguments%: \b %program% will resolve to the file-path of the binary that - is being debugged and \b %arguments% will be replaced with the command-line parameters that - have been specified internally by the respective runtime and container implementations. -\row - \li \c supportedRuntimes - \li list<string> - \li A list of runtime ids which are compatible with this debug wrapper (e.g. \c native, \c qml). -\row - \li \c supportedContainers - \li list<string> - \li A list of container ids which are compatible with this debug wrapper (e.g. \c process). -\endtable - \section2 Using Debug Wrappers -After defining the debug wrappers, you can start applications using those wrappers. There are three -ways to do this - all of them rely on a common way to specify which debug wrapper to use and optional -parameter values: +There are three ways to do start applications using debug wrappers - all of them rely on a common +way to specify which debug wrapper to use: \list \li Within your System-UI, do not use \c startApplication to start an app, but debugApplication: \badcode - ApplicationManager.debugApplication("io.qt.app", "gdbserver: { port: 5556 }") + ApplicationManager.debugApplication("io.qt.app", "/usr/bin/strace -f") \endcode \li Via D-Bus, you can call the debugApplication method: \badcode - qdbus io.qt.ApplicationManager /ApplicationManager debugApplication io.qt.app "gdbserver: { port: 5557 }" + qdbus io.qt.ApplicationManager /ApplicationManager debugApplication "gdbserver :5555" io.qt.app \endcode \li Using the \c appman-controller which uses D-Bus internally, but is able to find the correct bus automatically and supports standard-IO redirection: \badcode - appman-controller debug-application -ioe "gdbserver" io.qt.app + appman-controller debug-application -ioe "valgrind --tool=helgrind" io.qt.app \endcode The optional \c -i, \c -o and \c -e parameters will redirect the respective IO streams (\c stdin, \c stdout and \c stderr) to the calling terminal. @@ -181,13 +124,40 @@ parameter values: \note In order to use the D-Bus options, the application-manager has to be connected to either a session- or system-bus - make sure to not run it with \c{--dbus none}. -The debug wrapper argument is always interpreted as a standard YAML document consisting of either -\list -\li a single string denoting the debug wrapper name (no parameters supplied) or -\li a map with a single key denoting the debug wrapper name and its value being a map describing - all of the parameters: - \badcode - name: { param1: "value1", param2: "value2" } - \endcode -\endlist +The debug wrapper specification has to be a single argument string, that is interpreted as a command +line. If this string contains the sub-string \c{%program%}, it will be replaced with the full path +to the executable of the application (or the \c appman-launcher-qml binary for QML applications). +The same thing happens for \c{%arguments%}, which will be replaced with potential command line +arguments for the application. If you don't specify \c{%program%} or \c{%arguments%}, they will +simply be appended to the resulting command line. + +This means that all of these debug wrappers are essentially the same: +\badcode +appman-controller debug-application "gdbserver :5555 %program% %arguments%" io.qt.music +appman-controller debug-application "gdbserver :5555 %program%" io.qt.music +appman-controller debug-application "gdbserver :5555" io.qt.music +\endcode + +The explicit \c{%program%} argument is important, if the "wrapper" works differently. An example +for this would be to start the application with the JavaScript debugger on port 1234 in blocking +mode: +\badcode +appman-controller debug-application "%program% -qmljsdebugger=port:1234,block %arguments%" io.qt.browser +\endcode + +You also have the possibility to specify environment variables for the debug wrapper - just like on +the command line. This command will run your application through \c strace while also setting the +\c WAYLAND_DEBUG environment variable to \c 1. +\badcode +appman-controller debug-application "WAYLAND_DEBUG=1 strace -f" io.qt.browser +\endcode +For added convenience, you can even just only set environment variables without any actual debug +wrapper, e.g. to debug imports and plugin loading in a QML application: +\badcode +appman-controller debug-application "QT_DEBUG_PLUGINS=1 QML_IMPORT_TRACE=1" io.qt.browser +\endcode + + +It is advisable to create aliases or wrapper scripts when using complex debug wrappers on the +command line often. */ diff --git a/examples/softwarecontainer-plugin/softwarecontainer.cpp b/examples/softwarecontainer-plugin/softwarecontainer.cpp index 97ed8426..67f9520a 100644 --- a/examples/softwarecontainer-plugin/softwarecontainer.cpp +++ b/examples/softwarecontainer-plugin/softwarecontainer.cpp @@ -89,6 +89,26 @@ QT_END_NAMESPACE QT_USE_NAMESPACE_AM +// unfortunately, this is a copy of the code from debugwrapper.cpp +static QStringList substituteCommand(const QStringList &debugWrapperCommand, const QString &program, + const QStringList &arguments) +{ + QString stringifiedArguments = arguments.join(qL1C(' ')); + QStringList result; + + for (const QString &s : debugWrapperCommand) { + if (s == qSL("%arguments%")) { + result << arguments; + } else { + QString str(s); + str.replace(qL1S("%program%"), program); + str.replace(qL1S("%arguments%"), stringifiedArguments); + result << str; + } + } + return result; +} + SoftwareContainerManager::SoftwareContainerManager() { static bool once = false; @@ -114,6 +134,7 @@ void SoftwareContainerManager::setConfiguration(const QVariantMap &configuration } ContainerInterface *SoftwareContainerManager::create(bool isQuickLaunch, const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, const QStringList &debugWrapperCommand) { if (!m_interface) { @@ -182,7 +203,8 @@ ContainerInterface *SoftwareContainerManager::create(bool isQuickLaunch, const Q outputFd = STDOUT_FILENO; SoftwareContainer *container = new SoftwareContainer(this, isQuickLaunch, containerId, - outputFd, debugWrapperCommand); + outputFd, debugWrapperEnvironment, + debugWrapperCommand); m_containers.insert(containerId, container); connect(container, &QObject::destroyed, this, [this, containerId]() { m_containers.remove(containerId); }); return container; @@ -215,11 +237,13 @@ void SoftwareContainerManager::processStateChanged(int containerId, uint process SoftwareContainer::SoftwareContainer(SoftwareContainerManager *manager, bool isQuickLaunch, int containerId, - int outputFd, const QStringList &debugWrapperCommand) + int outputFd, const QMap<QString, QString> &debugWrapperEnvironment, + const QStringList &debugWrapperCommand) : m_manager(manager) , m_isQuickLaunch(isQuickLaunch) , m_id(containerId) , m_outputFd(outputFd) + , m_debugWrapperEnvironment(debugWrapperEnvironment) , m_debugWrapperCommand(debugWrapperCommand) { } @@ -369,7 +393,7 @@ bool SoftwareContainer::sendBindMounts() } -bool SoftwareContainer::start(const QStringList &arguments, const QProcessEnvironment &environment) +bool SoftwareContainer::start(const QStringList &arguments, const QMap<QString, QString> &runtimeEnvironment) { auto iface = manager()->interface(); if (!iface) @@ -383,7 +407,7 @@ bool SoftwareContainer::start(const QStringList &arguments, const QProcessEnviro return false; // parse out the actual socket file name from the DBus specification - QString dbusP2PSocket = environment.value(QStringLiteral("AM_DBUS_PEER_ADDRESS")); + QString dbusP2PSocket = runtimeEnvironment.value(QStringLiteral("AM_DBUS_PEER_ADDRESS")); dbusP2PSocket = dbusP2PSocket.mid(dbusP2PSocket.indexOf(QLatin1Char('=')) + 1); dbusP2PSocket = dbusP2PSocket.left(dbusP2PSocket.indexOf(QLatin1Char(','))); QFileInfo dbusP2PInfo(dbusP2PSocket); @@ -436,22 +460,7 @@ bool SoftwareContainer::start(const QStringList &arguments, const QProcessEnviro // Calculate the exact command to run QStringList command; if (!m_debugWrapperCommand.isEmpty()) { - // unfortunately, this is a copy of the code from abstractcontainer.cpp - command = arguments; - bool foundArgumentMarker = false; - for (int i = m_debugWrapperCommand.count() - 1; i >= 0; --i) { - QString str = m_debugWrapperCommand.at(i); - if (str == qL1S("%arguments%")) { - foundArgumentMarker = true; - continue; - } - str.replace(qL1S("%program%"), m_program); - - if (i == 0 || foundArgumentMarker) - command.prepend(str); - else - command.append(str); - } + command = substituteCommand(m_debugWrapperCommand, m_program, arguments); } else { command = arguments; command.prepend(m_program); @@ -470,26 +479,41 @@ bool SoftwareContainer::start(const QStringList &arguments, const QProcessEnviro // we start with a copy of the AM's environment, but some variables would overwrite important // redirections set by SC gateways. - static const char *forbiddenVars[] = { - "XDG_RUNTIME_DIR", - "DBUS_SESSION_BUS_ADDRESS", - "DBUS_SYSTEM_BUS_ADDRESS", - "PULSE_SERVER", - nullptr + static const QStringList forbiddenVars = { + qSL("XDG_RUNTIME_DIR"), + qSL("DBUS_SESSION_BUS_ADDRESS"), + qSL("DBUS_SYSTEM_BUS_ADDRESS"), + qSL("PULSE_SERVER") }; - QMap<QString, QString> envVars; - for (auto &&envVar : environment.keys()) { - bool forbidden = false; - for (const char **p = forbiddenVars; *p; ++p) { - if (envVar == QLatin1String(*p)) { - forbidden = true; - break; - } + // since we have to translate between a QProcessEnvironment and a QMap<>, we cache the result + static QMap<QString, QString> baseEnvVars; + if (baseEnvVars.isEmpty()) { + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + const auto keys = env.keys(); + for (const auto key : keys) { + if (!key.isEmpty() && !forbiddenVars.contains(key)) + baseEnvVars.insert(key, env.value(key)); } - if (!forbidden) - envVars.insert(envVar, environment.value(envVar)); } + + QMap<QString, QString> envVars = baseEnvVars; + + // set the env. variables coming from the runtime + for (auto it = runtimeEnvironment.cbegin(); it != runtimeEnvironment.cend(); ++it) { + if (it.value().isEmpty()) + envVars.remove(it.key()); + else + envVars.insert(it.key(), it.value()); + } + // set the env. variables coming from a debug wrapper + for (auto it = m_debugWrapperEnvironment.cbegin(); it != m_debugWrapperEnvironment.cend(); ++it) { + if (it.value().isEmpty()) + envVars.remove(it.key()); + else + envVars.insert(it.key(), it.value()); + } + QVariant venvVars = QVariant::fromValue(envVars); qDebug () << "SoftwareContainer is trying to launch application" << m_id diff --git a/examples/softwarecontainer-plugin/softwarecontainer.h b/examples/softwarecontainer-plugin/softwarecontainer.h index ec899926..56ba1bf1 100644 --- a/examples/softwarecontainer-plugin/softwarecontainer.h +++ b/examples/softwarecontainer-plugin/softwarecontainer.h @@ -56,7 +56,8 @@ class SoftwareContainer : public ContainerInterface public: SoftwareContainer(SoftwareContainerManager *manager, bool isQuickLaunch, int containerId, - int outputFd, const QStringList &debugWrapperCommand); + int outputFd, const QMap<QString, QString> &debugWrapperEnvironment, + const QStringList &debugWrapperCommand); ~SoftwareContainer(); SoftwareContainerManager *manager() const; @@ -74,7 +75,7 @@ public: QString mapContainerPathToHost(const QString &containerPath) const override; QString mapHostPathToContainer(const QString &hostPath) const override; - bool start(const QStringList &arguments, const QProcessEnvironment &environment) override; + bool start(const QStringList &arguments, const QMap<QString, QString> &runtimeEnvironment) override; bool isStarted() const override; qint64 processId() const override; @@ -104,6 +105,7 @@ private: QByteArray m_fifoPath; int m_fifoFd = -1; int m_outputFd; + QMap<QString, QString> m_debugWrapperEnvironment; QStringList m_debugWrapperCommand; QFileInfo m_dbusP2PInfo; }; @@ -123,6 +125,7 @@ public: ContainerInterface *create(bool isQuickLaunch, const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, const QStringList &debugWrapperCommand) override; public: QDBusInterface *interface() const; diff --git a/src/main-lib/defaultconfiguration.cpp b/src/main-lib/defaultconfiguration.cpp index cab61f0a..8f663be0 100644 --- a/src/main-lib/defaultconfiguration.cpp +++ b/src/main-lib/defaultconfiguration.cpp @@ -382,11 +382,6 @@ quint16 DefaultConfiguration::telnetPort() const return value<QVariant>(nullptr, { "debug", "telnetPort" }).value<quint16>(); } -QVariantList DefaultConfiguration::debugWrappers() const -{ - return value<QVariant>(nullptr, { "debugWrappers" }).toList(); -} - QVariantMap DefaultConfiguration::managerCrashAction() const { return value<QVariant>(nullptr, { "crashAction"} ).toMap(); diff --git a/src/main-lib/defaultconfiguration.h b/src/main-lib/defaultconfiguration.h index 66ec46ef..0e2c7f2f 100644 --- a/src/main-lib/defaultconfiguration.h +++ b/src/main-lib/defaultconfiguration.h @@ -102,8 +102,6 @@ public: QString telnetAddress() const; quint16 telnetPort() const; - QVariantList debugWrappers() const; - QVariantMap managerCrashAction() const; QStringList caCertificates() const; diff --git a/src/main-lib/main.cpp b/src/main-lib/main.cpp index 532d12c1..68e05dee 100644 --- a/src/main-lib/main.cpp +++ b/src/main-lib/main.cpp @@ -224,7 +224,6 @@ void Main::setup(const DefaultConfiguration *cfg) Q_DECL_NOEXCEPT_EXPR(false) loadQml(cfg->loadDummyData()); // --no-fullscreen on the command line trumps the fullscreen setting in the config file showWindow(cfg->fullscreen() && !cfg->noFullscreen()); - setupDebugWrappers(cfg->debugWrappers()); setupShellServer(cfg->telnetAddress(), cfg->telnetPort()); setupSSDPService(); } @@ -663,15 +662,6 @@ void Main::showWindow(bool showFullscreen) #endif } -void Main::setupDebugWrappers(const QVariantList &debugWrappers) -{ - // delay debug-wrapper setup - //TODO: find a better solution than hardcoding an 1.5 sec delay - QTimer::singleShot(1500, this, [this, debugWrappers]() { - m_applicationManager->setDebugWrapperConfiguration(debugWrappers); - }); -} - void Main::setupShellServer(const QString &telnetAddress, quint16 telnetPort) Q_DECL_NOEXCEPT_EXPR(false) { //TODO: could be delayed as well diff --git a/src/main-lib/main.h b/src/main-lib/main.h index b6d5d630..c8ffbb5c 100644 --- a/src/main-lib/main.h +++ b/src/main-lib/main.h @@ -119,7 +119,6 @@ protected: void loadQml(bool loadDummyData) Q_DECL_NOEXCEPT_EXPR(false); void showWindow(bool showFullscreen); - void setupDebugWrappers(const QVariantList &debugWrappers); void setupShellServer(const QString &telnetAddress, quint16 telnetPort) Q_DECL_NOEXCEPT_EXPR(false); void setupSSDPService() Q_DECL_NOEXCEPT_EXPR(false); diff --git a/src/manager-lib/abstractcontainer.cpp b/src/manager-lib/abstractcontainer.cpp index f0a74ca2..97aeaa92 100644 --- a/src/manager-lib/abstractcontainer.cpp +++ b/src/manager-lib/abstractcontainer.cpp @@ -111,29 +111,6 @@ QString AbstractContainer::mapHostPathToContainer(const QString &hostPath) const return hostPath; } -QStringList AbstractContainer::substituteDebugWrapperCommand(const QStringList &debugWrapperCommand, - const QString &program, - const QStringList &arguments) -{ - QStringList cmd = arguments; - - bool foundArgumentMarker = false; - for (int i = debugWrapperCommand.count() - 1; i >= 0; --i) { - QString str = debugWrapperCommand.at(i); - if (str == qL1S("%arguments%")) { - foundArgumentMarker = true; - continue; - } - str.replace(qL1S("%program%"), program); - - if (i == 0 || foundArgumentMarker) - cmd.prepend(str); - else - cmd.append(str); - } - return cmd; -} - AbstractContainerProcess *AbstractContainer::process() const { return m_process; diff --git a/src/manager-lib/abstractcontainer.h b/src/manager-lib/abstractcontainer.h index fdfebc85..57d51823 100644 --- a/src/manager-lib/abstractcontainer.h +++ b/src/manager-lib/abstractcontainer.h @@ -66,6 +66,7 @@ public: virtual bool supportsQuickLaunch() const; virtual AbstractContainer *create(const Application *app, const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, const QStringList &debugWrapperCommand) = 0; QVariantMap configuration() const; @@ -116,11 +117,8 @@ public: virtual QString mapContainerPathToHost(const QString &containerPath) const; virtual QString mapHostPathToContainer(const QString &hostPath) const; - virtual AbstractContainerProcess *start(const QStringList &arguments, const QProcessEnvironment &env) = 0; - - static QStringList substituteDebugWrapperCommand(const QStringList &debugWrapperCommand, - const QString &program, - const QStringList &arguments); + virtual AbstractContainerProcess *start(const QStringList &arguments, + const QMap<QString, QString> &runtimeEnvironment) = 0; AbstractContainerProcess *process() const; diff --git a/src/manager-lib/applicationmanager.cpp b/src/manager-lib/applicationmanager.cpp index f8c7f754..93f4473d 100644 --- a/src/manager-lib/applicationmanager.cpp +++ b/src/manager-lib/applicationmanager.cpp @@ -70,6 +70,7 @@ #include "qml-utilities.h" #include "utilities.h" #include "qtyaml.h" +#include "debugwrapper.h" /*! \qmltype ApplicationManager @@ -348,7 +349,6 @@ static ApplicationManager::RunState runtimeToManagerState(AbstractRuntime::State } } - ApplicationManagerPrivate::ApplicationManagerPrivate() { currentLocale = QLocale::system().name(); //TODO: language changes @@ -506,97 +506,6 @@ void ApplicationManager::setContainerSelectionFunction(const QJSValue &callback) } } -void ApplicationManager::setDebugWrapperConfiguration(const QVariantList &debugWrappers) -{ - // Example: - // debugWrappers: - // - name: gdbserver - // # %program% and %arguments% are internal variables - // command: [ /usr/bin/gdbserver, ':%port%', '%program%', '%arguments%' ] - // parameters: # <name>: <default value> - // port: 5555 - // supportedRuntimes: [ native, qml ] - // supportedContainers: [ process ] - // - name: valgrind - // command: [ /usr/bin/valgrind, '--', '%program%', '%arguments%' ] - // parameters: # <name>: <default value> - - for (const QVariant &v : debugWrappers) { - const QVariantMap &map = v.toMap(); - - ApplicationManagerPrivate::DebugWrapper dw; - dw.name = map.value(qSL("name")).toString(); - dw.command = map.value(qSL("command")).toStringList(); - dw.parameters = map.value(qSL("parameters")).toMap(); - dw.supportedContainers = map.value(qSL("supportedContainers")).toStringList(); - dw.supportedRuntimes = map.value(qSL("supportedRuntimes")).toStringList(); - - if (dw.name.isEmpty() || dw.command.isEmpty()) { - qCWarning(LogSystem) << "Ignoring invalid debug wrapper: " << dw.name << "/" << dw.command; - continue; - } - d->debugWrappers.append(dw); - } -} - -ApplicationManagerPrivate::DebugWrapper ApplicationManagerPrivate::parseDebugWrapperSpecification(const QString &spec) -{ - // Example: - // "gdbserver" - // "gdbserver: {port: 5555}" - - static ApplicationManagerPrivate::DebugWrapper fail; - - if (spec.isEmpty()) - return fail; - - auto docs = QtYaml::variantDocumentsFromYaml(spec.toUtf8()); - if (docs.size() != 1) - return fail; - const QVariant v = docs.at(0); - - QString name; - QVariantMap userParams; - - switch (v.type()) { - case QVariant::String: - name = v.toString(); - break; - case QVariant::Map: { - const QVariantMap map = v.toMap(); - name = map.firstKey(); - userParams = map.first().toMap(); - break; - } - default: - return fail; - } - - for (const auto &it : qAsConst(debugWrappers)) { - if (it.name == name) { - ApplicationManagerPrivate::DebugWrapper dw = it; - for (auto it = userParams.cbegin(); it != userParams.cend(); ++it) { - auto pit = dw.parameters.find(it.key()); - if (pit == dw.parameters.end()) - return fail; - *pit = it.value(); - } - - for (auto pit = dw.parameters.cbegin(); pit != dw.parameters.cend(); ++pit) { - QString key = qL1C('%') + pit.key() + qL1C('%'); - QString value = pit.value().toString(); - - // replace variable name with value in command line - std::for_each(dw.command.begin(), dw.command.end(), [key, value](QString &str) { - str.replace(key, value); - }); - } - return dw; - } - } - return fail; -} - QVector<const Application *> ApplicationManager::applications() const { return d->apps; @@ -727,10 +636,11 @@ bool ApplicationManager::startApplication(const Application *app, const QString } // validate the debug-wrapper - ApplicationManagerPrivate::DebugWrapper debugWrapper; + QStringList debugWrapperCommand; + QMap<QString, QString> debugEnvironmentVariables; if (!debugWrapperSpecification.isEmpty()) { - debugWrapper = d->parseDebugWrapperSpecification(debugWrapperSpecification); - if (!debugWrapper.isValid()) { + if (!DebugWrapper::parseSpecification(debugWrapperSpecification, debugWrapperCommand, + debugEnvironmentVariables)) { throw Exception("Tried to start application %1 using an invalid debug-wrapper specification: %2") .arg(app->id(), debugWrapperSpecification); } @@ -740,9 +650,9 @@ bool ApplicationManager::startApplication(const Application *app, const QString switch (runtime->state()) { case AbstractRuntime::Startup: case AbstractRuntime::Active: - if (debugWrapper.isValid()) { + if (!debugWrapperCommand.isEmpty()) { throw Exception("Application %1 is already running - cannot start with debug-wrapper: %2") - .arg(app->id(), debugWrapper.name); + .arg(app->id(), debugWrapperSpecification); } if (!documentUrl.isNull()) @@ -799,24 +709,13 @@ bool ApplicationManager::startApplication(const Application *app, const QString } bool attachRuntime = false; - if (debugWrapper.isValid()) { - if (!debugWrapper.supportedRuntimes.contains(app->runtimeName())) { - throw Exception("Application %1 is using the %2 runtime, which is not compatible with the requested debug-wrapper: %3") - .arg(app->id(), app->runtimeName(), debugWrapper.name); - } - if (!debugWrapper.supportedContainers.contains(containerId)) { - throw Exception("Application %1 is using the %2 container, which is not compatible with the requested debug-wrapper: %3") - .arg(app->id(), containerId, debugWrapper.name); - } - } - if (!runtime) { if (!inProcess) { // we cannot use the quicklaunch pool, if // (a) a debug-wrapper is being used, // (b) stdio is redirected or // (c) the app requests special environment variables - bool cannotUseQuickLaunch = debugWrapper.isValid() + bool cannotUseQuickLaunch = !debugWrapperCommand.isEmpty() || hasStdioRedirections || !app->environmentVariables().isEmpty(); @@ -839,7 +738,7 @@ bool ApplicationManager::startApplication(const Application *app, const QString if (!container) { container = ContainerFactory::instance()->create(containerId, app, stdioRedirections, - debugWrapper.command); + debugEnvironmentVariables, debugWrapperCommand); } else { container->setApplication(app); } diff --git a/src/manager-lib/applicationmanager.h b/src/manager-lib/applicationmanager.h index 73e188f6..1c006e8f 100644 --- a/src/manager-lib/applicationmanager.h +++ b/src/manager-lib/applicationmanager.h @@ -95,8 +95,6 @@ public: QVariantMap systemProperties() const; void setSystemProperties(const QVariantMap &map); - void setDebugWrapperConfiguration(const QVariantList &debugWrappers); - QVector<const Application *> applications() const; const Application *fromId(const QString &id) const; diff --git a/src/manager-lib/applicationmanager_p.h b/src/manager-lib/applicationmanager_p.h index 5c6b9c18..30e43ae9 100644 --- a/src/manager-lib/applicationmanager_p.h +++ b/src/manager-lib/applicationmanager_p.h @@ -66,21 +66,6 @@ public: QVector<IpcProxyObject *> interfaceExtensions; - struct DebugWrapper - { - bool isValid() const { return !name.isEmpty(); } - - QString name; - QStringList command; - QVariantMap parameters; - QStringList supportedRuntimes; - QStringList supportedContainers; - }; - - QVector<DebugWrapper> debugWrappers; - - DebugWrapper parseDebugWrapperSpecification(const QString &spec); - QList<QPair<QString, QString>> containerSelectionConfig; QJSValue containerSelectionFunction; diff --git a/src/manager-lib/containerfactory.cpp b/src/manager-lib/containerfactory.cpp index 08e55a35..e34dea46 100644 --- a/src/manager-lib/containerfactory.cpp +++ b/src/manager-lib/containerfactory.cpp @@ -81,12 +81,13 @@ AbstractContainerManager *ContainerFactory::manager(const QString &id) AbstractContainer *ContainerFactory::create(const QString &id, const Application *app, const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, const QStringList &debugWrapperCommand) { AbstractContainerManager *acm = manager(id); if (!acm) return nullptr; - return acm->create(app, stdioRedirections, debugWrapperCommand); + return acm->create(app, stdioRedirections, debugWrapperEnvironment, debugWrapperCommand); } void ContainerFactory::setConfiguration(const QVariantMap &configuration) diff --git a/src/manager-lib/containerfactory.h b/src/manager-lib/containerfactory.h index ed6fd016..db6baade 100644 --- a/src/manager-lib/containerfactory.h +++ b/src/manager-lib/containerfactory.h @@ -51,7 +51,6 @@ QT_BEGIN_NAMESPACE_AM class Application; class AbstractContainer; class AbstractContainerManager; -class ContainerDebugWrapper; class ContainerFactory : public QObject { @@ -66,6 +65,7 @@ public: AbstractContainerManager *manager(const QString &id); AbstractContainer *create(const QString &id, const Application *app, const QVector<int> &stdioRedirections = QVector<int>(), + const QMap<QString, QString> &debugWrapperEnvironment = QMap<QString, QString>(), const QStringList &debugWrapperCommand = QStringList()); void setConfiguration(const QVariantMap &configuration); diff --git a/src/manager-lib/debugwrapper.cpp b/src/manager-lib/debugwrapper.cpp new file mode 100644 index 00000000..50858836 --- /dev/null +++ b/src/manager-lib/debugwrapper.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#include "debugwrapper.h" + +QT_BEGIN_NAMESPACE_AM + +namespace DebugWrapper { + +bool parseSpecification(const QString &debugWrapperSpecification, QStringList &resultingCommand, + QMap<QString, QString> &resultingEnvironment) +{ + QStringList cmd; + QMap<QString, QString> env; + + // split at ' ', but also accept escape sequences for '\ ', '\n' and '\\' + // all leading FOO=BAR args will be converted to env var assignments + bool escaped = false; + bool canBeEnvVar = true; + bool isEnvVar = false; + static const QString program = qSL("%program%"); + static const QString arguments = qSL("%arguments%"); + int foundProgram = 0; + int foundArguments = 0; + QString str; + QString value; + QString *current = &str; + int size = debugWrapperSpecification.size(); + for (int i = 0; i <= size; ++i) { + const QChar c = (i < size) ? debugWrapperSpecification.at(i) : QChar(0); + if (!escaped || c.isNull()) { + switch (c.unicode()) { + case '\\': + escaped = true; + break; + case '=': + // switch to value parsing if this can be an env var and if this is the first = + // all other = are copied verbatim + isEnvVar = canBeEnvVar; + if (isEnvVar && current == &str) + current = &value; + else + current->append(qL1C('=')); + break; + case ' ': + case '\0': + // found the end of an argument: add to env vars or cmd and reset state + if (!str.isEmpty()) { + if (isEnvVar) { + env.insert(str, value); + } else { + cmd.append(str); + + foundArguments = foundArguments || str.contains(arguments); + foundProgram = foundProgram || str.contains(program); + } + } + canBeEnvVar = isEnvVar; // stop parsing as envvar as soon as we get a normal arg + isEnvVar = false; + current = &str; + str.clear(); + value.clear(); + break; + default: + current->append(c); + break; + } + } else { + switch (c.unicode()) { + case '\\': current->append(qL1C('\\')); break; + case ' ': current->append(qL1C(' ')); break; + case 'n': current->append(qL1C('\n')); break; + default: return false; + } + escaped = false; + } + } + + if (cmd.isEmpty() && env.isEmpty()) + return false; + + // convenience: if the spec doesn't include %program% or %arguments%, then simply append them + if (!foundProgram) + cmd << program; + if (!foundArguments) + cmd << arguments; + + resultingCommand = cmd; + resultingEnvironment = env; + return true; +} + +QStringList substituteCommand(const QStringList &debugWrapperCommand, const QString &program, + const QStringList &arguments) +{ + QString stringifiedArguments = arguments.join(qL1C(' ')); + QStringList result; + + for (const QString &s : debugWrapperCommand) { + if (s == qSL("%arguments%")) { + result << arguments; + } else { + QString str(s); + str.replace(qL1S("%program%"), program); + str.replace(qL1S("%arguments%"), stringifiedArguments); + result << str; + } + } + return result; +} + +} // namespace DebugWrapper + +QT_END_NAMESPACE_AM diff --git a/src/manager-lib/debugwrapper.h b/src/manager-lib/debugwrapper.h new file mode 100644 index 00000000..182d8c4d --- /dev/null +++ b/src/manager-lib/debugwrapper.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#pragma once + +#include <QtAppManCommon/global.h> + +#include <QStringList> +#include <QMap> + +QT_BEGIN_NAMESPACE_AM + +namespace DebugWrapper { + +bool parseSpecification(const QString &debugWrapperSpecification, QStringList &resultingCommand, + QMap<QString, QString> &resultingEnvironment); + +QStringList substituteCommand(const QStringList &debugWrapperCommand, const QString &program, + const QStringList &arguments); + +} + +QT_END_NAMESPACE_AM diff --git a/src/manager-lib/manager-lib.pro b/src/manager-lib/manager-lib.pro index 554a9057..f84f878d 100644 --- a/src/manager-lib/manager-lib.pro +++ b/src/manager-lib/manager-lib.pro @@ -36,6 +36,7 @@ HEADERS += \ applicationipcinterface_p.h \ applicationmanager_p.h \ systemreader.h \ + debugwrapper.h linux:HEADERS += \ sysfsreader.h \ @@ -66,6 +67,7 @@ SOURCES += \ applicationipcmanager.cpp \ applicationipcinterface.cpp \ systemreader.cpp \ + debugwrapper.cpp linux:SOURCES += \ sysfsreader.cpp \ diff --git a/src/manager-lib/nativeruntime.cpp b/src/manager-lib/nativeruntime.cpp index 181d173f..3b4f0046 100644 --- a/src/manager-lib/nativeruntime.cpp +++ b/src/manager-lib/nativeruntime.cpp @@ -240,30 +240,25 @@ bool NativeRuntime::start() break; } - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert(qSL("QT_QPA_PLATFORM"), qSL("wayland")); - env.remove(qSL("QT_IM_MODULE")); // Applications should use wayland text input - //env.insert(qSL("QT_WAYLAND_DISABLE_WINDOWDECORATION"), "1"); - env.insert(qSL("AM_SECURITY_TOKEN"), qL1S(securityToken().toHex())); - env.insert(qSL("AM_DBUS_PEER_ADDRESS"), applicationInterfaceServer()->address()); - env.insert(qSL("AM_DBUS_NOTIFICATION_BUS_ADDRESS"), NotificationManager::instance()->property("_am_dbus_name").toString()); - env.insert(qSL("AM_RUNTIME_CONFIGURATION"), QString::fromUtf8(QtYaml::yamlFromVariantDocuments({ configuration() }))); + QMap<QString, QString> env = { + { qSL("QT_QPA_PLATFORM"), qSL("wayland") }, + { qSL("QT_IM_MODULE"), QString() }, // Applications should use wayland text input + { qSL("AM_SECURITY_TOKEN"), qL1S(securityToken().toHex()) }, + { qSL("AM_DBUS_PEER_ADDRESS"), applicationInterfaceServer()->address() }, + { qSL("AM_DBUS_NOTIFICATION_BUS_ADDRESS"), NotificationManager::instance()->property("_am_dbus_name").toString() }, + { qSL("AM_RUNTIME_CONFIGURATION"), QString::fromUtf8(QtYaml::yamlFromVariantDocuments({ configuration() })) }, + { qSL("AM_BASE_DIR"), QDir::currentPath() } + }; + if (!m_needsLauncher && !m_isQuickLauncher) env.insert(qSL("AM_RUNTIME_SYSTEM_PROPERTIES"), QString::fromUtf8(QtYaml::yamlFromVariantDocuments({ systemProperties() }))); - env.insert(qSL("AM_BASE_DIR"), QDir::currentPath()); if (!Logging::isDltEnabled()) env.insert(qSL("AM_NO_DLT_LOGGING"), qSL("1")); for (QMapIterator<QString, QVariant> it(configuration().value(qSL("environmentVariables")).toMap()); it.hasNext(); ) { it.next(); - QString name = it.key(); - if (!name.isEmpty()) { - QString value = it.value().toString(); - if (value.isNull()) - env.remove(it.key()); - else - env.insert(name, value); - } + if (!it.key().isEmpty()) + env.insert(it.key(), it.value().toString()); } if (m_app && !m_app->environmentVariables().isEmpty()) { @@ -273,14 +268,8 @@ bool NativeRuntime::start() } else { for (QMapIterator<QString, QVariant> it(m_app->environmentVariables()); it.hasNext(); ) { it.next(); - QString name = it.key(); - if (!name.isEmpty()) { - QString value = it.value().toString(); - if (value.isNull()) - env.remove(it.key()); - else - env.insert(name, value); - } + if (!it.key().isEmpty()) + env.insert(it.key(), it.value().toString()); } } } diff --git a/src/manager-lib/plugincontainer.cpp b/src/manager-lib/plugincontainer.cpp index 7e5fa52f..f08ce39b 100644 --- a/src/manager-lib/plugincontainer.cpp +++ b/src/manager-lib/plugincontainer.cpp @@ -61,9 +61,11 @@ bool PluginContainerManager::supportsQuickLaunch() const } AbstractContainer *PluginContainerManager::create(const Application *app, const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, const QStringList &debugWrapperCommand) { - auto containerInterface = m_interface->create(app == nullptr, stdioRedirections, debugWrapperCommand); + auto containerInterface = m_interface->create(app == nullptr, stdioRedirections, + debugWrapperEnvironment, debugWrapperCommand); if (!containerInterface) return nullptr; return new PluginContainer(this, app, containerInterface); @@ -117,7 +119,7 @@ QString PluginContainer::mapHostPathToContainer(const QString &hostPath) const return m_interface->mapHostPathToContainer(hostPath); } -AbstractContainerProcess *PluginContainer::start(const QStringList &arguments, const QProcessEnvironment &env) +AbstractContainerProcess *PluginContainer::start(const QStringList &arguments, const QMap<QString, QString> &env) { if (m_startCalled) return nullptr; diff --git a/src/manager-lib/plugincontainer.h b/src/manager-lib/plugincontainer.h index e4893850..7bf708bb 100644 --- a/src/manager-lib/plugincontainer.h +++ b/src/manager-lib/plugincontainer.h @@ -56,7 +56,8 @@ public: bool supportsQuickLaunch() const override; AbstractContainer *create(const Application *app, const QVector<int> &stdioRedirections, - const QStringList &debugWrapperCommand) override; + const QMap<QString, QString> &debugWrapperEnvironment, + const QStringList &debugWrapperCommand) override; void setConfiguration(const QVariantMap &configuration) override; @@ -102,7 +103,7 @@ public: QString mapContainerPathToHost(const QString &containerPath) const override; QString mapHostPathToContainer(const QString &hostPath) const override; - AbstractContainerProcess *start(const QStringList &arguments, const QProcessEnvironment &env) override; + AbstractContainerProcess *start(const QStringList &arguments, const QMap<QString, QString> &env) override; protected: explicit PluginContainer(AbstractContainerManager *manager, const Application *app, ContainerInterface *containerInterface); diff --git a/src/manager-lib/processcontainer.cpp b/src/manager-lib/processcontainer.cpp index 89ae038b..ecbe708a 100644 --- a/src/manager-lib/processcontainer.cpp +++ b/src/manager-lib/processcontainer.cpp @@ -45,6 +45,7 @@ #include "application.h" #include "processcontainer.h" #include "systemreader.h" +#include "debugwrapper.h" #if defined(Q_OS_UNIX) # include <csignal> @@ -140,9 +141,13 @@ void HostProcess::setStopBeforeExec(bool stopBeforeExec) } -ProcessContainer::ProcessContainer(ProcessContainerManager *manager, const Application *app, const QVector<int> &stdioRedirections, const QStringList &debugWrapperCommand) +ProcessContainer::ProcessContainer(ProcessContainerManager *manager, const Application *app, + const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, + const QStringList &debugWrapperCommand) : AbstractContainer(manager, app) , m_stdioRedirections(stdioRedirections) + , m_debugWrapperEnvironment(debugWrapperEnvironment) , m_debugWrapperCommand(debugWrapperCommand) { } @@ -204,7 +209,8 @@ bool ProcessContainer::isReady() return true; } -AbstractContainerProcess *ProcessContainer::start(const QStringList &arguments, const QProcessEnvironment &environment) +AbstractContainerProcess *ProcessContainer::start(const QStringList &arguments, + const QMap<QString, QString> &runtimeEnvironment) { if (m_process) { qWarning() << "Process" << m_program << "is already started and cannot be started again"; @@ -213,13 +219,24 @@ AbstractContainerProcess *ProcessContainer::start(const QStringList &arguments, if (!QFile::exists(m_program)) return nullptr; - QProcessEnvironment completeEnv = environment; - if (completeEnv.isEmpty()) - completeEnv = QProcessEnvironment::systemEnvironment(); + QProcessEnvironment penv = QProcessEnvironment::systemEnvironment(); + + for (auto it = runtimeEnvironment.cbegin(); it != runtimeEnvironment.cend(); ++it) { + if (it.value().isEmpty()) + penv.remove(it.key()); + else + penv.insert(it.key(), it.value()); + } + for (auto it = m_debugWrapperEnvironment.cbegin(); it != m_debugWrapperEnvironment.cend(); ++it) { + if (it.value().isEmpty()) + penv.remove(it.key()); + else + penv.insert(it.key(), it.value()); + } HostProcess *process = new HostProcess(); process->setWorkingDirectory(m_baseDirectory); - process->setProcessEnvironment(completeEnv); + process->setProcessEnvironment(penv); process->setStopBeforeExec(configuration().value(qSL("stopBeforeExec")).toBool()); process->setStdioRedirections(m_stdioRedirections); @@ -227,7 +244,7 @@ AbstractContainerProcess *ProcessContainer::start(const QStringList &arguments, QStringList args = arguments; if (!m_debugWrapperCommand.isEmpty()) { - auto cmd = substituteDebugWrapperCommand(m_debugWrapperCommand, m_program, arguments); + auto cmd = DebugWrapper::substituteCommand(m_debugWrapperCommand, m_program, arguments); command = cmd.takeFirst(); args = cmd; @@ -259,9 +276,11 @@ bool ProcessContainerManager::supportsQuickLaunch() const return true; } -AbstractContainer *ProcessContainerManager::create(const Application *app, const QVector<int> &stdioRedirections, const QStringList &debugWrapperCommand) +AbstractContainer *ProcessContainerManager::create(const Application *app, const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, + const QStringList &debugWrapperCommand) { - return new ProcessContainer(this, app, stdioRedirections, debugWrapperCommand); + return new ProcessContainer(this, app, stdioRedirections, debugWrapperEnvironment, debugWrapperCommand); } void HostProcess::MyQProcess::setupChildProcess() diff --git a/src/manager-lib/processcontainer.h b/src/manager-lib/processcontainer.h index 4227e242..4c360a22 100644 --- a/src/manager-lib/processcontainer.h +++ b/src/manager-lib/processcontainer.h @@ -60,7 +60,8 @@ public: bool supportsQuickLaunch() const override; AbstractContainer *create(const Application *app, const QVector<int> &stdioRedirections, - const QStringList &debugWrapperCommand) override; + const QMap<QString, QString> &debugWrapperEnvironment, + const QStringList &debugWrapperCommand) override; }; class HostProcess : public AbstractContainerProcess @@ -104,7 +105,9 @@ class ProcessContainer : public AbstractContainer public: explicit ProcessContainer(ProcessContainerManager *manager, const Application *app, - const QVector<int> &stdioRedirections, const QStringList &debugWrapperCommand); + const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, + const QStringList &debugWrapperCommand); ~ProcessContainer(); QString controlGroup() const override; @@ -112,11 +115,13 @@ public: bool isReady() override; - AbstractContainerProcess *start(const QStringList &arguments, const QProcessEnvironment &environment) override; + AbstractContainerProcess *start(const QStringList &arguments, + const QMap<QString, QString> &runtimeEnvironment) override; private: QString m_currentControlGroup; QVector<int> m_stdioRedirections; + QMap<QString, QString> m_debugWrapperEnvironment; QStringList m_debugWrapperCommand; MemoryWatcher *m_memWatcher = nullptr; }; diff --git a/src/plugin-interfaces/containerinterface.h b/src/plugin-interfaces/containerinterface.h index 76450404..c4f7342d 100644 --- a/src/plugin-interfaces/containerinterface.h +++ b/src/plugin-interfaces/containerinterface.h @@ -65,7 +65,7 @@ public: virtual QString mapContainerPathToHost(const QString &containerPath) const = 0; virtual QString mapHostPathToContainer(const QString &hostPath) const = 0; - virtual bool start(const QStringList &arguments, const QProcessEnvironment &env) = 0; + virtual bool start(const QStringList &arguments, const QMap<QString, QString> &runtimeEnvironment) = 0; virtual bool isStarted() const = 0; virtual qint64 processId() const = 0; @@ -93,6 +93,7 @@ public: virtual ContainerInterface *create(bool isQuickLaunch, const QVector<int> &stdioRedirections, + const QMap<QString, QString> &debugWrapperEnvironment, const QStringList &debugWrapperCommand) = 0; }; diff --git a/src/tools/controller/controller.cpp b/src/tools/controller/controller.cpp index 63df9897..897cde31 100644 --- a/src/tools/controller/controller.cpp +++ b/src/tools/controller/controller.cpp @@ -296,7 +296,7 @@ int main(int argc, char *argv[]) clp.addOption({ { qSL("o"), qSL("attach-stdout") }, qSL("Attach the app's stdout to the controller's stdout") }); clp.addOption({ { qSL("e"), qSL("attach-stderr") }, qSL("Attach the app's stderr to the controller's stderr") }); clp.addOption({ { qSL("r"), qSL("restart") }, qSL("Before starting, stop the application if it is already running") }); - clp.addPositionalArgument(qSL("debug-wrapper"), qSL("The name of a configured debug-wrapper.")); + clp.addPositionalArgument(qSL("debug-wrapper"), qSL("The debug-wrapper specification.")); clp.addPositionalArgument(qSL("application-id"), qSL("The id of an installed application.")); clp.addPositionalArgument(qSL("document-url"), qSL("The optional document-url."), qSL("[document-url]")); clp.process(a); diff --git a/template-opt/am/config.yaml b/template-opt/am/config.yaml index 95ed7675..94fc9bcb 100644 --- a/template-opt/am/config.yaml +++ b/template-opt/am/config.yaml @@ -21,35 +21,3 @@ installationLocations: installationPath: "/media/sdcard/apps" documentPath: "/media/sdcard/docs" mountPoint: "/media/sdcard" - -# useful debug wrappers - mainly for Unix -# %program% and %arguments% are internal variables - -debugWrappers: -- name: gdbserver - command: [ '/usr/bin/gdbserver', ':%port%', '%program%', '%arguments%' ] - parameters: # <name>: <default value> - port: 5555 - supportedRuntimes: [ native, qml ] - supportedContainers: [ process ] -- name: qmldebugger # can also be used for the qmlprofiler - command: ['%program%', '-qmljsdebugger=port:%port%,block', '%arguments%' ] - parameters: # <name>: <default value> - port: 3456 - supportedRuntimes: [ qml ] - supportedContainers: [ process ] -- name: cppqmldebugger - command: [ '/usr/bin/gdbserver', ':%gdbport%', '%program%', '-qmljsdebugger=port:%qmlport%,block', '%arguments%' ] - parameters: # <name>: <default value> - gdbport: 5555 - qmlport: 3456 - supportedRuntimes: [ native, qml ] - supportedContainers: [ process ] -- name: valgrind - command: [ '/usr/bin/valgrind', '%program%', '%arguments%' ] - supportedRuntimes: [ native, qml ] - supportedContainers: [ process ] -- name: strace - command: [ '/usr/bin/strace', '%program%', '%arguments%' ] - supportedRuntimes: [ native, qml ] - supportedContainers: [ process ] diff --git a/tests/debugwrapper/debugwrapper.pro b/tests/debugwrapper/debugwrapper.pro new file mode 100644 index 00000000..b7e9128a --- /dev/null +++ b/tests/debugwrapper/debugwrapper.pro @@ -0,0 +1,8 @@ +TARGET = tst_debugwrapper + +include($$PWD/../tests.pri) + +QT *= \ + appman_manager-private + +SOURCES += tst_debugwrapper.cpp diff --git a/tests/debugwrapper/tst_debugwrapper.cpp b/tests/debugwrapper/tst_debugwrapper.cpp new file mode 100644 index 00000000..db979861 --- /dev/null +++ b/tests/debugwrapper/tst_debugwrapper.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite 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. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore> +#include <QtTest> + +#include <debugwrapper.h> + +#include "../error-checking.h" + +QT_USE_NAMESPACE_AM + +class tst_DebugWrapper : public QObject +{ + Q_OBJECT + +public: + tst_DebugWrapper(QObject *parent = nullptr); + ~tst_DebugWrapper(); + +private slots: + void specification_data(); + void specification(); + + void substitute_data(); + void substitute(); +}; + + +tst_DebugWrapper::tst_DebugWrapper(QObject *parent) + : QObject(parent) +{ } + +tst_DebugWrapper::~tst_DebugWrapper() +{ } + +typedef QMap<QString, QString> StringMap; +Q_DECLARE_METATYPE(StringMap) + +void tst_DebugWrapper::specification_data() +{ + QTest::addColumn<QString>("spec"); + QTest::addColumn<bool>("valid"); + QTest::addColumn<StringMap>("env"); + QTest::addColumn<QStringList>("cmd"); + + QMap<QString, QString> noenv; + + QTest::newRow("empty") << "" << false << noenv << QStringList(); + QTest::newRow("empty2") << " " << false << noenv << QStringList(); + + QTest::newRow("nocmd") << "foo=bar" << true << StringMap {{ "foo", "bar" }} << QStringList { "%program%", "%arguments%" }; + QTest::newRow("nocmd2") << "foo=bar " << true << StringMap {{ "foo", "bar" }} << QStringList { "%program%", "%arguments%" }; + + QTest::newRow("1") << "foo" << true << noenv << QStringList { "foo", "%program%", "%arguments%" }; + QTest::newRow("2") << " foo" << true << noenv << QStringList { "foo", "%program%", "%arguments%" }; + QTest::newRow("3") << "foo " << true << noenv << QStringList { "foo", "%program%", "%arguments%" }; + QTest::newRow("4") << "foo bar" << true << noenv << QStringList { "foo", "bar", "%program%", "%arguments%" }; + QTest::newRow("5") << "foo bar" << true << noenv << QStringList { "foo", "bar", "%program%", "%arguments%" }; + QTest::newRow("6") << "foo bar baz" << true << noenv << QStringList { "foo", "bar", "baz", "%program%", "%arguments%" }; + QTest::newRow("7") << "fo\\ o b\\nar b\\\\az" << true << noenv << QStringList { "fo o", "b\nar", "b\\az", "%program%", "%arguments%" }; + QTest::newRow("8") << "foo=bar baz" << true << StringMap {{ "foo", "bar" }} << QStringList { "baz", "%program%", "%arguments%" }; + QTest::newRow("9") << "foo=bar a= baz zab" << true << StringMap {{ "foo", "bar" }, { "a", QString() }} << QStringList { "baz", "zab", "%program%", "%arguments%" }; + QTest::newRow("a") << "foo=b\\ a=\\n baz z\\ ab" << true << StringMap {{ "foo", "b a=\n" }} << QStringList { "baz", "z ab", "%program%", "%arguments%" }; + QTest::newRow("b") << "a=b c d=e" << true << StringMap {{ "a", "b" }} << QStringList { "c", "d=e", "%program%", "%arguments%" }; + + QTest::newRow("z") << "a=b %program% c %arguments% d" << true << StringMap {{ "a", "b" }} << QStringList { "%program%", "c", "%arguments%", "d" }; + QTest::newRow("y") << "a=b %program% c d" << true << StringMap {{ "a", "b" }} << QStringList { "%program%", "c", "d", "%arguments%" }; + QTest::newRow("x") << "a=b %arguments%" << true << StringMap {{ "a", "b" }} << QStringList { "%arguments%", "%program%" }; + QTest::newRow("w") << "%program% %arguments%" << true << noenv << QStringList { "%program%", "%arguments%" }; + QTest::newRow("w") << "%program% foo-%program% foo-%arguments%-bar %arguments%" << true << noenv << QStringList { "%program%", "foo-%program%", "foo-%arguments%-bar", "%arguments%" }; +} + +void tst_DebugWrapper::specification() +{ + QFETCH(QString, spec); + QFETCH(bool, valid); + QFETCH(StringMap, env); + QFETCH(QStringList, cmd); + + StringMap resultEnv; + QStringList resultCmd; + QCOMPARE(DebugWrapper::parseSpecification(spec, resultCmd, resultEnv), valid); + QCOMPARE(cmd, resultCmd); + QCOMPARE(env, resultEnv); +} + +void tst_DebugWrapper::substitute_data() +{ + QTest::addColumn<QStringList>("cmd"); + QTest::addColumn<QString>("program"); + QTest::addColumn<QStringList>("arguments"); + QTest::addColumn<QStringList>("result"); + + QTest::newRow("1") << QStringList { "%program%", "%arguments%" } + << QString("prg") << QStringList { "arg1", "arg2" } + << QStringList { "prg", "arg1", "arg2" }; + + QTest::newRow("2") << QStringList { "%program%" } + << QString("prg") << QStringList { "arg1", "arg2" } + << QStringList { "prg" }; + + QTest::newRow("3") << QStringList { "%program%", "\"x-%program%\"", "%arguments%", "x-%arguments%" } + << QString("prg") << QStringList { "arg1", "arg2" } + << QStringList { "prg", "\"x-prg\"", "arg1", "arg2", "x-arg1 arg2" }; + + QTest::newRow("4") << QStringList { "foo", "%arguments%", "bar", "%program%", "baz", "%arguments%", "foo2" } + << QString("prg") << QStringList { "a1", "a2", "a3" } + << QStringList { "foo", "a1", "a2", "a3", "bar", "prg", "baz", "a1", "a2", "a3", "foo2" }; +} + +void tst_DebugWrapper::substitute() +{ + QFETCH(QStringList, cmd); + QFETCH(QString, program); + QFETCH(QStringList, arguments); + QFETCH(QStringList, result); + + QCOMPARE(DebugWrapper::substituteCommand(cmd, program, arguments), result); +} + +QTEST_APPLESS_MAIN(tst_DebugWrapper) + +#include "tst_debugwrapper.moc" diff --git a/tests/qml/simple/am-config.yaml b/tests/qml/simple/am-config.yaml index 614e1b00..81b2851e 100644 --- a/tests/qml/simple/am-config.yaml +++ b/tests/qml/simple/am-config.yaml @@ -64,11 +64,3 @@ flags: runtimes: qml: quitTime: 5000 - -debugWrappers: -- name: fakedebugger - command: [ '%program%', '%arguments%' ] - parameters: # <name>: <default value> - supportedRuntimes: [ native, qml ] - supportedContainers: [ process ] - diff --git a/tests/qml/simple/tst_applicationmanager.qml b/tests/qml/simple/tst_applicationmanager.qml index ff7a9ad2..6e64c294 100755 --- a/tests/qml/simple/tst_applicationmanager.qml +++ b/tests/qml/simple/tst_applicationmanager.qml @@ -350,7 +350,7 @@ TestCase { var started = false; if (data.tag === "Debug") { - started = ApplicationManager.debugApplication(data.appId, "fakedebugger"); + started = ApplicationManager.debugApplication(data.appId, "%program% %arguments%"); if (singleProcess) { verify(!started); return; @@ -464,14 +464,14 @@ TestCase { ignoreWarning("Cannot start an invalid application"); verify(!ApplicationManager.startApplication("invalidApplication")) - ignoreWarning("Tried to start application tld.test.simple1 using an invalid debug-wrapper specification: invalidDebugWrapper"); - verify(!ApplicationManager.debugApplication(simpleApplication.id, "invalidDebugWrapper")) + ignoreWarning("Tried to start application tld.test.simple1 using an invalid debug-wrapper specification: "); + verify(!ApplicationManager.debugApplication(simpleApplication.id, " ")) verify(ApplicationManager.startApplication(simpleApplication.id)); checkApplicationState(simpleApplication.id, ApplicationManager.StartingUp); checkApplicationState(simpleApplication.id, ApplicationManager.Running); - ignoreWarning("Application tld.test.simple1 is already running - cannot start with debug-wrapper: fakedebugger"); - verify(!ApplicationManager.debugApplication(simpleApplication.id, "fakedebugger")) + ignoreWarning("Application tld.test.simple1 is already running - cannot start with debug-wrapper: %program% %arguments%"); + verify(!ApplicationManager.debugApplication(simpleApplication.id, "%program% %arguments%")) ApplicationManager.stopApplication(simpleApplication.id, true); checkApplicationState(simpleApplication.id, ApplicationManager.ShuttingDown); checkApplicationState(simpleApplication.id, ApplicationManager.NotRunning); diff --git a/tests/tests.pro b/tests/tests.pro index 261bcffa..b18ad3eb 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -11,6 +11,7 @@ SUBDIRS = \ packageextractor \ packager-tool \ applicationinstaller \ + debugwrapper \ qml \ linux*:SUBDIRS += \ |