From 22599094b0d72e271ba4d8bad2c1587f9ce75c69 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 8 Aug 2013 14:05:11 +0200 Subject: Introduce the concept of a "device process". Provide a QProcess-like abstraction that can be used to implement processes running locally or on a remote device. Objects of a concrete class implementing the functionality are created by IDevice objects. Current implementations are: - Local execution (QProcess-based), provided via the DesktopDevice. - Remote execution via SSH. - A specialized case of the former for remote Linux systems (provided by LinuxDevice). The latter is already being used in a number of places. As a result, lots of code dealing with details such as setting the remote environment could be moved to a central location. These things are no longer the concern of whoever is wishing to run a remote process. Change-Id: I919260ee6e77a020ca47226a4a534e7b8398106f Reviewed-by: hjk --- src/libs/ssh/sshremoteprocess.cpp | 5 + src/libs/ssh/sshremoteprocess.h | 1 + src/plugins/madde/maddedevice.cpp | 12 + src/plugins/madde/maddedevice.h | 1 + src/plugins/madde/maemorunconfiguration.cpp | 18 -- src/plugins/madde/maemorunconfiguration.h | 2 - .../devicesupport/desktopdevice.cpp | 6 + .../projectexplorer/devicesupport/desktopdevice.h | 2 + .../devicesupport/desktopdeviceprocess.cpp | 125 ++++++++ .../devicesupport/desktopdeviceprocess.h | 70 +++++ .../devicesupport/deviceapplicationrunner.cpp | 71 +++-- .../devicesupport/deviceapplicationrunner.h | 16 +- .../devicesupport/deviceprocess.cpp | 52 ++++ .../projectexplorer/devicesupport/deviceprocess.h | 87 ++++++ .../projectexplorer/devicesupport/idevice.cpp | 6 + .../projectexplorer/devicesupport/idevice.h | 5 + .../devicesupport/sshdeviceprocess.cpp | 329 +++++++++++++++++++++ .../devicesupport/sshdeviceprocess.h | 86 ++++++ src/plugins/projectexplorer/projectexplorer.pro | 6 + src/plugins/projectexplorer/projectexplorer.qbs | 6 + src/plugins/qnx/blackberrydeviceprocesssupport.cpp | 17 +- src/plugins/qnx/blackberrydeviceprocesssupport.h | 1 + src/plugins/qnx/qnxabstractrunsupport.cpp | 10 +- src/plugins/qnx/qnxabstractrunsupport.h | 7 +- src/plugins/qnx/qnxanalyzesupport.cpp | 11 +- src/plugins/qnx/qnxdebugsupport.cpp | 14 +- src/plugins/qnx/qnxdeviceconfiguration.cpp | 17 +- src/plugins/qnx/qnxrunconfiguration.cpp | 23 +- src/plugins/qnx/qnxrunconfiguration.h | 4 +- src/plugins/qnx/qnxruncontrolfactory.cpp | 5 +- .../remotelinux/abstractremotelinuxrunsupport.cpp | 32 +- .../remotelinux/abstractremotelinuxrunsupport.h | 7 +- src/plugins/remotelinux/linuxdevice.cpp | 24 +- src/plugins/remotelinux/linuxdevice.h | 3 + src/plugins/remotelinux/linuxdeviceprocess.cpp | 84 ++++++ src/plugins/remotelinux/linuxdeviceprocess.h | 64 ++++ src/plugins/remotelinux/remotelinux.pro | 6 +- src/plugins/remotelinux/remotelinux.qbs | 2 + .../remotelinux/remotelinuxanalyzesupport.cpp | 11 +- .../remotelinux/remotelinuxdebugsupport.cpp | 22 +- .../remotelinuxenvironmentaspectwidget.cpp | 2 +- .../remotelinux/remotelinuxenvironmentreader.cpp | 59 ++-- .../remotelinux/remotelinuxenvironmentreader.h | 15 +- .../remotelinux/remotelinuxrunconfiguration.cpp | 36 +-- .../remotelinux/remotelinuxrunconfiguration.h | 12 +- .../remotelinuxrunconfigurationwidget.cpp | 2 +- src/plugins/remotelinux/remotelinuxruncontrol.cpp | 19 +- src/plugins/valgrind/valgrindruncontrolfactory.cpp | 3 +- 48 files changed, 1212 insertions(+), 206 deletions(-) create mode 100644 src/plugins/projectexplorer/devicesupport/desktopdeviceprocess.cpp create mode 100644 src/plugins/projectexplorer/devicesupport/desktopdeviceprocess.h create mode 100644 src/plugins/projectexplorer/devicesupport/deviceprocess.cpp create mode 100644 src/plugins/projectexplorer/devicesupport/deviceprocess.h create mode 100644 src/plugins/projectexplorer/devicesupport/sshdeviceprocess.cpp create mode 100644 src/plugins/projectexplorer/devicesupport/sshdeviceprocess.h create mode 100644 src/plugins/remotelinux/linuxdeviceprocess.cpp create mode 100644 src/plugins/remotelinux/linuxdeviceprocess.h diff --git a/src/libs/ssh/sshremoteprocess.cpp b/src/libs/ssh/sshremoteprocess.cpp index ec40f9c6f3..6602b3c1df 100644 --- a/src/libs/ssh/sshremoteprocess.cpp +++ b/src/libs/ssh/sshremoteprocess.cpp @@ -176,6 +176,11 @@ void SshRemoteProcess::addToEnvironment(const QByteArray &var, const QByteArray d->m_env << qMakePair(var, value); // Cached locally and sent on start() } +void SshRemoteProcess::clearEnvironment() +{ + d->m_env.clear(); +} + void SshRemoteProcess::requestTerminal(const SshPseudoTerminal &terminal) { QSSH_ASSERT_AND_RETURN(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive); diff --git a/src/libs/ssh/sshremoteprocess.h b/src/libs/ssh/sshremoteprocess.h index a50a3f0f2f..40c7db7790 100644 --- a/src/libs/ssh/sshremoteprocess.h +++ b/src/libs/ssh/sshremoteprocess.h @@ -80,6 +80,7 @@ public: * usually configured to ignore such requests for security reasons. */ void addToEnvironment(const QByteArray &var, const QByteArray &value); + void clearEnvironment(); void requestTerminal(const SshPseudoTerminal &terminal); void start(); diff --git a/src/plugins/madde/maddedevice.cpp b/src/plugins/madde/maddedevice.cpp index 821fb059f6..b055872adb 100644 --- a/src/plugins/madde/maddedevice.cpp +++ b/src/plugins/madde/maddedevice.cpp @@ -31,10 +31,13 @@ #include "maddedevicetester.h" #include "maemoconstants.h" +#include #include #include #include +#include + using namespace ProjectExplorer; using namespace RemoteLinux; @@ -115,5 +118,14 @@ DeviceTester *MaddeDevice::createDeviceTester() const return new MaddeDeviceTester; } +DeviceProcess *MaddeDevice::createProcess(QObject *parent) const +{ + LinuxDeviceProcess * const proc + = static_cast(LinuxDevice::createProcess(parent)); + proc->setRcFilesToSource(QStringList() << QLatin1String("/etc/profile") + << QLatin1String("/home/user/.profile") << QLatin1String("~/.profile")); + return proc; +} + } // namespace Internal } // namespace Madde diff --git a/src/plugins/madde/maddedevice.h b/src/plugins/madde/maddedevice.h index bd2ec00253..a0bb80ca6d 100644 --- a/src/plugins/madde/maddedevice.h +++ b/src/plugins/madde/maddedevice.h @@ -59,6 +59,7 @@ public: static QSize packageManagerIconSize(Core::Id type); ProjectExplorer::DeviceTester *createDeviceTester() const; + ProjectExplorer::DeviceProcess *createProcess(QObject *parent) const; private: MaddeDevice(); diff --git a/src/plugins/madde/maemorunconfiguration.cpp b/src/plugins/madde/maemorunconfiguration.cpp index b046428523..55c17c8393 100644 --- a/src/plugins/madde/maemorunconfiguration.cpp +++ b/src/plugins/madde/maemorunconfiguration.cpp @@ -112,24 +112,6 @@ bool MaemoRunConfiguration::fromMap(const QVariantMap &map) return true; } -QString MaemoRunConfiguration::environmentPreparationCommand() const -{ - return MaemoGlobal::remoteSourceProfilesCommand(); -} - -QString MaemoRunConfiguration::commandPrefix() const -{ - IDevice::ConstPtr dev = DeviceKitInformation::device(target()->kit()); - if (!dev) - return QString(); - - const QString prefix = environmentPreparationCommand() + QLatin1Char(';'); - - RemoteLinuxEnvironmentAspect *aspect = extraAspect(); - QTC_ASSERT(aspect, return QString()); - return QString::fromLatin1("%1 %2").arg(prefix, aspect->userEnvironmentChangesAsString()); -} - Utils::PortList MaemoRunConfiguration::freePorts() const { return MaemoGlobal::freePorts(target()->kit()); diff --git a/src/plugins/madde/maemorunconfiguration.h b/src/plugins/madde/maemorunconfiguration.h index d7083322d9..edfebb8fe7 100644 --- a/src/plugins/madde/maemorunconfiguration.h +++ b/src/plugins/madde/maemorunconfiguration.h @@ -48,8 +48,6 @@ public: bool fromMap(const QVariantMap &map); bool isEnabled() const; QWidget *createConfigurationWidget(); - QString environmentPreparationCommand() const; - QString commandPrefix() const; Utils::PortList freePorts() const; Internal::MaemoRemoteMountsModel *remoteMounts() const { return m_remoteMounts; } diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp index 68478164d7..d0e7643e6d 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp @@ -29,6 +29,7 @@ #include "desktopdevice.h" #include "projectexplorerconstants.h" +#include "desktopdeviceprocess.h" #include "deviceprocesslist.h" #include "localprocesslist.h" #include "desktopdeviceconfigurationwidget.h" @@ -108,6 +109,11 @@ DeviceProcessList *DesktopDevice::createProcessListModel(QObject *parent) const return new Internal::LocalProcessList(sharedFromThis(), parent); } +DeviceProcess *DesktopDevice::createProcess(QObject *parent) const +{ + return new Internal::DesktopDeviceProcess(sharedFromThis(), parent); +} + IDevice::Ptr DesktopDevice::clone() const { return Ptr(new DesktopDevice(*this)); diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.h b/src/plugins/projectexplorer/devicesupport/desktopdevice.h index 58f86d191a..d737ceeea7 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.h +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.h @@ -53,6 +53,8 @@ public: bool canAutoDetectPorts() const; bool canCreateProcessModel() const; DeviceProcessList *createProcessListModel(QObject *parent) const; + bool canCreateProcess() const { return true; } + DeviceProcess *createProcess(QObject *parent) const; IDevice::Ptr clone() const; diff --git a/src/plugins/projectexplorer/devicesupport/desktopdeviceprocess.cpp b/src/plugins/projectexplorer/devicesupport/desktopdeviceprocess.cpp new file mode 100644 index 0000000000..a151a983dd --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/desktopdeviceprocess.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "desktopdeviceprocess.h" + +#include +#include + +#ifdef Q_OS_UNIX +#include +#endif + +namespace ProjectExplorer { +namespace Internal { + +DesktopDeviceProcess::DesktopDeviceProcess(const QSharedPointer &device, + QObject *parent) + : DeviceProcess(device, parent), m_process(new QProcess(this)) +{ + connect(m_process, SIGNAL(error(QProcess::ProcessError)), + SIGNAL(error(QProcess::ProcessError))); + connect(m_process, SIGNAL(finished(int)), SIGNAL(finished())); + connect(m_process, SIGNAL(readyReadStandardOutput()), SIGNAL(readyReadStandardOutput())); + connect(m_process, SIGNAL(readyReadStandardError()), SIGNAL(readyReadStandardError())); + connect(m_process, SIGNAL(started()), SIGNAL(started())); +} + +void DesktopDeviceProcess::start(const QString &executable, const QStringList &arguments) +{ + QTC_ASSERT(m_process->state() == QProcess::NotRunning, return); + m_process->start(executable, arguments); +} + +void DesktopDeviceProcess::interrupt() +{ +#ifdef Q_OS_UNIX + ::kill(m_process->pid(), SIGINT); +#elif Q_OS_WIN + // tbd +#endif +} + +void DesktopDeviceProcess::terminate() +{ + m_process->terminate(); +} + +void DesktopDeviceProcess::kill() +{ + m_process->kill(); +} + +QProcess::ProcessState DesktopDeviceProcess::state() const +{ + return m_process->state(); +} + +QProcess::ExitStatus DesktopDeviceProcess::exitStatus() const +{ + return m_process->exitStatus(); +} + +int DesktopDeviceProcess::exitCode() const +{ + return m_process->exitCode(); +} + +QString DesktopDeviceProcess::errorString() const +{ + return m_process->errorString(); +} + +Utils::Environment DesktopDeviceProcess::environment() const +{ + return Utils::Environment(m_process->processEnvironment().toStringList()); +} + +void DesktopDeviceProcess::setEnvironment(const Utils::Environment &env) +{ + m_process->setProcessEnvironment(env.toProcessEnvironment()); +} + +void DesktopDeviceProcess::setWorkingDirectory(const QString &directory) +{ + m_process->setWorkingDirectory(directory); +} + +QByteArray DesktopDeviceProcess::readAllStandardOutput() +{ + return m_process->readAllStandardOutput(); +} + +QByteArray DesktopDeviceProcess::readAllStandardError() +{ + return m_process->readAllStandardError(); +} + +} // namespace Internal +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/desktopdeviceprocess.h b/src/plugins/projectexplorer/devicesupport/desktopdeviceprocess.h new file mode 100644 index 0000000000..1a245a8195 --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/desktopdeviceprocess.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QTC_DESKTOPDEVICEPROCESS_H +#define QTC_DESKTOPDEVICEPROCESS_H + +#include "deviceprocess.h" + +namespace ProjectExplorer { +namespace Internal { +class ProcessHelper; + +class DesktopDeviceProcess : public DeviceProcess +{ + Q_OBJECT +public: + DesktopDeviceProcess(const QSharedPointer &device, QObject *parent = 0); + + void start(const QString &executable, const QStringList &arguments); + void interrupt(); + void terminate(); + void kill(); + + QProcess::ProcessState state() const; + QProcess::ExitStatus exitStatus() const; + int exitCode() const; + QString errorString() const; + + Utils::Environment environment() const; + void setEnvironment(const Utils::Environment &env); + + void setWorkingDirectory(const QString &directory); + + QByteArray readAllStandardOutput(); + QByteArray readAllStandardError(); + +private: + QProcess * const m_process; +}; + +} // namespace Internal +} // namespace ProjectExplorer + +#endif // Include guard diff --git a/src/plugins/projectexplorer/devicesupport/deviceapplicationrunner.cpp b/src/plugins/projectexplorer/devicesupport/deviceapplicationrunner.cpp index b7fe7248c3..deda2ebb2e 100644 --- a/src/plugins/projectexplorer/devicesupport/deviceapplicationrunner.cpp +++ b/src/plugins/projectexplorer/devicesupport/deviceapplicationrunner.cpp @@ -28,11 +28,14 @@ ****************************************************************************/ #include "deviceapplicationrunner.h" +#include "sshdeviceprocess.h" + #include #include -#include +#include #include +#include #include using namespace QSsh; @@ -49,10 +52,13 @@ public: SshConnection *connection; DeviceApplicationHelperAction *preRunAction; DeviceApplicationHelperAction *postRunAction; + DeviceProcess *deviceProcess; IDevice::ConstPtr device; - SshRemoteProcess::Ptr remoteApp; QTimer stopTimer; - QByteArray commandLine; + QString command; + QStringList arguments; + Utils::Environment environment; + QString workingDir; State state; bool stopRequested; bool success; @@ -74,6 +80,7 @@ DeviceApplicationRunner::DeviceApplicationRunner(QObject *parent) : d->preRunAction = 0; d->postRunAction = 0; d->connection = 0; + d->deviceProcess = 0; d->state = Inactive; d->stopTimer.setSingleShot(true); @@ -86,23 +93,33 @@ DeviceApplicationRunner::~DeviceApplicationRunner() delete d; } +void DeviceApplicationRunner::setEnvironment(const Utils::Environment &env) +{ + d->environment = env; +} + +void DeviceApplicationRunner::setWorkingDirectory(const QString &workingDirectory) +{ + d->workingDir = workingDirectory; +} + void DeviceApplicationRunner::start(const IDevice::ConstPtr &device, - const QByteArray &commandLine) + const QString &command, const QStringList &arguments) { + QTC_ASSERT(device->canCreateProcess(), return); QTC_ASSERT(d->state == Inactive, return); d->device = device; - d->commandLine = commandLine; + d->command = command; + d->arguments = arguments; d->stopRequested = false; d->success = true; connectToServer(); } -void DeviceApplicationRunner::stop(const QByteArray &stopCommand) +void DeviceApplicationRunner::stop() { - QTC_ASSERT(d->state != Inactive, return); - if (d->stopRequested) return; d->stopRequested = true; @@ -117,7 +134,7 @@ void DeviceApplicationRunner::stop(const QByteArray &stopCommand) break; case Run: d->stopTimer.start(10000); - d->connection->createRemoteProcess(stopCommand)->start(); + d->deviceProcess->terminate(); break; case PostRun: d->postRunAction->stop(); @@ -188,10 +205,10 @@ void DeviceApplicationRunner::setFinished() if (d->state == Inactive) return; - if (d->remoteApp) { - d->remoteApp->disconnect(this); - d->remoteApp->close(); - d->remoteApp.clear(); + if (d->deviceProcess) { + d->deviceProcess->disconnect(this); + d->deviceProcess->deleteLater(); + d->deviceProcess = 0; } if (d->connection) { d->connection->disconnect(this); @@ -232,7 +249,7 @@ void DeviceApplicationRunner::handleConnectionFailure() break; case Run: d->stopTimer.stop(); - d->remoteApp->disconnect(this); + d->deviceProcess->disconnect(this); executePostRunAction(); break; case PostRun: @@ -290,16 +307,16 @@ void DeviceApplicationRunner::handleStopTimeout() setFinished(); } -void DeviceApplicationRunner::handleApplicationFinished(int exitStatus) +void DeviceApplicationRunner::handleApplicationFinished() { QTC_ASSERT(d->state == Run, return); d->stopTimer.stop(); - if (exitStatus == SshRemoteProcess::CrashExit) { - emit reportError(tr("Remote application crashed: %1").arg(d->remoteApp->errorString())); + if (d->deviceProcess->exitStatus() == QProcess::CrashExit) { + emit reportError(tr("Remote application crashed: %1").arg(d->deviceProcess->errorString())); d->success = false; } else { - const int exitCode = d->remoteApp->exitCode(); + const int exitCode = d->deviceProcess->exitCode(); if (exitCode != 0) { emit reportError(tr("Remote application finished with exit code %1.").arg(exitCode)); d->success = false; @@ -313,13 +330,13 @@ void DeviceApplicationRunner::handleApplicationFinished(int exitStatus) void DeviceApplicationRunner::handleRemoteStdout() { QTC_ASSERT(d->state == Run, return); - emit remoteStdout(d->remoteApp->readAllStandardOutput()); + emit remoteStdout(d->deviceProcess->readAllStandardOutput()); } void DeviceApplicationRunner::handleRemoteStderr() { QTC_ASSERT(d->state == Run, return); - emit remoteStderr(d->remoteApp->readAllStandardError()); + emit remoteStderr(d->deviceProcess->readAllStandardError()); } void DeviceApplicationRunner::runApplication() @@ -327,12 +344,14 @@ void DeviceApplicationRunner::runApplication() QTC_ASSERT(d->state == PreRun, return); d->state = Run; - d->remoteApp = d->connection->createRemoteProcess(d->commandLine); - connect(d->remoteApp.data(), SIGNAL(started()), SIGNAL(remoteProcessStarted())); - connect(d->remoteApp.data(), SIGNAL(readyReadStandardOutput()), SLOT(handleRemoteStdout())); - connect(d->remoteApp.data(), SIGNAL(readyReadStandardError()), SLOT(handleRemoteStderr())); - connect(d->remoteApp.data(), SIGNAL(closed(int)), SLOT(handleApplicationFinished(int))); - d->remoteApp->start(); + d->deviceProcess = d->device->createProcess(this); + connect(d->deviceProcess, SIGNAL(started()), SIGNAL(remoteProcessStarted())); + connect(d->deviceProcess, SIGNAL(readyReadStandardOutput()), SLOT(handleRemoteStdout())); + connect(d->deviceProcess, SIGNAL(readyReadStandardError()), SLOT(handleRemoteStderr())); + connect(d->deviceProcess, SIGNAL(finished()), SLOT(handleApplicationFinished())); + d->deviceProcess->setEnvironment(d->environment); + d->deviceProcess->setWorkingDirectory(d->workingDir); + d->deviceProcess->start(d->command, d->arguments); } } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/deviceapplicationrunner.h b/src/plugins/projectexplorer/devicesupport/deviceapplicationrunner.h index 483f125692..188d05be22 100644 --- a/src/plugins/projectexplorer/devicesupport/deviceapplicationrunner.h +++ b/src/plugins/projectexplorer/devicesupport/deviceapplicationrunner.h @@ -35,6 +35,12 @@ #include +QT_BEGIN_NAMESPACE +class QStringList; +QT_END_NAMESPACE + +namespace Utils { class Environment; } + namespace ProjectExplorer { class PROJECTEXPLORER_EXPORT DeviceApplicationHelperAction : public QObject @@ -60,8 +66,12 @@ public: explicit DeviceApplicationRunner(QObject *parent = 0); virtual ~DeviceApplicationRunner(); - void start(const IDevice::ConstPtr &device, const QByteArray &commandLine); - void stop(const QByteArray &stopCommand); + void setEnvironment(const Utils::Environment &env); + void setWorkingDirectory(const QString &workingDirectory); + + void start(const IDevice::ConstPtr &device, const QString &command, + const QStringList &arguments); + void stop(); // Use these if you need to do something before and after the application is run, respectively. // Typically, the post-run action reverts the effects of the pre-run action. @@ -82,7 +92,7 @@ private slots: void handleConnectionFailure(); void handleHelperActionFinished(bool success); void handleStopTimeout(); - void handleApplicationFinished(int exitStatus); + void handleApplicationFinished(); void handleRemoteStdout(); void handleRemoteStderr(); diff --git a/src/plugins/projectexplorer/devicesupport/deviceprocess.cpp b/src/plugins/projectexplorer/devicesupport/deviceprocess.cpp new file mode 100644 index 0000000000..aa2f8c748c --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/deviceprocess.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "deviceprocess.h" + +#include "idevice.h" + +#include + +namespace ProjectExplorer { + +DeviceProcess::DeviceProcess(const IDevice::ConstPtr &device, QObject *parent) + : QObject(parent), m_device(device) +{ +} + +DeviceProcess::~DeviceProcess() +{ +} + +IDevice::ConstPtr DeviceProcess::device() const +{ + return m_device; +} + +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/deviceprocess.h b/src/plugins/projectexplorer/devicesupport/deviceprocess.h new file mode 100644 index 0000000000..92ef02dda0 --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/deviceprocess.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QTC_DEVICEPROCESS_H +#define QTC_DEVICEPROCESS_H + +#include "../projectexplorer_export.h" + +#include +#include +#include +#include + +namespace Utils { class Environment; } + +namespace ProjectExplorer { +class IDevice; + +class PROJECTEXPLORER_EXPORT DeviceProcess : public QObject +{ + Q_OBJECT +public: + virtual ~DeviceProcess(); + + virtual void start(const QString &executable, const QStringList &arguments = QStringList()) = 0; + virtual void interrupt() = 0; + virtual void terminate() = 0; + virtual void kill() = 0; + + virtual QProcess::ProcessState state() const = 0; + virtual QProcess::ExitStatus exitStatus() const = 0; + virtual int exitCode() const = 0; + virtual QString errorString() const = 0; + + virtual Utils::Environment environment() const = 0; + virtual void setEnvironment(const Utils::Environment &env) = 0; + + virtual void setWorkingDirectory(const QString &workingDirectory) = 0; + + virtual QByteArray readAllStandardOutput() = 0; + virtual QByteArray readAllStandardError() = 0; + +signals: + void started(); + void finished(); + void error(QProcess::ProcessError error); + + void readyReadStandardOutput(); + void readyReadStandardError(); + +protected: + DeviceProcess(const QSharedPointer &device, QObject *parent = 0); + QSharedPointer device() const; + +private: + const QSharedPointer m_device; +}; + +} // namespace ProjectExplorer + +#endif // Include guard. diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index 7f24712c73..f39615def8 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -275,6 +275,12 @@ DeviceTester *IDevice::createDeviceTester() const return 0; } +DeviceProcess *IDevice::createProcess(QObject * /* parent */) const +{ + QTC_CHECK(false); + return 0; +} + IDevice::DeviceState IDevice::deviceState() const { return d->deviceState; diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index b066688132..8b416c0409 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -47,6 +47,7 @@ namespace QSsh { class SshConnectionParameters; } namespace Utils { class PortList; } namespace ProjectExplorer { +class DeviceProcess; class DeviceProcessList; namespace Internal { class IDevicePrivate; } @@ -62,6 +63,7 @@ public: virtual ~DeviceProcessSupport(); virtual QString killProcessByPidCommandLine(int pid) const = 0; virtual QString killProcessByNameCommandLine(const QString &filePath) const = 0; + virtual QString interruptProcessByNameCommandLine(const QString &filePath) const = 0; }; class PROJECTEXPLORER_EXPORT PortsGatheringMethod @@ -121,6 +123,9 @@ public: virtual bool hasDeviceTester() const { return false; } virtual DeviceTester *createDeviceTester() const; + virtual bool canCreateProcess() const { return false; } + virtual DeviceProcess *createProcess(QObject *parent) const; + enum DeviceState { DeviceReadyToUse, DeviceConnected, DeviceDisconnected, DeviceStateUnknown }; DeviceState deviceState() const; void setDeviceState(const DeviceState state); diff --git a/src/plugins/projectexplorer/devicesupport/sshdeviceprocess.cpp b/src/plugins/projectexplorer/devicesupport/sshdeviceprocess.cpp new file mode 100644 index 0000000000..1fad5540b2 --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/sshdeviceprocess.cpp @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "sshdeviceprocess.h" + +#include "idevice.h" + +#include +#include +#include +#include +#include + +#include + +namespace ProjectExplorer { + +class SshDeviceProcess::SshDeviceProcessPrivate +{ +public: + SshDeviceProcessPrivate(SshDeviceProcess *q) : q(q) {} + + SshDeviceProcess * const q; + bool serverSupportsSignals; + QSsh::SshConnection *connection; + QSsh::SshRemoteProcess::Ptr process; + QString executable; + QStringList arguments; + QString errorMessage; + QSsh::SshRemoteProcess::ExitStatus exitStatus; + QByteArray stdOut; + QByteArray stdErr; + int exitCode; + enum State { Inactive, Connecting, Connected, ProcessRunning } state; + Utils::Environment environment; + + void setState(State newState); + void doSignal(QSsh::SshRemoteProcess::Signal signal); +}; + +SshDeviceProcess::SshDeviceProcess(const IDevice::ConstPtr &device, QObject *parent) + : DeviceProcess(device, parent), d(new SshDeviceProcessPrivate(this)) +{ + d->connection = 0; + d->state = SshDeviceProcessPrivate::Inactive; + setSshServerSupportsSignals(false); +} + +SshDeviceProcess::~SshDeviceProcess() +{ + d->setState(SshDeviceProcessPrivate::Inactive); + delete d; +} + +void SshDeviceProcess::start(const QString &executable, const QStringList &arguments) +{ + QTC_ASSERT(d->state == SshDeviceProcessPrivate::Inactive, return); + d->setState(SshDeviceProcessPrivate::Connecting); + + d->errorMessage.clear(); + d->exitCode = -1; + d->executable = executable; + d->arguments = arguments; + d->connection + = QSsh::SshConnectionManager::instance().acquireConnection(device()->sshParameters()); + connect(d->connection, SIGNAL(error(QSsh::SshError)), SLOT(handleConnectionError())); + connect(d->connection, SIGNAL(disconnected()), SLOT(handleDisconnected())); + if (d->connection->state() == QSsh::SshConnection::Connected) { + handleConnected(); + } else { + connect(d->connection, SIGNAL(connected()), SLOT(handleConnected())); + if (d->connection->state() == QSsh::SshConnection::Unconnected) + d->connection->connectToHost(); + } +} + +void SshDeviceProcess::interrupt() +{ + QTC_ASSERT(d->state == SshDeviceProcessPrivate::ProcessRunning, return); + d->doSignal(QSsh::SshRemoteProcess::IntSignal); +} + +void SshDeviceProcess::terminate() +{ + d->doSignal(QSsh::SshRemoteProcess::TermSignal); +} + +void SshDeviceProcess::kill() +{ + d->doSignal(QSsh::SshRemoteProcess::KillSignal); +} + +QString SshDeviceProcess::executable() const +{ + return d->executable; +} + +QStringList SshDeviceProcess::arguments() const +{ + return d->arguments; +} + +QProcess::ProcessState SshDeviceProcess::state() const +{ + switch (d->state) { + case SshDeviceProcessPrivate::Inactive: + return QProcess::NotRunning; + case SshDeviceProcessPrivate::Connecting: + case SshDeviceProcessPrivate::Connected: + return QProcess::Starting; + case SshDeviceProcessPrivate::ProcessRunning: + return QProcess::Running; + default: + QTC_CHECK(false); + return QProcess::NotRunning; + } +} + +QProcess::ExitStatus SshDeviceProcess::exitStatus() const +{ + return d->exitStatus == QSsh::SshRemoteProcess::NormalExit + ? QProcess::NormalExit : QProcess::CrashExit; +} + +int SshDeviceProcess::exitCode() const +{ + return d->exitCode; +} + +QString SshDeviceProcess::errorString() const +{ + return d->errorMessage; +} + +Utils::Environment SshDeviceProcess::environment() const +{ + return d->environment; +} + +void SshDeviceProcess::setEnvironment(const Utils::Environment &env) +{ + d->environment = env; +} + +QByteArray SshDeviceProcess::readAllStandardOutput() +{ + const QByteArray data = d->stdOut; + d->stdOut.clear(); + return data; +} + +QByteArray SshDeviceProcess::readAllStandardError() +{ + const QByteArray data = d->stdErr; + d->stdErr.clear(); + return data; +} + +void SshDeviceProcess::setSshServerSupportsSignals(bool signalsSupported) +{ + d->serverSupportsSignals = signalsSupported; +} + +void SshDeviceProcess::handleConnected() +{ + QTC_ASSERT(d->state == SshDeviceProcessPrivate::Connecting, return); + d->setState(SshDeviceProcessPrivate::Connected); + + d->process = d->connection->createRemoteProcess(fullCommandLine().toUtf8()); + connect(d->process.data(), SIGNAL(started()), SLOT(handleProcessStarted())); + connect(d->process.data(), SIGNAL(closed(int)), SLOT(handleProcessFinished(int))); + connect(d->process.data(), SIGNAL(readyReadStandardOutput()), SLOT(handleStdout())); + connect(d->process.data(), SIGNAL(readyReadStandardError()), SLOT(handleStderr())); + + d->process->clearEnvironment(); + const Utils::Environment env = environment(); + for (Utils::Environment::const_iterator it = env.constBegin(); it != env.constEnd(); ++it) + d->process->addToEnvironment(env.key(it).toUtf8(), env.value(it).toUtf8()); + d->process->start(); +} + +void SshDeviceProcess::handleConnectionError() +{ + QTC_ASSERT(d->state != SshDeviceProcessPrivate::Inactive, return); + + d->errorMessage = d->connection->errorString(); + handleDisconnected(); +} + +void SshDeviceProcess::handleDisconnected() +{ + QTC_ASSERT(d->state != SshDeviceProcessPrivate::Inactive, return); + const SshDeviceProcessPrivate::State oldState = d->state; + d->setState(SshDeviceProcessPrivate::Inactive); + switch (oldState) { + case SshDeviceProcessPrivate::Connecting: + case SshDeviceProcessPrivate::Connected: + emit error(QProcess::FailedToStart); + break; + case SshDeviceProcessPrivate::ProcessRunning: + emit finished(); + default: + break; + } +} + +void SshDeviceProcess::handleProcessStarted() +{ + QTC_ASSERT(d->state == SshDeviceProcessPrivate::Connected, return); + + d->setState(SshDeviceProcessPrivate::ProcessRunning); + emit started(); +} + +void SshDeviceProcess::handleProcessFinished(int exitStatus) +{ + d->exitStatus = static_cast(exitStatus); + switch (d->exitStatus) { + case QSsh::SshRemoteProcess::FailedToStart: + QTC_ASSERT(d->state == SshDeviceProcessPrivate::Connected, return); + break; + case QSsh::SshRemoteProcess::CrashExit: + QTC_ASSERT(d->state == SshDeviceProcessPrivate::ProcessRunning, return); + break; + case QSsh::SshRemoteProcess::NormalExit: + QTC_ASSERT(d->state == SshDeviceProcessPrivate::ProcessRunning, return); + d->exitCode = d->process->exitCode(); + break; + default: + QTC_ASSERT(false, return); + } + d->errorMessage = d->process->errorString(); + d->setState(SshDeviceProcessPrivate::Inactive); + emit finished(); +} + +void SshDeviceProcess::handleStdout() +{ + d->stdOut += d->process->readAllStandardOutput(); + emit readyReadStandardOutput(); +} + +void SshDeviceProcess::handleStderr() +{ + d->stdErr += d->process->readAllStandardError(); + emit readyReadStandardError(); +} + +QString SshDeviceProcess::fullCommandLine() const +{ + QString cmdLine = executable(); + if (!arguments().isEmpty()) + cmdLine.append(QLatin1Char(' ')).append(arguments().join(QLatin1String(" "))); + return cmdLine; +} + +void SshDeviceProcess::SshDeviceProcessPrivate::doSignal(QSsh::SshRemoteProcess::Signal signal) +{ + switch (state) { + case SshDeviceProcessPrivate::Inactive: + QTC_ASSERT(false, return); + break; + case SshDeviceProcessPrivate::Connecting: + errorMessage = tr("Terminated by request."); + setState(SshDeviceProcessPrivate::Inactive); + emit q->error(QProcess::FailedToStart); + break; + case SshDeviceProcessPrivate::Connected: + case SshDeviceProcessPrivate::ProcessRunning: + if (serverSupportsSignals) { + process->sendSignal(signal); + } else { + const DeviceProcessSupport::Ptr processSupport = q->device()->processSupport(); + QString signalCommandLine = signal == QSsh::SshRemoteProcess::IntSignal + ? processSupport->interruptProcessByNameCommandLine(executable) + : processSupport->killProcessByNameCommandLine(executable); + const QSsh::SshRemoteProcess::Ptr signalProcess + = connection->createRemoteProcess(signalCommandLine.toUtf8()); + signalProcess->start(); + } + break; + } +} + +void SshDeviceProcess::SshDeviceProcessPrivate::setState(SshDeviceProcess::SshDeviceProcessPrivate::State newState) +{ + if (state == newState) + return; + + state = newState; + if (state != Inactive) + return; + + if (process) + process->disconnect(q); + if (connection) { + connection->disconnect(q); + QSsh::SshConnectionManager::instance().releaseConnection(connection); + connection = 0; + } +} + +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/sshdeviceprocess.h b/src/plugins/projectexplorer/devicesupport/sshdeviceprocess.h new file mode 100644 index 0000000000..6981921a72 --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/sshdeviceprocess.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QTC_SSHDEVICEPROCESS_H +#define QTC_SSHDEVICEPROCESS_H + +#include "deviceprocess.h" + +namespace ProjectExplorer { + +class PROJECTEXPLORER_EXPORT SshDeviceProcess : public DeviceProcess +{ + Q_OBJECT +public: + SshDeviceProcess(const QSharedPointer &device, QObject *parent = 0); + ~SshDeviceProcess(); + + void start(const QString &executable, const QStringList &arguments); + void interrupt(); + void terminate(); + void kill(); + + QString executable() const; + QStringList arguments() const; + QProcess::ProcessState state() const; + QProcess::ExitStatus exitStatus() const; + int exitCode() const; + QString errorString() const; + + Utils::Environment environment() const; + void setEnvironment(const Utils::Environment &env); + + void setWorkingDirectory(const QString & /* directory */) { } // No such thing in the RFC. + + QByteArray readAllStandardOutput(); + QByteArray readAllStandardError(); + + // Default is "false" due to OpenSSH not implementing this feature for some reason. + void setSshServerSupportsSignals(bool signalsSupported); + +private slots: + void handleConnected(); + void handleConnectionError(); + void handleDisconnected(); + void handleProcessStarted(); + void handleProcessFinished(int exitStatus); + void handleStdout(); + void handleStderr(); + +private: + virtual QString fullCommandLine() const; + + class SshDeviceProcessPrivate; + friend class SshDeviceProcessPrivate; + SshDeviceProcessPrivate * const d; +}; + +} // namespace ProjectExplorer + +#endif // Include guard. diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro index d1a17621b2..484db7658f 100644 --- a/src/plugins/projectexplorer/projectexplorer.pro +++ b/src/plugins/projectexplorer/projectexplorer.pro @@ -113,10 +113,12 @@ HEADERS += projectexplorer.h \ devicesupport/desktopdevicefactory.h \ devicesupport/idevicewidget.h \ devicesupport/idevicefactory.h \ + devicesupport/desktopdeviceprocess.h \ devicesupport/devicecheckbuildstep.h \ devicesupport/devicemanager.h \ devicesupport/devicemanagermodel.h \ devicesupport/devicefactoryselectiondialog.h \ + devicesupport/deviceprocess.h \ devicesupport/deviceprocesslist.h \ devicesupport/deviceprocessesdialog.h \ devicesupport/devicesettingswidget.h \ @@ -125,6 +127,7 @@ HEADERS += projectexplorer.h \ devicesupport/deviceusedportsgatherer.h \ devicesupport/deviceapplicationrunner.h \ devicesupport/localprocesslist.h \ + devicesupport/sshdeviceprocess.h \ devicesupport/sshdeviceprocesslist.h \ devicesupport/desktopdeviceconfigurationwidget.h \ deploymentdata.h \ @@ -232,10 +235,12 @@ SOURCES += projectexplorer.cpp \ devicesupport/desktopdevice.cpp \ devicesupport/desktopdevicefactory.cpp \ devicesupport/idevicefactory.cpp \ + devicesupport/desktopdeviceprocess.cpp \ devicesupport/devicecheckbuildstep.cpp \ devicesupport/devicemanager.cpp \ devicesupport/devicemanagermodel.cpp \ devicesupport/devicefactoryselectiondialog.cpp \ + devicesupport/deviceprocess.cpp \ devicesupport/deviceprocesslist.cpp \ devicesupport/deviceprocessesdialog.cpp \ devicesupport/devicesettingswidget.cpp \ @@ -244,6 +249,7 @@ SOURCES += projectexplorer.cpp \ devicesupport/deviceusedportsgatherer.cpp \ devicesupport/deviceapplicationrunner.cpp \ devicesupport/localprocesslist.cpp \ + devicesupport/sshdeviceprocess.cpp \ devicesupport/sshdeviceprocesslist.cpp \ devicesupport/desktopdeviceconfigurationwidget.cpp \ deployablefile.cpp \ diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs index eeea81b108..0d6985344b 100644 --- a/src/plugins/projectexplorer/projectexplorer.qbs +++ b/src/plugins/projectexplorer/projectexplorer.qbs @@ -265,6 +265,8 @@ QtcPlugin { "devicesupport/devicemanager.h", "devicesupport/devicemanagermodel.cpp", "devicesupport/devicemanagermodel.h", + "devicesupport/deviceprocess.cpp", + "devicesupport/deviceprocess.h", "devicesupport/deviceprocessesdialog.cpp", "devicesupport/deviceprocessesdialog.h", "devicesupport/deviceprocesslist.cpp", @@ -284,8 +286,12 @@ QtcPlugin { "devicesupport/idevicefactory.cpp", "devicesupport/idevicefactory.h", "devicesupport/idevicewidget.h", + "devicesupport/desktopdeviceprocess.cpp", + "devicesupport/desktopdeviceprocess.h", "devicesupport/localprocesslist.cpp", "devicesupport/localprocesslist.h", + "devicesupport/sshdeviceprocess.cpp", + "devicesupport/sshdeviceprocess.h", "devicesupport/sshdeviceprocesslist.cpp", "devicesupport/sshdeviceprocesslist.h", "devicesupport/desktopdeviceconfigurationwidget.cpp", diff --git a/src/plugins/qnx/blackberrydeviceprocesssupport.cpp b/src/plugins/qnx/blackberrydeviceprocesssupport.cpp index bedf0cc618..2acce3e5fe 100644 --- a/src/plugins/qnx/blackberrydeviceprocesssupport.cpp +++ b/src/plugins/qnx/blackberrydeviceprocesssupport.cpp @@ -34,12 +34,23 @@ using namespace Qnx; using namespace Qnx::Internal; -QString BlackBerryDeviceProcessSupport::killProcessByNameCommandLine(const QString &filePath) const +static QString signalProcessByNameCommandLine(const QString &filePath, int signal) { QString executable = filePath; return QString::fromLatin1("for PID in $(pidin -F \"%a %A\" | grep \"%1\" | awk '/%1/ {print $1}'); " "do " - "kill $PID; sleep 1; kill -9 $PID; " - "done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/"))); + "kill -%2 $PID; " + "done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/"))).arg(signal); +} + +QString BlackBerryDeviceProcessSupport::killProcessByNameCommandLine(const QString &filePath) const +{ + return QString::fromLatin1("%1; %2").arg(signalProcessByNameCommandLine(filePath, 15), + signalProcessByNameCommandLine(filePath, 9)); +} + +QString BlackBerryDeviceProcessSupport::interruptProcessByNameCommandLine(const QString &filePath) const +{ + return signalProcessByNameCommandLine(filePath, 2); } diff --git a/src/plugins/qnx/blackberrydeviceprocesssupport.h b/src/plugins/qnx/blackberrydeviceprocesssupport.h index e79f647305..656df0c1f1 100644 --- a/src/plugins/qnx/blackberrydeviceprocesssupport.h +++ b/src/plugins/qnx/blackberrydeviceprocesssupport.h @@ -41,6 +41,7 @@ class BlackBerryDeviceProcessSupport : public RemoteLinux::LinuxDeviceProcessSup { public: QString killProcessByNameCommandLine(const QString &filePath) const; + QString interruptProcessByNameCommandLine(const QString &filePath) const; }; } // namespace Internal diff --git a/src/plugins/qnx/qnxabstractrunsupport.cpp b/src/plugins/qnx/qnxabstractrunsupport.cpp index cb97aa36f9..151dc8f0fd 100644 --- a/src/plugins/qnx/qnxabstractrunsupport.cpp +++ b/src/plugins/qnx/qnxabstractrunsupport.cpp @@ -46,9 +46,10 @@ using namespace Qnx::Internal; QnxAbstractRunSupport::QnxAbstractRunSupport(QnxRunConfiguration *runConfig, QObject *parent) : QObject(parent) , m_remoteExecutable(runConfig->remoteExecutableFilePath()) - , m_commandPrefix(runConfig->commandPrefix()) , m_device(DeviceKitInformation::device(runConfig->target()->kit())) , m_state(Inactive) + , m_environment(runConfig->environment()) + , m_workingDir(runConfig->workingDirectory()) { m_runner = new DeviceApplicationRunner(this); m_portsGatherer = new DeviceUsedPortsGatherer(this); @@ -84,7 +85,7 @@ void QnxAbstractRunSupport::handleRemoteProcessFinished(bool) void QnxAbstractRunSupport::setFinished() { if (m_state != GatheringPorts && m_state != Inactive) - m_runner->stop(m_device->processSupport()->killProcessByNameCommandLine(executable()).toUtf8()); + m_runner->stop(); m_state = Inactive; } @@ -104,11 +105,6 @@ DeviceApplicationRunner *QnxAbstractRunSupport::appRunner() const return m_runner; } -QString QnxAbstractRunSupport::commandPrefix() const -{ - return m_commandPrefix; -} - const IDevice::ConstPtr QnxAbstractRunSupport::device() const { return m_device; diff --git a/src/plugins/qnx/qnxabstractrunsupport.h b/src/plugins/qnx/qnxabstractrunsupport.h index 1e37059774..73650283de 100644 --- a/src/plugins/qnx/qnxabstractrunsupport.h +++ b/src/plugins/qnx/qnxabstractrunsupport.h @@ -31,6 +31,7 @@ #define QNXABSTRACTRUNSUPPORT_H #include +#include #include #include @@ -64,6 +65,8 @@ protected: virtual void startExecution() = 0; virtual QString executable() const; + Utils::Environment environment() const { return m_environment; } + QString workingDirectory() const { return m_workingDir; } void setFinished(); @@ -71,7 +74,6 @@ protected: void setState(State state); ProjectExplorer::DeviceApplicationRunner *appRunner() const; - QString commandPrefix() const; const ProjectExplorer::IDevice::ConstPtr device() const; protected slots: @@ -90,10 +92,11 @@ private: ProjectExplorer::DeviceUsedPortsGatherer * m_portsGatherer; Utils::PortList m_portList; const QString m_remoteExecutable; - const QString m_commandPrefix; ProjectExplorer::IDevice::ConstPtr m_device; ProjectExplorer::DeviceApplicationRunner *m_runner; State m_state; + Utils::Environment m_environment; + QString m_workingDir; }; } // namespace Internal diff --git a/src/plugins/qnx/qnxanalyzesupport.cpp b/src/plugins/qnx/qnxanalyzesupport.cpp index b67d0e6083..fe9caf70bf 100644 --- a/src/plugins/qnx/qnxanalyzesupport.cpp +++ b/src/plugins/qnx/qnxanalyzesupport.cpp @@ -34,6 +34,7 @@ #include #include +#include using namespace ProjectExplorer; @@ -78,10 +79,12 @@ void QnxAnalyzeSupport::startExecution() setState(StartingRemoteProcess); - const QString args = m_runControl->startParameters().debuggeeArgs + - QString::fromLatin1(" -qmljsdebugger=port:%1,block").arg(m_qmlPort); - const QString command = QString::fromLatin1("%1 %2 %3").arg(commandPrefix(), executable(), args); - appRunner()->start(device(), command.toUtf8()); + const QStringList args = QStringList() + << Utils::QtcProcess::splitArgs(m_runControl->startParameters().debuggeeArgs) + << QString::fromLatin1("-qmljsdebugger=port:%1,block").arg(m_qmlPort); + appRunner()->setEnvironment(environment()); + appRunner()->setWorkingDirectory(workingDirectory()); + appRunner()->start(device(), executable(), args); } void QnxAnalyzeSupport::handleRemoteProcessFinished(bool success) diff --git a/src/plugins/qnx/qnxdebugsupport.cpp b/src/plugins/qnx/qnxdebugsupport.cpp index 7b76080fc7..02049fbf62 100644 --- a/src/plugins/qnx/qnxdebugsupport.cpp +++ b/src/plugins/qnx/qnxdebugsupport.cpp @@ -41,6 +41,7 @@ #include #include #include +#include using namespace ProjectExplorer; using namespace RemoteLinux; @@ -91,15 +92,14 @@ void QnxDebugSupport::startExecution() if (m_useQmlDebugger) m_engine->startParameters().processArgs += QString::fromLocal8Bit(" -qmljsdebugger=port:%1,block").arg(m_qmlPort); - QString remoteCommandLine; + QStringList arguments; if (m_useCppDebugger) - remoteCommandLine = QString::fromLatin1("%1 %2 %3") - .arg(commandPrefix(), executable()).arg(m_pdebugPort); + arguments << QString::number(m_pdebugPort); else if (m_useQmlDebugger && !m_useCppDebugger) - remoteCommandLine = QString::fromLatin1("%1 %2 %3") - .arg(commandPrefix(), executable(), m_engine->startParameters().processArgs); - - appRunner()->start(device(), remoteCommandLine.toUtf8()); + arguments = Utils::QtcProcess::splitArgs(m_engine->startParameters().processArgs); + appRunner()->setEnvironment(environment()); + appRunner()->setWorkingDirectory(workingDirectory()); + appRunner()->start(device(), executable(), arguments); } void QnxDebugSupport::handleRemoteProcessStarted() diff --git a/src/plugins/qnx/qnxdeviceconfiguration.cpp b/src/plugins/qnx/qnxdeviceconfiguration.cpp index 5810617fc6..2f9a5a2571 100644 --- a/src/plugins/qnx/qnxdeviceconfiguration.cpp +++ b/src/plugins/qnx/qnxdeviceconfiguration.cpp @@ -42,13 +42,24 @@ using namespace Qnx::Internal; class QnxDeviceProcessSupport : public RemoteLinux::LinuxDeviceProcessSupport { - QString killProcessByNameCommandLine(const QString &filePath) const + static QString signalProcessByNameCommandLine(const QString &filePath, int sig) { QString executable = filePath; return QString::fromLatin1("for PID in $(ps -f -o pid,comm | grep %1 | awk '/%1/ {print $1}'); " "do " - "kill $PID; sleep 1; kill -9 $PID; " - "done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/"))); + "kill -%2 $PID; " + "done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/"))).arg(sig); + } + + QString killProcessByNameCommandLine(const QString &filePath) const + { + return QString::fromLatin1("%1; %2").arg(signalProcessByNameCommandLine(filePath, 15), + signalProcessByNameCommandLine(filePath, 9)); + } + + QString interruptProcessByNameCommandLine(const QString &filePath) const + { + return signalProcessByNameCommandLine(filePath, 2); } }; diff --git a/src/plugins/qnx/qnxrunconfiguration.cpp b/src/plugins/qnx/qnxrunconfiguration.cpp index 277bcfe6e1..2f5f2c3074 100644 --- a/src/plugins/qnx/qnxrunconfiguration.cpp +++ b/src/plugins/qnx/qnxrunconfiguration.cpp @@ -33,6 +33,7 @@ #include "qnxconstants.h" #include +#include #include #include @@ -60,22 +61,14 @@ void QnxRunConfiguration::setQtLibPath(const QString &path) m_qtLibPath = path; } -QString QnxRunConfiguration::environmentPreparationCommand() const +Utils::Environment QnxRunConfiguration::environment() const { - QString command; - const QStringList filesToSource = QStringList() << QLatin1String("/etc/profile") - << QLatin1String("$HOME/.profile"); - foreach (const QString &filePath, filesToSource) - command += QString::fromLatin1("test -f %1 && . %1;").arg(filePath); - if (!workingDirectory().isEmpty()) - command += QLatin1String("cd ") + workingDirectory() + QLatin1Char(';'); - - if (!m_qtLibPath.isEmpty()) - command += QLatin1String("LD_LIBRARY_PATH=") + m_qtLibPath + QLatin1String(":$LD_LIBRARY_PATH"); - else - command.chop(1); // Trailing semicolon. - - return command; + Utils::Environment env = RemoteLinuxRunConfiguration::environment(); + if (!m_qtLibPath.isEmpty()) { + env.appendOrSet(QLatin1String("LD_LIBRARY_PATH"), + m_qtLibPath + QLatin1String(":$LD_LIBRARY_PATH")); + } + return env; } QWidget *QnxRunConfiguration::createConfigurationWidget() diff --git a/src/plugins/qnx/qnxrunconfiguration.h b/src/plugins/qnx/qnxrunconfiguration.h index 7d0f173985..18e67d62de 100644 --- a/src/plugins/qnx/qnxrunconfiguration.h +++ b/src/plugins/qnx/qnxrunconfiguration.h @@ -34,6 +34,8 @@ #include +namespace Utils { class Environment; } + namespace Qnx { namespace Internal { @@ -44,7 +46,7 @@ public: QnxRunConfiguration(ProjectExplorer::Target *parent, const Core::Id id, const QString &projectFilePath); - QString environmentPreparationCommand() const; + Utils::Environment environment() const; QWidget *createConfigurationWidget(); diff --git a/src/plugins/qnx/qnxruncontrolfactory.cpp b/src/plugins/qnx/qnxruncontrolfactory.cpp index 389870502a..f979280ffa 100644 --- a/src/plugins/qnx/qnxruncontrolfactory.cpp +++ b/src/plugins/qnx/qnxruncontrolfactory.cpp @@ -86,7 +86,7 @@ static DebuggerStartParameters createDebuggerStartParameters(const QnxRunConfigu params.displayName = runConfig->displayName(); params.remoteSetupNeeded = true; params.closeMode = KillAtClose; - params.processArgs = runConfig->arguments(); + params.processArgs = runConfig->arguments().join(QLatin1String(" ")); Debugger::DebuggerRunConfigurationAspect *aspect = runConfig->extraAspect(); @@ -127,9 +127,8 @@ static AnalyzerStartParameters createAnalyzerStartParameters(const QnxRunConfigu if (mode == QmlProfilerRunMode) params.startMode = StartLocal; params.debuggee = runConfig->remoteExecutableFilePath(); - params.debuggeeArgs = runConfig->arguments(); + params.debuggeeArgs = runConfig->arguments().join(QLatin1String(" ")); params.connParams = DeviceKitInformation::device(runConfig->target()->kit())->sshParameters(); - params.analyzerCmdPrefix = runConfig->commandPrefix(); params.displayName = runConfig->displayName(); params.sysroot = SysRootKitInformation::sysRoot(runConfig->target()->kit()).toString(); params.analyzerHost = params.connParams.host; diff --git a/src/plugins/remotelinux/abstractremotelinuxrunsupport.cpp b/src/plugins/remotelinux/abstractremotelinuxrunsupport.cpp index 6424cb80c8..b36c8390ed 100644 --- a/src/plugins/remotelinux/abstractremotelinuxrunsupport.cpp +++ b/src/plugins/remotelinux/abstractremotelinuxrunsupport.cpp @@ -34,7 +34,7 @@ #include #include #include - +#include #include using namespace ProjectExplorer; @@ -50,7 +50,8 @@ public: device(DeviceKitInformation::device(runConfig->target()->kit())), remoteFilePath(runConfig->remoteExecutableFilePath()), arguments(runConfig->arguments()), - commandPrefix(runConfig->commandPrefix()) + environment(runConfig->environment()), + workingDir(runConfig->workingDirectory()) { } @@ -60,8 +61,9 @@ public: const ProjectExplorer::IDevice::ConstPtr device; Utils::PortList portList; const QString remoteFilePath; - const QString arguments; - const QString commandPrefix; + const QStringList arguments; + const Utils::Environment environment; + const QString workingDir; }; } // namespace Internal @@ -138,11 +140,8 @@ void AbstractRemoteLinuxRunSupport::setFinished() { if (d->state == Inactive) return; - if (d->state == Running) { - const QString stopCommand - = d->device->processSupport()->killProcessByNameCommandLine(d->remoteFilePath); - d->appRunner.stop(stopCommand.toUtf8()); - } + if (d->state == Running) + d->appRunner.stop(); d->state = Inactive; } @@ -156,19 +155,24 @@ bool AbstractRemoteLinuxRunSupport::setPort(int &port) return true; } -QString AbstractRemoteLinuxRunSupport::arguments() const +QStringList AbstractRemoteLinuxRunSupport::arguments() const { return d->arguments; } -QString AbstractRemoteLinuxRunSupport::commandPrefix() const +QString AbstractRemoteLinuxRunSupport::remoteFilePath() const { - return d->commandPrefix; + return d->remoteFilePath; } -QString AbstractRemoteLinuxRunSupport::remoteFilePath() const +Utils::Environment AbstractRemoteLinuxRunSupport::environment() const { - return d->remoteFilePath; + return d->environment; +} + +QString AbstractRemoteLinuxRunSupport::workingDirectory() const +{ + return d->workingDir; } const IDevice::ConstPtr AbstractRemoteLinuxRunSupport::device() const diff --git a/src/plugins/remotelinux/abstractremotelinuxrunsupport.h b/src/plugins/remotelinux/abstractremotelinuxrunsupport.h index a7a954681b..c052a7a3c3 100644 --- a/src/plugins/remotelinux/abstractremotelinuxrunsupport.h +++ b/src/plugins/remotelinux/abstractremotelinuxrunsupport.h @@ -41,6 +41,8 @@ class DeviceApplicationHelperAction; class DeviceApplicationRunner; } +namespace Utils { class Environment; } + namespace RemoteLinux { class RemoteLinuxRunConfiguration; @@ -79,9 +81,10 @@ protected: void setFinished(); bool setPort(int &port); - QString arguments() const; - QString commandPrefix() const; + QStringList arguments() const; QString remoteFilePath() const; + Utils::Environment environment() const; + QString workingDirectory() const; const ProjectExplorer::IDevice::ConstPtr device() const; void reset(); diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index efb014e5eb..64570c225f 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -30,6 +30,7 @@ #include "linuxdevice.h" #include "genericlinuxdeviceconfigurationwidget.h" +#include "linuxdeviceprocess.h" #include "linuxdevicetester.h" #include "publickeydeploymentdialog.h" #include "remotelinux_constants.h" @@ -122,14 +123,26 @@ QString LinuxDeviceProcessSupport::killProcessByPidCommandLine(int pid) const return QLatin1String("kill -9 ") + QString::number(pid); } -QString LinuxDeviceProcessSupport::killProcessByNameCommandLine(const QString &filePath) const + +static QString signalProcessByNameCommandLine(const QString &filePath, int signal) { return QString::fromLatin1("cd /proc; for pid in `ls -d [0123456789]*`; " "do " "if [ \"`readlink /proc/$pid/exe`\" = \"%1\" ]; then " - " kill $pid; sleep 1; kill -9 $pid; " + " kill %2 $pid;" "fi; " - "done").arg(filePath); + "done").arg(filePath).arg(signal); +} + +QString LinuxDeviceProcessSupport::killProcessByNameCommandLine(const QString &filePath) const +{ + return QString::fromLatin1("%1; %2").arg(signalProcessByNameCommandLine(filePath, 15), + signalProcessByNameCommandLine(filePath, 9)); +} + +QString LinuxDeviceProcessSupport::interruptProcessByNameCommandLine(const QString &filePath) const +{ + return signalProcessByNameCommandLine(filePath, 2); } @@ -244,6 +257,11 @@ DeviceProcessSupport::Ptr LinuxDevice::processSupport() const return DeviceProcessSupport::Ptr(new LinuxDeviceProcessSupport); } +DeviceProcess *LinuxDevice::createProcess(QObject *parent) const +{ + return new LinuxDeviceProcess(sharedFromThis(), parent); +} + bool LinuxDevice::canAutoDetectPorts() const { return true; diff --git a/src/plugins/remotelinux/linuxdevice.h b/src/plugins/remotelinux/linuxdevice.h index 7e8224a475..85946e735e 100644 --- a/src/plugins/remotelinux/linuxdevice.h +++ b/src/plugins/remotelinux/linuxdevice.h @@ -47,6 +47,7 @@ class REMOTELINUX_EXPORT LinuxDeviceProcessSupport : public ProjectExplorer::Dev public: QString killProcessByPidCommandLine(int pid) const; QString killProcessByNameCommandLine(const QString &filePath) const; + QString interruptProcessByNameCommandLine(const QString &filePath) const; }; class REMOTELINUX_EXPORT LinuxDevice : public ProjectExplorer::IDevice @@ -69,6 +70,8 @@ public: ProjectExplorer::IDevice::Ptr clone() const; ProjectExplorer::DeviceProcessSupport::Ptr processSupport() const; + bool canCreateProcess() const { return true; } + ProjectExplorer::DeviceProcess *createProcess(QObject *parent) const; bool canAutoDetectPorts() const; ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const; bool canCreateProcessModel() const { return true; } diff --git a/src/plugins/remotelinux/linuxdeviceprocess.cpp b/src/plugins/remotelinux/linuxdeviceprocess.cpp new file mode 100644 index 0000000000..2dd8e792a7 --- /dev/null +++ b/src/plugins/remotelinux/linuxdeviceprocess.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ +#include "linuxdeviceprocess.h" + +#include +#include + +namespace RemoteLinux { + +static QString quote(const QString &s) { return Utils::QtcProcess::quoteArgUnix(s); } + +LinuxDeviceProcess::LinuxDeviceProcess(const QSharedPointer &device, + QObject *parent) + : ProjectExplorer::SshDeviceProcess(device, parent) +{ + setEnvironment(Utils::Environment(Utils::OsTypeLinux)); +} + +void LinuxDeviceProcess::setRcFilesToSource(const QStringList &filePaths) +{ + m_rcFilesToSource = filePaths; +} + +void LinuxDeviceProcess::setWorkingDirectory(const QString &directory) +{ + m_workingDir = directory; +} + +QString LinuxDeviceProcess::fullCommandLine() const +{ + QString fullCommandLine; + foreach (const QString &filePath, rcFilesToSource()) + fullCommandLine += QString::fromLatin1("test -f %1 && source %1;").arg(filePath); + if (!m_workingDir.isEmpty()) { + fullCommandLine.append(QLatin1String("cd ")).append(quote(m_workingDir)) + .append(QLatin1String(" && ")); + } + const QString envString = environment().toStringList().join(QLatin1String(" ")); + if (!envString.isEmpty()) + fullCommandLine.append(QLatin1Char(' ')).append(envString); + if (!fullCommandLine.isEmpty()) + fullCommandLine += QLatin1Char(' '); + fullCommandLine.append(quote(executable())); + if (!arguments().isEmpty()) { + fullCommandLine.append(QLatin1Char(' ')); + fullCommandLine.append(Utils::QtcProcess::joinArgsUnix(arguments())); + } + return fullCommandLine; +} + +QStringList LinuxDeviceProcess::rcFilesToSource() const +{ + if (!m_rcFilesToSource.isEmpty()) + return m_rcFilesToSource; + return QStringList() << QLatin1String("/etc/profile") << QLatin1String("$HOME/.profile"); +} + +} // namespace RemoteLinux diff --git a/src/plugins/remotelinux/linuxdeviceprocess.h b/src/plugins/remotelinux/linuxdeviceprocess.h new file mode 100644 index 0000000000..c42e1e7c97 --- /dev/null +++ b/src/plugins/remotelinux/linuxdeviceprocess.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QTC_LINUXDEVICEPROCESS_H +#define QTC_LINUXDEVICEPROCESS_H + +#include "remotelinux_export.h" + +#include + +#include + +namespace RemoteLinux { + +class REMOTELINUX_EXPORT LinuxDeviceProcess : public ProjectExplorer::SshDeviceProcess +{ + Q_OBJECT +public: + explicit LinuxDeviceProcess(const QSharedPointer &device, + QObject *parent = 0); + + // Files to source before executing the command (if they exist). Overrides the default. + void setRcFilesToSource(const QStringList &filePaths); + + void setWorkingDirectory(const QString &directory); + +private: + QString fullCommandLine() const; + + QStringList rcFilesToSource() const; + + QStringList m_rcFilesToSource; + QString m_workingDir; +}; + +} // namespace RemoteLinux + +#endif // Include guard. diff --git a/src/plugins/remotelinux/remotelinux.pro b/src/plugins/remotelinux/remotelinux.pro index 62b3061f40..8c4453cabb 100644 --- a/src/plugins/remotelinux/remotelinux.pro +++ b/src/plugins/remotelinux/remotelinux.pro @@ -47,7 +47,8 @@ HEADERS += \ remotelinuxcheckforfreediskspacestep.h \ remotelinuxdeploymentdatamodel.h \ remotelinuxanalyzesupport.h \ - abstractremotelinuxrunsupport.h + abstractremotelinuxrunsupport.h \ + linuxdeviceprocess.h SOURCES += \ embeddedlinuxqtversion.cpp \ @@ -92,7 +93,8 @@ SOURCES += \ remotelinuxcheckforfreediskspacestep.cpp \ remotelinuxdeploymentdatamodel.cpp \ remotelinuxanalyzesupport.cpp \ - abstractremotelinuxrunsupport.cpp + abstractremotelinuxrunsupport.cpp \ + linuxdeviceprocess.cpp FORMS += \ genericlinuxdeviceconfigurationwizardsetuppage.ui \ diff --git a/src/plugins/remotelinux/remotelinux.qbs b/src/plugins/remotelinux/remotelinux.qbs index 15cf5ce44a..cb610ef7a6 100644 --- a/src/plugins/remotelinux/remotelinux.qbs +++ b/src/plugins/remotelinux/remotelinux.qbs @@ -47,6 +47,8 @@ QtcPlugin { "genericremotelinuxdeploystepfactory.h", "linuxdevice.cpp", "linuxdevice.h", + "linuxdeviceprocess.cpp", + "linuxdeviceprocess.h", "linuxdevicetester.cpp", "linuxdevicetester.h", "packageuploader.cpp", diff --git a/src/plugins/remotelinux/remotelinuxanalyzesupport.cpp b/src/plugins/remotelinux/remotelinuxanalyzesupport.cpp index e51b544f87..48680b861f 100644 --- a/src/plugins/remotelinux/remotelinuxanalyzesupport.cpp +++ b/src/plugins/remotelinux/remotelinuxanalyzesupport.cpp @@ -81,7 +81,6 @@ AnalyzerStartParameters RemoteLinuxAnalyzeSupport::startParameters(const RemoteL params.startMode = StartLocal; params.runMode = runMode; params.connParams = DeviceKitInformation::device(runConfig->target()->kit())->sshParameters(); - params.analyzerCmdPrefix = runConfig->commandPrefix(); params.displayName = runConfig->displayName(); params.sysroot = SysRootKitInformation::sysRoot(runConfig->target()->kit()).toString(); params.analyzerHost = params.connParams.host; @@ -140,11 +139,11 @@ void RemoteLinuxAnalyzeSupport::startExecution() connect(runner, SIGNAL(reportProgress(QString)), SLOT(handleProgressReport(QString))); connect(runner, SIGNAL(reportError(QString)), SLOT(handleAppRunnerError(QString))); - const QString args = arguments() - + QString::fromLocal8Bit(" -qmljsdebugger=port:%1,block").arg(d->qmlPort); - const QString remoteCommandLine = - QString::fromLatin1("%1 %2 %3").arg(commandPrefix()).arg(remoteFilePath()).arg(args); - runner->start(device(), remoteCommandLine.toUtf8()); + const QStringList args = arguments() + << QString::fromLocal8Bit("-qmljsdebugger=port:%1,block").arg(d->qmlPort); + runner->setWorkingDirectory(workingDirectory()); + runner->setEnvironment(environment()); + runner->start(device(), remoteFilePath(), args); } void RemoteLinuxAnalyzeSupport::handleAppRunnerError(const QString &error) diff --git a/src/plugins/remotelinux/remotelinuxdebugsupport.cpp b/src/plugins/remotelinux/remotelinuxdebugsupport.cpp index 2ccecb0e5f..8448103e58 100644 --- a/src/plugins/remotelinux/remotelinuxdebugsupport.cpp +++ b/src/plugins/remotelinux/remotelinuxdebugsupport.cpp @@ -98,7 +98,7 @@ DebuggerStartParameters LinuxDeviceDebugSupport::startParameters(const RemoteLin } if (aspect->useCppDebugger()) { params.languages |= CppLanguage; - params.processArgs = runConfig->arguments(); + params.processArgs = runConfig->arguments().join(QLatin1String(" ")); params.startMode = AttachToRemoteServer; params.executable = runConfig->localExecutableFilePath(); params.remoteChannel = device->sshParameters().host + QLatin1String(":-1"); @@ -162,17 +162,23 @@ void LinuxDeviceDebugSupport::startExecution() connect(runner, SIGNAL(remoteStdout(QByteArray)), SLOT(handleRemoteOutput(QByteArray))); if (d->qmlDebugging && !d->cppDebugging) connect(runner, SIGNAL(remoteProcessStarted()), SLOT(handleRemoteProcessStarted())); - QString args = arguments(); + QString command; + QStringList args = arguments(); if (d->qmlDebugging) - args += QString::fromLocal8Bit(" -qmljsdebugger=port:%1,block").arg(d->qmlPort); - const QString remoteCommandLine = (d->qmlDebugging && !d->cppDebugging) - ? QString::fromLatin1("%1 %2 %3").arg(commandPrefix()).arg(remoteFilePath()).arg(args) - : QString::fromLatin1("%1 gdbserver :%2 %3 %4").arg(commandPrefix()) - .arg(d->gdbServerPort).arg(remoteFilePath()).arg(args); + args += QString::fromLocal8Bit("-qmljsdebugger=port:%1,block").arg(d->qmlPort); + if (d->qmlDebugging && !d->cppDebugging) { + command = remoteFilePath(); + } else { + command = QLatin1String("gdbserver"); + args.prepend(remoteFilePath()); + args.prepend(QString::fromLatin1(":%1").arg(d->gdbServerPort)); + } connect(runner, SIGNAL(finished(bool)), SLOT(handleAppRunnerFinished(bool))); connect(runner, SIGNAL(reportProgress(QString)), SLOT(handleProgressReport(QString))); connect(runner, SIGNAL(reportError(QString)), SLOT(handleAppRunnerError(QString))); - runner->start(device(), remoteCommandLine.toUtf8()); + runner->setEnvironment(environment()); + runner->setWorkingDirectory(workingDirectory()); + runner->start(device(), command, args); } void LinuxDeviceDebugSupport::handleAppRunnerError(const QString &error) diff --git a/src/plugins/remotelinux/remotelinuxenvironmentaspectwidget.cpp b/src/plugins/remotelinux/remotelinuxenvironmentaspectwidget.cpp index 69c0b57cc9..222c06f9ac 100644 --- a/src/plugins/remotelinux/remotelinuxenvironmentaspectwidget.cpp +++ b/src/plugins/remotelinux/remotelinuxenvironmentaspectwidget.cpp @@ -70,7 +70,7 @@ void RemoteLinuxEnvironmentAspectWidget::fetchEnvironment() disconnect(button, SIGNAL(clicked()), this, SLOT(fetchEnvironment())); connect(button, SIGNAL(clicked()), this, SLOT(stopFetchEnvironment())); button->setText(tr("Cancel Fetch Operation")); - deviceEnvReader->start(aspect()->runConfiguration()->environmentPreparationCommand()); + deviceEnvReader->start(); } void RemoteLinuxEnvironmentAspectWidget::fetchEnvironmentFinished() diff --git a/src/plugins/remotelinux/remotelinuxenvironmentreader.cpp b/src/plugins/remotelinux/remotelinuxenvironmentreader.cpp index 2f3d8161cd..2ffa21bdac 100644 --- a/src/plugins/remotelinux/remotelinuxenvironmentreader.cpp +++ b/src/plugins/remotelinux/remotelinuxenvironmentreader.cpp @@ -28,7 +28,7 @@ ****************************************************************************/ #include "remotelinuxenvironmentreader.h" -#include +#include #include #include #include @@ -44,79 +44,67 @@ RemoteLinuxEnvironmentReader::RemoteLinuxEnvironmentReader(RunConfiguration *con , m_stop(false) , m_env(Utils::OsTypeLinux) , m_kit(config->target()->kit()) - , m_remoteProcessRunner(0) + , m_deviceProcess(0) { connect(config->target(), SIGNAL(kitChanged()), this, SLOT(handleCurrentDeviceConfigChanged())); } -void RemoteLinuxEnvironmentReader::start(const QString &environmentSetupCommand) +void RemoteLinuxEnvironmentReader::start() { IDevice::ConstPtr device = DeviceKitInformation::device(m_kit); if (!device) return; m_stop = false; - if (!m_remoteProcessRunner) - m_remoteProcessRunner = new QSsh::SshRemoteProcessRunner(this); - connect(m_remoteProcessRunner, SIGNAL(connectionError()), SLOT(handleConnectionFailure())); - connect(m_remoteProcessRunner, SIGNAL(processClosed(int)), SLOT(remoteProcessFinished(int))); - const QByteArray remoteCall - = QString(environmentSetupCommand + QLatin1String("; env")).toUtf8(); - m_remoteProcessRunner->run(remoteCall, device->sshParameters()); + m_deviceProcess = device->createProcess(this); + connect(m_deviceProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(handleError())); + connect(m_deviceProcess, SIGNAL(finished()), SLOT(remoteProcessFinished())); + m_deviceProcess->start(QLatin1String("env")); } void RemoteLinuxEnvironmentReader::stop() { m_stop = true; - if (m_remoteProcessRunner) - disconnect(m_remoteProcessRunner, 0, this, 0); + destroyProcess(); } -void RemoteLinuxEnvironmentReader::handleConnectionFailure() +void RemoteLinuxEnvironmentReader::handleError() { if (m_stop) return; - disconnect(m_remoteProcessRunner, 0, this, 0); - emit error(tr("Connection error: %1").arg(m_remoteProcessRunner->lastConnectionErrorString())); - emit finished(); + emit error(tr("Error: %1").arg(m_deviceProcess->errorString())); + setFinished(); } void RemoteLinuxEnvironmentReader::handleCurrentDeviceConfigChanged() { - if (m_remoteProcessRunner) - disconnect(m_remoteProcessRunner, 0, this, 0); m_env.clear(); setFinished(); } -void RemoteLinuxEnvironmentReader::remoteProcessFinished(int exitStatus) +void RemoteLinuxEnvironmentReader::remoteProcessFinished() { - Q_ASSERT(exitStatus == QSsh::SshRemoteProcess::FailedToStart - || exitStatus == QSsh::SshRemoteProcess::CrashExit - || exitStatus == QSsh::SshRemoteProcess::NormalExit); - if (m_stop) return; - disconnect(m_remoteProcessRunner, 0, this, 0); m_env.clear(); QString errorMessage; - if (exitStatus != QSsh::SshRemoteProcess::NormalExit) { - errorMessage = m_remoteProcessRunner->processErrorString(); - } else if (m_remoteProcessRunner->processExitCode() != 0) { + if (m_deviceProcess->exitStatus() != QProcess::NormalExit) { + errorMessage = m_deviceProcess->errorString(); + } else if (m_deviceProcess->exitCode() != 0) { errorMessage = tr("Process exited with code %1.") - .arg(m_remoteProcessRunner->processExitCode()); + .arg(m_deviceProcess->exitCode()); } if (!errorMessage.isEmpty()) { errorMessage = tr("Error running 'env': %1").arg(errorMessage); const QString remoteStderr - = QString::fromUtf8(m_remoteProcessRunner->readAllStandardError()).trimmed(); + = QString::fromUtf8(m_deviceProcess->readAllStandardError()).trimmed(); if (!remoteStderr.isEmpty()) errorMessage += tr("\nRemote stderr was: '%1'").arg(remoteStderr); emit error(errorMessage); } else { - QString remoteOutput = QString::fromUtf8(m_remoteProcessRunner->readAllStandardOutput()); + QString remoteOutput = QString::fromUtf8(m_deviceProcess->readAllStandardOutput()); if (!remoteOutput.isEmpty()) { m_env = Utils::Environment(remoteOutput.split(QLatin1Char('\n'), QString::SkipEmptyParts), Utils::OsTypeLinux); @@ -131,5 +119,16 @@ void RemoteLinuxEnvironmentReader::setFinished() emit finished(); } +void RemoteLinuxEnvironmentReader::destroyProcess() +{ + if (!m_deviceProcess) + return; + m_deviceProcess->disconnect(this); + if (m_deviceProcess->state() != QProcess::NotRunning) + m_deviceProcess->terminate(); + m_deviceProcess->deleteLater(); + m_deviceProcess = 0; +} + } // namespace Internal } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/remotelinuxenvironmentreader.h b/src/plugins/remotelinux/remotelinuxenvironmentreader.h index e495440af5..f8cb02f73f 100644 --- a/src/plugins/remotelinux/remotelinuxenvironmentreader.h +++ b/src/plugins/remotelinux/remotelinuxenvironmentreader.h @@ -35,12 +35,11 @@ #include namespace ProjectExplorer { -class RunConfiguration; +class DeviceProcess; class Kit; +class RunConfiguration; } -namespace QSsh { class SshRemoteProcessRunner; } - namespace RemoteLinux { namespace Internal { @@ -51,7 +50,7 @@ class RemoteLinuxEnvironmentReader : public QObject public: RemoteLinuxEnvironmentReader(ProjectExplorer::RunConfiguration *config, QObject *parent = 0); - void start(const QString &environmentSetupCommand); + void start(); void stop(); Utils::Environment remoteEnvironment() const { return m_env; } @@ -61,18 +60,18 @@ signals: void error(const QString &error); private slots: - void handleConnectionFailure(); + void handleError(); void handleCurrentDeviceConfigChanged(); - - void remoteProcessFinished(int exitStatus); + void remoteProcessFinished(); private: void setFinished(); + void destroyProcess(); bool m_stop; Utils::Environment m_env; ProjectExplorer::Kit *m_kit; - QSsh::SshRemoteProcessRunner *m_remoteProcessRunner; + ProjectExplorer::DeviceProcess *m_deviceProcess; }; } // namespace Internal diff --git a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp index d248e22e79..5b872ecde7 100644 --- a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp @@ -38,6 +38,7 @@ #include #include #include +#include using namespace ProjectExplorer; using namespace Utils; @@ -73,7 +74,7 @@ public: QString projectFilePath; QString gdbPath; - QString arguments; + QStringList arguments; QString disabledReason; bool useAlternateRemoteExecutable; QString alternateRemoteExecutable; @@ -142,6 +143,7 @@ OutputFormatter *RemoteLinuxRunConfiguration::createOutputFormatter() const return new QtSupport::QtOutputFormatter(target()->project()); } + QVariantMap RemoteLinuxRunConfiguration::toMap() const { QVariantMap map(RunConfiguration::toMap()); @@ -159,7 +161,7 @@ bool RemoteLinuxRunConfiguration::fromMap(const QVariantMap &map) if (!RunConfiguration::fromMap(map)) return false; - d->arguments = map.value(QLatin1String(ArgumentsKey)).toString(); + d->arguments = map.value(QLatin1String(ArgumentsKey)).toStringList(); const QDir dir = QDir(target()->project()->projectDirectory()); d->projectFilePath = QDir::cleanPath(dir.filePath(map.value(QLatin1String(ProFileKey)).toString())); @@ -181,31 +183,21 @@ QString RemoteLinuxRunConfiguration::defaultDisplayName() return tr("Run on Remote Device"); } -QString RemoteLinuxRunConfiguration::arguments() const +QStringList RemoteLinuxRunConfiguration::arguments() const { return d->arguments; } -QString RemoteLinuxRunConfiguration::environmentPreparationCommand() const -{ - QString command; - const QStringList filesToSource = QStringList() << QLatin1String("/etc/profile") - << QLatin1String("$HOME/.profile"); - foreach (const QString &filePath, filesToSource) - command += QString::fromLatin1("test -f %1 && source %1;").arg(filePath); - if (!workingDirectory().isEmpty()) - command += QLatin1String("cd ") + workingDirectory(); - else - command.chop(1); // Trailing semicolon. - return command; -} - -QString RemoteLinuxRunConfiguration::commandPrefix() const +Environment RemoteLinuxRunConfiguration::environment() const { RemoteLinuxEnvironmentAspect *aspect = extraAspect(); - QTC_ASSERT(aspect, return QString()); - return QString::fromLatin1("%1; DISPLAY=:0.0 %2") - .arg(environmentPreparationCommand(), aspect->userEnvironmentChangesAsString()); + QTC_ASSERT(aspect, return Environment()); + Environment env(OsTypeLinux); + env.modify(aspect->userEnvironmentChanges()); + const QString displayKey = QLatin1String("DISPLAY"); + if (!env.hasKey(displayKey)) + env.appendOrSet(displayKey, QLatin1String(":0.0")); + return env; } QString RemoteLinuxRunConfiguration::localExecutableFilePath() const @@ -228,7 +220,7 @@ QString RemoteLinuxRunConfiguration::remoteExecutableFilePath() const void RemoteLinuxRunConfiguration::setArguments(const QString &args) { - d->arguments = args; + d->arguments = QtcProcess::splitArgs(args); // TODO: Widget should be list-based. } QString RemoteLinuxRunConfiguration::workingDirectory() const diff --git a/src/plugins/remotelinux/remotelinuxrunconfiguration.h b/src/plugins/remotelinux/remotelinuxrunconfiguration.h index 1419164b62..af2fdca6df 100644 --- a/src/plugins/remotelinux/remotelinuxrunconfiguration.h +++ b/src/plugins/remotelinux/remotelinuxrunconfiguration.h @@ -34,7 +34,12 @@ #include -namespace Utils { class PortList; } +#include + +namespace Utils { +class Environment; +class PortList; +} namespace RemoteLinux { class RemoteLinuxRunConfigurationWidget; @@ -62,13 +67,12 @@ public: QWidget *createConfigurationWidget(); Utils::OutputFormatter *createOutputFormatter() const; - virtual QString environmentPreparationCommand() const; - virtual QString commandPrefix() const; + virtual Utils::Environment environment() const; QString localExecutableFilePath() const; QString defaultRemoteExecutableFilePath() const; QString remoteExecutableFilePath() const; - QString arguments() const; + QStringList arguments() const; void setArguments(const QString &args); QString workingDirectory() const; void setWorkingDirectory(const QString &wd); diff --git a/src/plugins/remotelinux/remotelinuxrunconfigurationwidget.cpp b/src/plugins/remotelinux/remotelinuxrunconfigurationwidget.cpp index 2b744daa2e..964715838e 100644 --- a/src/plugins/remotelinux/remotelinuxrunconfigurationwidget.cpp +++ b/src/plugins/remotelinux/remotelinuxrunconfigurationwidget.cpp @@ -148,7 +148,7 @@ void RemoteLinuxRunConfigurationWidget::addGenericWidgets(QVBoxLayout *mainLayou altRemoteExeLayout->addWidget(&d->useAlternateCommandBox); d->genericWidgetsLayout.addRow(tr("Alternate executable on device:"), altRemoteExeWidget); - d->argsLineEdit.setText(d->runConfiguration->arguments()); + d->argsLineEdit.setText(d->runConfiguration->arguments().join(QLatin1String(" "))); d->genericWidgetsLayout.addRow(tr("Arguments:"), &d->argsLineEdit); d->workingDirLineEdit.setPlaceholderText(tr("")); diff --git a/src/plugins/remotelinux/remotelinuxruncontrol.cpp b/src/plugins/remotelinux/remotelinuxruncontrol.cpp index 93b3f69ec5..1d9e281315 100644 --- a/src/plugins/remotelinux/remotelinuxruncontrol.cpp +++ b/src/plugins/remotelinux/remotelinuxruncontrol.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -49,8 +50,9 @@ public: ProjectExplorer::DeviceApplicationRunner runner; IDevice::ConstPtr device; QString remoteExecutable; - QString arguments; - QString prefix; + QStringList arguments; + Utils::Environment environment; + QString workingDir; }; RemoteLinuxRunControl::RemoteLinuxRunControl(RunConfiguration *rc) @@ -61,7 +63,8 @@ RemoteLinuxRunControl::RemoteLinuxRunControl(RunConfiguration *rc) const RemoteLinuxRunConfiguration * const lrc = qobject_cast(rc); d->remoteExecutable = lrc->remoteExecutableFilePath(); d->arguments = lrc->arguments(); - d->prefix = lrc->commandPrefix(); + d->environment = lrc->environment(); + d->workingDir = lrc->workingDirectory(); } RemoteLinuxRunControl::~RemoteLinuxRunControl() @@ -80,16 +83,14 @@ void RemoteLinuxRunControl::start() connect(&d->runner, SIGNAL(remoteStdout(QByteArray)), SLOT(handleRemoteOutput(QByteArray))); connect(&d->runner, SIGNAL(finished(bool)), SLOT(handleRunnerFinished())); connect(&d->runner, SIGNAL(reportProgress(QString)), SLOT(handleProgressReport(QString))); - const QString commandLine = QString::fromLatin1("%1 %2 %3") - .arg(d->prefix, d->remoteExecutable, d->arguments); - d->runner.start(d->device, commandLine.toUtf8()); + d->runner.setEnvironment(d->environment); + d->runner.setWorkingDirectory(d->workingDir); + d->runner.start(d->device, d->remoteExecutable, d->arguments); } RunControl::StopResult RemoteLinuxRunControl::stop() { - const QString stopCommandLine - = d->device->processSupport()->killProcessByNameCommandLine(d->remoteExecutable); - d->runner.stop(stopCommandLine.toUtf8()); + d->runner.stop(); return AsynchronousStop; } diff --git a/src/plugins/valgrind/valgrindruncontrolfactory.cpp b/src/plugins/valgrind/valgrindruncontrolfactory.cpp index 0e087c5204..67b31438eb 100644 --- a/src/plugins/valgrind/valgrindruncontrolfactory.cpp +++ b/src/plugins/valgrind/valgrindruncontrolfactory.cpp @@ -99,8 +99,7 @@ RunControl *ValgrindRunControlFactory::create(RunConfiguration *runConfiguration sp.startMode = StartRemote; sp.debuggee = rc2->remoteExecutableFilePath(); sp.connParams = DeviceKitInformation::device(rc2->target()->kit())->sshParameters(); - sp.analyzerCmdPrefix = rc2->commandPrefix(); - sp.debuggeeArgs = rc2->arguments(); + sp.debuggeeArgs = rc2->arguments().join(QLatin1String(" ")); } else { QTC_ASSERT(false, return 0); } -- cgit v1.2.1