diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2018-12-14 15:41:59 +0100 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2018-12-17 13:42:25 +0000 |
commit | 1dfa7279d4ba359bfc5b3c09094aec6c23efff14 (patch) | |
tree | 91725a55f39ec451513d04aa7f5f22011cf40078 /src | |
parent | e6a71fb43d6beb533bacb3dc99c5322efa17c98e (diff) | |
download | qt-creator-1dfa7279d4ba359bfc5b3c09094aec6c23efff14.tar.gz |
RemoteLinux: Add an rsync deploy step
Using rsync enables proper incremental deployment and is particularly
helpful when larger files are involved.
We check whether rsync works as part of the device test. If it does, it
becomes the default deploy step, otherwise we fall back to SFTP.
Change-Id: I6ab938ccd5acd7e0cbe07b90b6938dccad19bba5
Reviewed-by: hjk <hjk@qt.io>
Diffstat (limited to 'src')
19 files changed, 421 insertions, 15 deletions
diff --git a/src/libs/ssh/sshconnection.cpp b/src/libs/ssh/sshconnection.cpp index 17108b91cc..c8b8e23322 100644 --- a/src/libs/ssh/sshconnection.cpp +++ b/src/libs/ssh/sshconnection.cpp @@ -111,7 +111,7 @@ struct SshConnection::SshConnectionPrivate return masterSocketDir->path() + "/control_socket"; } - QStringList connectionArgs() const + QStringList connectionOptions() const { QString hostKeyCheckingString; switch (connParams.hostKeyCheckingMode) { @@ -137,7 +137,12 @@ struct SshConnection::SshConnectionPrivate args << "-o" << ("ControlPath=" + socketFilePath()); if (connParams.timeout != 0) args << "-o" << ("ConnectTimeout=" + QString::number(connParams.timeout)); - return args << connParams.host(); + return args; + } + + QStringList connectionArgs() const + { + return connectionOptions() << connParams.host(); } SshConnectionParameters connParams; @@ -289,6 +294,11 @@ SshConnectionInfo SshConnection::connectionInfo() const return d->connInfo; } +QStringList SshConnection::connectionOptions() const +{ + return d->connectionOptions(); +} + bool SshConnection::sharingEnabled() const { return d->sharingEnabled; diff --git a/src/libs/ssh/sshconnection.h b/src/libs/ssh/sshconnection.h index 09151795d0..1b5775682e 100644 --- a/src/libs/ssh/sshconnection.h +++ b/src/libs/ssh/sshconnection.h @@ -107,6 +107,7 @@ public: QString errorString() const; SshConnectionParameters connectionParameters() const; SshConnectionInfo connectionInfo() const; + QStringList connectionOptions() const; bool sharingEnabled() const; ~SshConnection(); diff --git a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp index e837d5121e..4cd0f8a490 100644 --- a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp @@ -256,7 +256,7 @@ void DeviceSettingsWidget::testDevice() { const IDevice::ConstPtr &device = currentDevice(); QTC_ASSERT(device && device->hasDeviceTester(), return); - DeviceTestDialog dlg(device, this); + DeviceTestDialog dlg(m_deviceManager->mutableDevice(device->id()), this); dlg.exec(); } diff --git a/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp b/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp index e308b76f46..5c60e5e70b 100644 --- a/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp @@ -48,7 +48,7 @@ public: bool finished; }; -DeviceTestDialog::DeviceTestDialog(const IDevice::ConstPtr &deviceConfiguration, +DeviceTestDialog::DeviceTestDialog(const IDevice::Ptr &deviceConfiguration, QWidget *parent) : QDialog(parent) , d(std::make_unique<DeviceTestDialogPrivate>(deviceConfiguration->createDeviceTester())) diff --git a/src/plugins/projectexplorer/devicesupport/devicetestdialog.h b/src/plugins/projectexplorer/devicesupport/devicetestdialog.h index 7263234fbe..d7a25c1dcd 100644 --- a/src/plugins/projectexplorer/devicesupport/devicetestdialog.h +++ b/src/plugins/projectexplorer/devicesupport/devicetestdialog.h @@ -39,7 +39,7 @@ class DeviceTestDialog : public QDialog Q_OBJECT public: - DeviceTestDialog(const IDevice::ConstPtr &deviceConfiguration, QWidget *parent = nullptr); + DeviceTestDialog(const IDevice::Ptr &deviceConfiguration, QWidget *parent = nullptr); ~DeviceTestDialog() override; void reject() override; diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index 0487954312..4d17ff933d 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -112,6 +112,7 @@ const char IdKey[] = "InternalId"; const char OriginKey[] = "Origin"; const char MachineTypeKey[] = "Type"; const char VersionKey[] = "Version"; +const char ExtraDataKey[] = "ExtraData"; // Connection const char HostKey[] = "Host"; @@ -152,6 +153,7 @@ public: QString qmlsceneCommand; QList<Utils::Icon> deviceIcons; + QVariantHash extraData; }; } // namespace Internal @@ -347,6 +349,7 @@ void IDevice::fromMap(const QVariantMap &map) d->debugServerPath = map.value(QLatin1String(DebugServerKey)).toString(); d->qmlsceneCommand = map.value(QLatin1String(QmlsceneKey)).toString(); + d->extraData = map.value(ExtraDataKey).toHash(); } /*! @@ -377,6 +380,7 @@ QVariantMap IDevice::toMap() const map.insert(QLatin1String(DebugServerKey), d->debugServerPath); map.insert(QLatin1String(QmlsceneKey), d->qmlsceneCommand); + map.insert(ExtraDataKey, d->extraData); return map; } @@ -446,6 +450,16 @@ void IDevice::setQmlsceneCommand(const QString &path) d->qmlsceneCommand = path; } +void IDevice::setExtraData(Core::Id kind, const QVariant &data) +{ + d->extraData.insert(kind.toString(), data); +} + +QVariant IDevice::extraData(Core::Id kind) const +{ + return d->extraData.value(kind.toString()); +} + int IDevice::version() const { return d->version; diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 4d3a2f8302..f6cfaef574 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -206,6 +206,9 @@ public: QString qmlsceneCommand() const; void setQmlsceneCommand(const QString &path); + void setExtraData(Core::Id kind, const QVariant &data); + QVariant extraData(Core::Id kind) const; + protected: IDevice(); IDevice(Core::Id type, Origin origin, MachineType machineType, Core::Id id = Core::Id()); @@ -226,7 +229,7 @@ class PROJECTEXPLORER_EXPORT DeviceTester : public QObject public: enum TestResult { TestSuccess, TestFailure }; - virtual void testDevice(const ProjectExplorer::IDevice::ConstPtr &deviceConfiguration) = 0; + virtual void testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration) = 0; virtual void stopTest() = 0; signals: diff --git a/src/plugins/qnx/qnxdevicetester.cpp b/src/plugins/qnx/qnxdevicetester.cpp index 004de59acb..cf5614e817 100644 --- a/src/plugins/qnx/qnxdevicetester.cpp +++ b/src/plugins/qnx/qnxdevicetester.cpp @@ -66,7 +66,7 @@ QnxDeviceTester::QnxDeviceTester(QObject *parent) << QLatin1String("uname"); } -void QnxDeviceTester::testDevice(const ProjectExplorer::IDevice::ConstPtr &deviceConfiguration) +void QnxDeviceTester::testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration) { QTC_ASSERT(m_state == Inactive, return); diff --git a/src/plugins/qnx/qnxdevicetester.h b/src/plugins/qnx/qnxdevicetester.h index 91d5548bda..c63ec13d75 100644 --- a/src/plugins/qnx/qnxdevicetester.h +++ b/src/plugins/qnx/qnxdevicetester.h @@ -40,7 +40,7 @@ class QnxDeviceTester : public ProjectExplorer::DeviceTester public: explicit QnxDeviceTester(QObject *parent = nullptr); - void testDevice(const ProjectExplorer::IDevice::ConstPtr &deviceConfiguration) override; + void testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration) override; void stopTest() override; private slots: diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index 3b62402a80..426a12e04c 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -286,4 +286,14 @@ DeviceEnvironmentFetcher::Ptr LinuxDevice::environmentFetcher() const return DeviceEnvironmentFetcher::Ptr(new LinuxDeviceEnvironmentFetcher(sharedFromThis())); } +void LinuxDevice::setSupportsRsync(bool supportsRsync) +{ + setExtraData("RemoteLinux.SupportsRSync", supportsRsync); +} + +bool LinuxDevice::supportsRSync() const +{ + return extraData("RemoteLinux.SupportsRSync").toBool(); +} + } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/linuxdevice.h b/src/plugins/remotelinux/linuxdevice.h index 210bb6177f..919ccb092e 100644 --- a/src/plugins/remotelinux/linuxdevice.h +++ b/src/plugins/remotelinux/linuxdevice.h @@ -65,6 +65,9 @@ public: ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOperation() const override; ProjectExplorer::DeviceEnvironmentFetcher::Ptr environmentFetcher() const override; + void setSupportsRsync(bool supportsRsync); + bool supportsRSync() const; + protected: LinuxDevice() = default; LinuxDevice(const QString &name, Core::Id type, diff --git a/src/plugins/remotelinux/linuxdevicetester.cpp b/src/plugins/remotelinux/linuxdevicetester.cpp index 4ef601374c..b6a470ab07 100644 --- a/src/plugins/remotelinux/linuxdevicetester.cpp +++ b/src/plugins/remotelinux/linuxdevicetester.cpp @@ -25,6 +25,9 @@ #include "linuxdevicetester.h" +#include "linuxdevice.h" +#include "rsyncdeploystep.h" + #include <projectexplorer/devicesupport/deviceusedportsgatherer.h> #include <utils/port.h> #include <utils/qtcassert.h> @@ -40,19 +43,21 @@ namespace RemoteLinux { namespace Internal { namespace { -enum State { Inactive, Connecting, RunningUname, TestingPorts, TestingSftp }; +enum State { Inactive, Connecting, RunningUname, TestingPorts, TestingSftp, TestingRsync }; } // anonymous namespace class GenericLinuxDeviceTesterPrivate { public: - IDevice::ConstPtr deviceConfiguration; + IDevice::Ptr deviceConfiguration; SshConnection *connection = nullptr; SshRemoteProcessPtr process; DeviceUsedPortsGatherer portsGatherer; SftpTransferPtr sftpUpload; + QProcess rsyncProcess; State state = Inactive; + bool sftpWorks = false; }; } // namespace Internal @@ -71,7 +76,7 @@ GenericLinuxDeviceTester::~GenericLinuxDeviceTester() delete d; } -void GenericLinuxDeviceTester::testDevice(const IDevice::ConstPtr &deviceConfiguration) +void GenericLinuxDeviceTester::testDevice(const IDevice::Ptr &deviceConfiguration) { QTC_ASSERT(d->state == Inactive, return); @@ -105,6 +110,9 @@ void GenericLinuxDeviceTester::stopTest() case TestingSftp: d->sftpUpload->stop(); break; + case TestingRsync: + d->rsyncProcess.disconnect(); + d->rsyncProcess.kill(); case Inactive: break; } @@ -195,11 +203,52 @@ void GenericLinuxDeviceTester::handleSftpFinished(const QString &error) QTC_ASSERT(d->state == TestingSftp, return); if (!error.isEmpty()) { emit errorMessage(tr("Error setting up SFTP connection: %1\n").arg(error)); - setFinished(TestFailure); + d->sftpWorks = false; } else { emit progressMessage(tr("SFTP service available.\n")); - setFinished(TestSuccess); + d->sftpWorks = true; + } + + emit progressMessage(tr("Checking whether rsync works...")); + connect(&d->rsyncProcess, &QProcess::errorOccurred, [this] { + if (d->rsyncProcess.error() == QProcess::FailedToStart) + handleRsyncFinished(); + }); + connect(&d->rsyncProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished), + [this] { + handleRsyncFinished(); + }); + const RsyncCommandLine cmdLine = RsyncDeployStep::rsyncCommand(*d->connection); + const QStringList args = QStringList(cmdLine.options) + << "-n" << "--exclude=*" << (cmdLine.remoteHostSpec + ":/tmp"); + d->rsyncProcess.start("rsync", args); +} + +void GenericLinuxDeviceTester::handleRsyncFinished() +{ + QString error; + if (d->rsyncProcess.error() == QProcess::FailedToStart) { + error = tr("Failed to start rsync: %1\n").arg(d->rsyncProcess.errorString()); + } else if (d->rsyncProcess.exitStatus() == QProcess::CrashExit) { + error = tr("rsync crashed.\n"); + } else if (d->rsyncProcess.exitCode() != 0) { + error = tr("rsync failed with exit code %1: %2\n") + .arg(d->rsyncProcess.exitCode()) + .arg(QString::fromLocal8Bit(d->rsyncProcess.readAllStandardError())); } + TestResult result = TestSuccess; + if (!error.isEmpty()) { + emit errorMessage(error); + if (!d->sftpWorks) { + emit errorMessage(tr("Deployment to this device will not work out of the box.\n")); + result = TestFailure; + } + } else { + emit progressMessage(tr("rsync is functional.\n")); + } + + d->deviceConfiguration.staticCast<LinuxDevice>()->setSupportsRsync(error.isEmpty()); + setFinished(result); } void GenericLinuxDeviceTester::setFinished(TestResult result) diff --git a/src/plugins/remotelinux/linuxdevicetester.h b/src/plugins/remotelinux/linuxdevicetester.h index a63ec51d04..9aefdda33f 100644 --- a/src/plugins/remotelinux/linuxdevicetester.h +++ b/src/plugins/remotelinux/linuxdevicetester.h @@ -41,7 +41,7 @@ public: explicit GenericLinuxDeviceTester(QObject *parent = nullptr); ~GenericLinuxDeviceTester() override; - void testDevice(const ProjectExplorer::IDevice::ConstPtr &deviceConfiguration) override; + void testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration) override; void stopTest() override; private: @@ -51,6 +51,7 @@ private: void handlePortsGatheringError(const QString &message); void handlePortListReady(); void handleSftpFinished(const QString &error); + void handleRsyncFinished(); void setFinished(ProjectExplorer::DeviceTester::TestResult result); Internal::GenericLinuxDeviceTesterPrivate * const d; diff --git a/src/plugins/remotelinux/remotelinux.pro b/src/plugins/remotelinux/remotelinux.pro index a5aa037ef1..027cc59b22 100644 --- a/src/plugins/remotelinux/remotelinux.pro +++ b/src/plugins/remotelinux/remotelinux.pro @@ -40,6 +40,7 @@ HEADERS += \ remotelinuxkillappservice.h \ remotelinuxkillappstep.h \ remotelinuxqmltoolingsupport.h \ + rsyncdeploystep.h \ linuxdeviceprocess.h \ remotelinuxcustomrunconfiguration.h \ remotelinuxsignaloperation.h \ @@ -82,6 +83,7 @@ SOURCES += \ remotelinuxkillappservice.cpp \ remotelinuxkillappstep.cpp \ remotelinuxqmltoolingsupport.cpp \ + rsyncdeploystep.cpp \ linuxdeviceprocess.cpp \ remotelinuxcustomrunconfiguration.cpp \ remotelinuxsignaloperation.cpp \ diff --git a/src/plugins/remotelinux/remotelinux.qbs b/src/plugins/remotelinux/remotelinux.qbs index 7d85832215..1f553d662b 100644 --- a/src/plugins/remotelinux/remotelinux.qbs +++ b/src/plugins/remotelinux/remotelinux.qbs @@ -95,6 +95,8 @@ Project { "remotelinuxsignaloperation.h", "remotelinuxx11forwardingaspect.cpp", "remotelinuxx11forwardingaspect.h", + "rsyncdeploystep.cpp", + "rsyncdeploystep.h", "sshkeydeployer.cpp", "sshkeydeployer.h", "tarpackagecreationstep.cpp", diff --git a/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp b/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp index c9a778f4c0..c452bfc374 100644 --- a/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp @@ -26,12 +26,15 @@ #include "remotelinuxdeployconfiguration.h" #include "genericdirectuploadstep.h" +#include "linuxdevice.h" #include "remotelinuxcheckforfreediskspacestep.h" #include "remotelinuxkillappstep.h" #include "remotelinux_constants.h" +#include "rsyncdeploystep.h" #include <projectexplorer/abi.h> #include <projectexplorer/deploymentdataview.h> +#include <projectexplorer/kitinformation.h> #include <projectexplorer/project.h> #include <projectexplorer/target.h> @@ -51,7 +54,12 @@ void RemoteLinuxDeployConfiguration::initialize() { stepList()->appendStep(new RemoteLinuxCheckForFreeDiskSpaceStep(stepList())); stepList()->appendStep(new RemoteLinuxKillAppStep(stepList())); - stepList()->appendStep(new GenericDirectUploadStep(stepList())); + const LinuxDevice::ConstPtr device = DeviceKitInformation::device(target()->kit()) + .staticCast<const LinuxDevice>(); + if (device && device->supportsRSync()) + stepList()->appendStep(new RsyncDeployStep(stepList())); + else + stepList()->appendStep(new GenericDirectUploadStep(stepList())); } NamedWidget *RemoteLinuxDeployConfiguration::createConfigWidget() diff --git a/src/plugins/remotelinux/remotelinuxplugin.cpp b/src/plugins/remotelinux/remotelinuxplugin.cpp index 6a5dffa747..f0f9b7777d 100644 --- a/src/plugins/remotelinux/remotelinuxplugin.cpp +++ b/src/plugins/remotelinux/remotelinuxplugin.cpp @@ -39,6 +39,7 @@ #include "remotelinuxdeployconfiguration.h" #include "remotelinuxcustomcommanddeploymentstep.h" #include "remotelinuxkillappstep.h" +#include "rsyncdeploystep.h" #include "tarpackagecreationstep.h" #include "uploadandinstalltarpackagestep.h" @@ -73,6 +74,7 @@ public: GenericDeployStepFactory<TarPackageCreationStep> tarPackageCreationStepFactory; GenericDeployStepFactory<UploadAndInstallTarPackageStep> uploadAndInstallTarPackageStepFactory; GenericDeployStepFactory<GenericDirectUploadStep> genericDirectUploadStepFactory; + GenericDeployStepFactory<RsyncDeployStep> rsyncDeployStepFactory; GenericDeployStepFactory<RemoteLinuxCustomCommandDeploymentStep> customCommandDeploymentStepFactory; GenericDeployStepFactory<RemoteLinuxCheckForFreeDiskSpaceStep> diff --git a/src/plugins/remotelinux/rsyncdeploystep.cpp b/src/plugins/remotelinux/rsyncdeploystep.cpp new file mode 100644 index 0000000000..eca82f765c --- /dev/null +++ b/src/plugins/remotelinux/rsyncdeploystep.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "rsyncdeploystep.h" + +#include "abstractremotelinuxdeployservice.h" + +#include <projectexplorer/deploymentdata.h> +#include <projectexplorer/runconfigurationaspects.h> +#include <projectexplorer/target.h> +#include <ssh/sshconnection.h> +#include <ssh/sshremoteprocess.h> +#include <ssh/sshsettings.h> +#include <utils/algorithm.h> +#include <utils/qtcprocess.h> + +using namespace ProjectExplorer; +using namespace QSsh; +using namespace Utils; + +namespace RemoteLinux { +namespace Internal { + +class RsyncDeployService : public AbstractRemoteLinuxDeployService +{ + Q_OBJECT +public: + RsyncDeployService(QObject *parent = nullptr) : AbstractRemoteLinuxDeployService(parent) {} + + void setDeployableFiles(const QList<DeployableFile> &files) { m_deployableFiles = files; } + void setIgnoreMissingFiles(bool ignore) { m_ignoreMissingFiles = ignore; } + +private: + bool isDeploymentNecessary() const override; + + void doDeviceSetup() override { handleDeviceSetupDone(true); } + void stopDeviceSetup() override { handleDeviceSetupDone(false); }; + + void doDeploy() override; + void stopDeployment() override { setFinished(); }; + + void filterDeployableFiles() const; + void createRemoteDirectories(); + void deployFiles(); + void deployNextFile(); + void setFinished(); + + mutable QList<DeployableFile> m_deployableFiles; + bool m_ignoreMissingFiles = false; + QProcess m_rsync; + SshRemoteProcessPtr m_mkdir; +}; + +bool RsyncDeployService::isDeploymentNecessary() const +{ + filterDeployableFiles(); + return !m_deployableFiles.empty(); +} + +void RsyncDeployService::doDeploy() +{ + createRemoteDirectories(); +} + +void RsyncDeployService::filterDeployableFiles() const +{ + if (m_ignoreMissingFiles) { + erase(m_deployableFiles, [](const DeployableFile &f) { + return !f.localFilePath().exists(); + }); + } +} + +void RsyncDeployService::createRemoteDirectories() +{ + QStringList remoteDirs; + for (const DeployableFile &f : m_deployableFiles) + remoteDirs << f.remoteDirectory(); + remoteDirs.sort(); + remoteDirs.removeDuplicates(); + m_mkdir = connection()->createRemoteProcess("mkdir -p " + QtcProcess::Arguments + ::createUnixArgs(remoteDirs).toString().toUtf8()); + connect(m_mkdir.get(), &SshRemoteProcess::done, [this](const QString &error) { + QString userError; + if (!error.isEmpty()) + userError = error; + if (m_mkdir->exitCode() != 0) + userError = QString::fromUtf8(m_mkdir->readAllStandardError()); + if (!userError.isEmpty()) { + emit errorMessage(tr("Failed to create remote directories: %1").arg(userError)); + setFinished(); + return; + } + deployFiles(); + }); + m_mkdir->start(); +} + +void RsyncDeployService::deployFiles() +{ + connect(&m_rsync, &QProcess::readyReadStandardOutput, [this] { + emit progressMessage(QString::fromLocal8Bit(m_rsync.readAllStandardOutput())); + }); + connect(&m_rsync, &QProcess::readyReadStandardError, [this] { + emit warningMessage(QString::fromLocal8Bit(m_rsync.readAllStandardError())); + }); + connect(&m_rsync, &QProcess::errorOccurred, [this] { + if (m_rsync.error() == QProcess::FailedToStart) { + emit errorMessage(tr("rsync failed to start: %1").arg(m_rsync.errorString())); + setFinished(); + } + }); + connect(&m_rsync, static_cast<void (QProcess::*)(int)>(&QProcess::finished), [this] { + if (m_rsync.exitStatus() == QProcess::CrashExit) { + emit errorMessage(tr("rsync crashed.")); + setFinished(); + return; + } + if (m_rsync.exitCode() != 0) { + emit errorMessage(tr("rsync failed with exit code %1.").arg(m_rsync.exitCode())); + setFinished(); + return; + } + deployNextFile(); + }); + deployNextFile(); +} + +void RsyncDeployService::deployNextFile() +{ + if (m_deployableFiles.empty()) { + setFinished(); + return; + } + const DeployableFile file = m_deployableFiles.takeFirst(); + const RsyncCommandLine cmdLine = RsyncDeployStep::rsyncCommand(*connection()); + const QStringList args = QStringList(cmdLine.options) + << file.localFilePath().toString() + << (cmdLine.remoteHostSpec + ':' + file.remoteFilePath()); + m_rsync.start("rsync", args); // TODO: Get rsync location from settings? +} + +void RsyncDeployService::setFinished() +{ + if (m_mkdir) { + m_mkdir->disconnect(); + m_mkdir->kill(); + } + m_rsync.disconnect(); + m_rsync.kill(); + handleDeploymentDone(); +} + +} // namespace Internal + +class RsyncDeployStep::RsyncDeployStepPrivate +{ +public: + Internal::RsyncDeployService deployService; + BaseBoolAspect *ignoreMissingFilesAspect; +}; + +RsyncDeployStep::RsyncDeployStep(BuildStepList *bsl) + : AbstractRemoteLinuxDeployStep(bsl, stepId()), d(new RsyncDeployStepPrivate) +{ + d->ignoreMissingFilesAspect = addAspect<BaseBoolAspect>(); + d->ignoreMissingFilesAspect + ->setSettingsKey("RemoteLinux.RsyncDeployStep.IgnoreMissingFiles"); + d->ignoreMissingFilesAspect->setLabel(tr("Ignore missing files")); + d->ignoreMissingFilesAspect->setValue(false); + + setDefaultDisplayName(displayName()); +} + +RsyncDeployStep::~RsyncDeployStep() +{ + delete d; +} + +bool RsyncDeployStep::initInternal(QString *error) +{ + d->deployService.setDeployableFiles(target()->deploymentData().allFiles()); + d->deployService.setIgnoreMissingFiles(d->ignoreMissingFilesAspect->value()); + return d->deployService.isDeploymentPossible(error); +} + +AbstractRemoteLinuxDeployService *RsyncDeployStep::deployService() const +{ + return &d->deployService; +} + +Core::Id RsyncDeployStep::stepId() +{ + return "RemoteLinux.RsyncDeployStep"; +} + +QString RsyncDeployStep::displayName() +{ + return tr("Deploy files via rsync"); +} + +RsyncCommandLine RsyncDeployStep::rsyncCommand(const SshConnection &sshConnection) +{ + const QString sshCmdLine = QtcProcess::joinArgs( + QStringList{SshSettings::sshFilePath().toUserOutput()} + << sshConnection.connectionOptions()); + const SshConnectionParameters sshParams = sshConnection.connectionParameters(); + return RsyncCommandLine(QStringList{"-e", sshCmdLine, "-avz"}, + sshParams.userName() + '@' + sshParams.host()); +} + +} //namespace RemoteLinux + +#include <rsyncdeploystep.moc> diff --git a/src/plugins/remotelinux/rsyncdeploystep.h b/src/plugins/remotelinux/rsyncdeploystep.h new file mode 100644 index 0000000000..ddc627e7dc --- /dev/null +++ b/src/plugins/remotelinux/rsyncdeploystep.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "abstractremotelinuxdeploystep.h" +#include "remotelinux_export.h" + +namespace QSsh { class SshConnection; } + +namespace RemoteLinux { + +class RsyncCommandLine +{ +public: + RsyncCommandLine(const QStringList &o, const QString &h) : options(o), remoteHostSpec(h) {} + const QStringList options; + const QString remoteHostSpec; +}; + +class REMOTELINUX_EXPORT RsyncDeployStep : public AbstractRemoteLinuxDeployStep +{ + Q_OBJECT + +public: + explicit RsyncDeployStep(ProjectExplorer::BuildStepList *bsl); + ~RsyncDeployStep() override; + + static Core::Id stepId(); + static QString displayName(); + + static RsyncCommandLine rsyncCommand(const QSsh::SshConnection &sshConnection); + +private: + AbstractRemoteLinuxDeployService *deployService() const override; + + bool initInternal(QString *error = nullptr) override; + + class RsyncDeployStepPrivate; + RsyncDeployStepPrivate * const d; +}; + +} // namespace RemoteLinux |