forked from qt-creator/qt-creator
RemoteLinux: Move GenericDirectUploadService implementation
... to genericdirectuploadstep.cpp and inline its pimpl. Change-Id: I50550ee4bbf4266fa191008c9db1696bae3f7f43 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
@@ -5,7 +5,6 @@ add_qtc_plugin(RemoteLinux
|
|||||||
abstractremotelinuxdeploystep.cpp abstractremotelinuxdeploystep.h
|
abstractremotelinuxdeploystep.cpp abstractremotelinuxdeploystep.h
|
||||||
customcommanddeploystep.cpp customcommanddeploystep.h
|
customcommanddeploystep.cpp customcommanddeploystep.h
|
||||||
deploymenttimeinfo.cpp deploymenttimeinfo.h
|
deploymenttimeinfo.cpp deploymenttimeinfo.h
|
||||||
genericdirectuploadservice.cpp genericdirectuploadservice.h
|
|
||||||
genericdirectuploadstep.cpp genericdirectuploadstep.h
|
genericdirectuploadstep.cpp genericdirectuploadstep.h
|
||||||
genericlinuxdeviceconfigurationwidget.cpp genericlinuxdeviceconfigurationwidget.h
|
genericlinuxdeviceconfigurationwidget.cpp genericlinuxdeviceconfigurationwidget.h
|
||||||
genericlinuxdeviceconfigurationwizard.cpp genericlinuxdeviceconfigurationwizard.h
|
genericlinuxdeviceconfigurationwizard.cpp genericlinuxdeviceconfigurationwizard.h
|
||||||
|
|||||||
@@ -1,314 +0,0 @@
|
|||||||
// Copyright (C) 2016 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#include "genericdirectuploadservice.h"
|
|
||||||
|
|
||||||
#include "remotelinuxtr.h"
|
|
||||||
|
|
||||||
#include <projectexplorer/deployablefile.h>
|
|
||||||
#include <projectexplorer/devicesupport/filetransfer.h>
|
|
||||||
#include <projectexplorer/devicesupport/idevice.h>
|
|
||||||
|
|
||||||
#include <utils/hostosinfo.h>
|
|
||||||
#include <utils/processinterface.h>
|
|
||||||
#include <utils/qtcassert.h>
|
|
||||||
#include <utils/qtcprocess.h>
|
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QDir>
|
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
|
||||||
using namespace Utils;
|
|
||||||
using namespace Utils::Tasking;
|
|
||||||
|
|
||||||
namespace RemoteLinux {
|
|
||||||
namespace Internal {
|
|
||||||
|
|
||||||
const int MaxConcurrentStatCalls = 10;
|
|
||||||
|
|
||||||
struct UploadStorage
|
|
||||||
{
|
|
||||||
QList<DeployableFile> filesToUpload;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GenericDirectUploadServicePrivate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
GenericDirectUploadServicePrivate(GenericDirectUploadService *service) : q(service) {}
|
|
||||||
|
|
||||||
QDateTime timestampFromStat(const DeployableFile &file, QtcProcess *statProc);
|
|
||||||
|
|
||||||
using FilesToStat = std::function<QList<DeployableFile>(UploadStorage *)>;
|
|
||||||
using StatEndHandler
|
|
||||||
= std::function<void(UploadStorage *, const DeployableFile &, const QDateTime &)>;
|
|
||||||
TaskItem statTask(UploadStorage *storage, const DeployableFile &file,
|
|
||||||
StatEndHandler statEndHandler);
|
|
||||||
TaskItem statTree(const TreeStorage<UploadStorage> &storage, FilesToStat filesToStat,
|
|
||||||
StatEndHandler statEndHandler);
|
|
||||||
TaskItem uploadTask(const TreeStorage<UploadStorage> &storage);
|
|
||||||
TaskItem chmodTask(const DeployableFile &file);
|
|
||||||
TaskItem chmodTree(const TreeStorage<UploadStorage> &storage);
|
|
||||||
|
|
||||||
GenericDirectUploadService *q = nullptr;
|
|
||||||
IncrementalDeployment incremental = IncrementalDeployment::NotSupported;
|
|
||||||
bool ignoreMissingFiles = false;
|
|
||||||
QList<DeployableFile> deployableFiles;
|
|
||||||
};
|
|
||||||
|
|
||||||
QList<DeployableFile> collectFilesToUpload(const DeployableFile &deployable)
|
|
||||||
{
|
|
||||||
QList<DeployableFile> collected;
|
|
||||||
FilePath localFile = deployable.localFilePath();
|
|
||||||
if (localFile.isDir()) {
|
|
||||||
const FilePaths files = localFile.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
|
||||||
const QString remoteDir = deployable.remoteDirectory() + '/' + localFile.fileName();
|
|
||||||
for (const FilePath &localFilePath : files)
|
|
||||||
collected.append(collectFilesToUpload(DeployableFile(localFilePath, remoteDir)));
|
|
||||||
} else {
|
|
||||||
collected << deployable;
|
|
||||||
}
|
|
||||||
return collected;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Internal
|
|
||||||
|
|
||||||
using namespace Internal;
|
|
||||||
|
|
||||||
GenericDirectUploadService::GenericDirectUploadService(QObject *parent)
|
|
||||||
: AbstractRemoteLinuxDeployService(parent), d(new GenericDirectUploadServicePrivate(this))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
GenericDirectUploadService::~GenericDirectUploadService()
|
|
||||||
{
|
|
||||||
delete d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenericDirectUploadService::setDeployableFiles(const QList<DeployableFile> &deployableFiles)
|
|
||||||
{
|
|
||||||
d->deployableFiles = deployableFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenericDirectUploadService::setIncrementalDeployment(IncrementalDeployment incremental)
|
|
||||||
{
|
|
||||||
d->incremental = incremental;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenericDirectUploadService::setIgnoreMissingFiles(bool ignoreMissingFiles)
|
|
||||||
{
|
|
||||||
d->ignoreMissingFiles = ignoreMissingFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GenericDirectUploadService::isDeploymentNecessary() const
|
|
||||||
{
|
|
||||||
QList<DeployableFile> collected;
|
|
||||||
for (int i = 0; i < d->deployableFiles.count(); ++i)
|
|
||||||
collected.append(collectFilesToUpload(d->deployableFiles.at(i)));
|
|
||||||
|
|
||||||
QTC_CHECK(collected.size() >= d->deployableFiles.size());
|
|
||||||
d->deployableFiles = collected;
|
|
||||||
return !d->deployableFiles.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
QDateTime GenericDirectUploadServicePrivate::timestampFromStat(const DeployableFile &file,
|
|
||||||
QtcProcess *statProc)
|
|
||||||
{
|
|
||||||
bool succeeded = false;
|
|
||||||
QString error;
|
|
||||||
if (statProc->error() == QProcess::FailedToStart) {
|
|
||||||
error = Tr::tr("Failed to start \"stat\": %1").arg(statProc->errorString());
|
|
||||||
} else if (statProc->exitStatus() == QProcess::CrashExit) {
|
|
||||||
error = Tr::tr("\"stat\" crashed.");
|
|
||||||
} else if (statProc->exitCode() != 0) {
|
|
||||||
error = Tr::tr("\"stat\" failed with exit code %1: %2")
|
|
||||||
.arg(statProc->exitCode()).arg(statProc->cleanedStdErr());
|
|
||||||
} else {
|
|
||||||
succeeded = true;
|
|
||||||
}
|
|
||||||
if (!succeeded) {
|
|
||||||
emit q->warningMessage(Tr::tr("Failed to retrieve remote timestamp for file \"%1\". "
|
|
||||||
"Incremental deployment will not work. Error message was: %2")
|
|
||||||
.arg(file.remoteFilePath(), error));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const QByteArray output = statProc->readAllRawStandardOutput().trimmed();
|
|
||||||
const QString warningString(Tr::tr("Unexpected stat output for remote file \"%1\": %2")
|
|
||||||
.arg(file.remoteFilePath()).arg(QString::fromUtf8(output)));
|
|
||||||
if (!output.startsWith(file.remoteFilePath().toUtf8())) {
|
|
||||||
emit q->warningMessage(warningString);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const QByteArrayList columns = output.mid(file.remoteFilePath().toUtf8().size() + 1).split(' ');
|
|
||||||
if (columns.size() < 14) { // Normal Linux stat: 16 columns in total, busybox stat: 15 columns
|
|
||||||
emit q->warningMessage(warningString);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
bool isNumber;
|
|
||||||
const qint64 secsSinceEpoch = columns.at(11).toLongLong(&isNumber);
|
|
||||||
if (!isNumber) {
|
|
||||||
emit q->warningMessage(warningString);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return QDateTime::fromSecsSinceEpoch(secsSinceEpoch);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskItem GenericDirectUploadServicePrivate::statTask(UploadStorage *storage,
|
|
||||||
const DeployableFile &file,
|
|
||||||
StatEndHandler statEndHandler)
|
|
||||||
{
|
|
||||||
const auto setupHandler = [=](QtcProcess &process) {
|
|
||||||
// We'd like to use --format=%Y, but it's not supported by busybox.
|
|
||||||
process.setCommand({q->deviceConfiguration()->filePath("stat"),
|
|
||||||
{"-t", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}});
|
|
||||||
};
|
|
||||||
const auto endHandler = [=](const QtcProcess &process) {
|
|
||||||
QtcProcess *proc = const_cast<QtcProcess *>(&process);
|
|
||||||
const QDateTime timestamp = timestampFromStat(file, proc);
|
|
||||||
statEndHandler(storage, file, timestamp);
|
|
||||||
};
|
|
||||||
return Process(setupHandler, endHandler, endHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskItem GenericDirectUploadServicePrivate::statTree(const TreeStorage<UploadStorage> &storage,
|
|
||||||
FilesToStat filesToStat, StatEndHandler statEndHandler)
|
|
||||||
{
|
|
||||||
const auto setupHandler = [=](TaskTree &tree) {
|
|
||||||
UploadStorage *storagePtr = storage.activeStorage();
|
|
||||||
const QList<DeployableFile> files = filesToStat(storagePtr);
|
|
||||||
QList<TaskItem> statList{optional, ParallelLimit(MaxConcurrentStatCalls)};
|
|
||||||
for (const DeployableFile &file : std::as_const(files)) {
|
|
||||||
QTC_ASSERT(file.isValid(), continue);
|
|
||||||
statList.append(statTask(storagePtr, file, statEndHandler));
|
|
||||||
}
|
|
||||||
tree.setupRoot({statList});
|
|
||||||
};
|
|
||||||
return Tree(setupHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskItem GenericDirectUploadServicePrivate::uploadTask(const TreeStorage<UploadStorage> &storage)
|
|
||||||
{
|
|
||||||
const auto setupHandler = [this, storage](FileTransfer &transfer) {
|
|
||||||
if (storage->filesToUpload.isEmpty()) {
|
|
||||||
emit q->progressMessage(Tr::tr("No files need to be uploaded."));
|
|
||||||
return TaskAction::StopWithDone;
|
|
||||||
}
|
|
||||||
emit q->progressMessage(Tr::tr("%n file(s) need to be uploaded.", "",
|
|
||||||
storage->filesToUpload.size()));
|
|
||||||
FilesToTransfer files;
|
|
||||||
for (const DeployableFile &file : std::as_const(storage->filesToUpload)) {
|
|
||||||
if (!file.localFilePath().exists()) {
|
|
||||||
const QString message = Tr::tr("Local file \"%1\" does not exist.")
|
|
||||||
.arg(file.localFilePath().toUserOutput());
|
|
||||||
if (ignoreMissingFiles) {
|
|
||||||
emit q->warningMessage(message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
emit q->errorMessage(message);
|
|
||||||
return TaskAction::StopWithError;
|
|
||||||
}
|
|
||||||
files.append({file.localFilePath(),
|
|
||||||
q->deviceConfiguration()->filePath(file.remoteFilePath())});
|
|
||||||
}
|
|
||||||
if (files.isEmpty()) {
|
|
||||||
emit q->progressMessage(Tr::tr("No files need to be uploaded."));
|
|
||||||
return TaskAction::StopWithDone;
|
|
||||||
}
|
|
||||||
transfer.setFilesToTransfer(files);
|
|
||||||
QObject::connect(&transfer, &FileTransfer::progress,
|
|
||||||
q, &GenericDirectUploadService::progressMessage);
|
|
||||||
return TaskAction::Continue;
|
|
||||||
};
|
|
||||||
const auto errorHandler = [this](const FileTransfer &transfer) {
|
|
||||||
emit q->errorMessage(transfer.resultData().m_errorString);
|
|
||||||
};
|
|
||||||
|
|
||||||
return Transfer(setupHandler, {}, errorHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskItem GenericDirectUploadServicePrivate::chmodTask(const DeployableFile &file)
|
|
||||||
{
|
|
||||||
const auto setupHandler = [=](QtcProcess &process) {
|
|
||||||
process.setCommand({q->deviceConfiguration()->filePath("chmod"),
|
|
||||||
{"a+x", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}});
|
|
||||||
};
|
|
||||||
const auto errorHandler = [=](const QtcProcess &process) {
|
|
||||||
const QString error = process.errorString();
|
|
||||||
if (!error.isEmpty()) {
|
|
||||||
emit q->warningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2")
|
|
||||||
.arg(file.remoteFilePath(), error));
|
|
||||||
} else if (process.exitCode() != 0) {
|
|
||||||
emit q->warningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2")
|
|
||||||
.arg(file.remoteFilePath(), process.cleanedStdErr()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return Process(setupHandler, {}, errorHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskItem GenericDirectUploadServicePrivate::chmodTree(const TreeStorage<UploadStorage> &storage)
|
|
||||||
{
|
|
||||||
const auto setupChmodHandler = [=](TaskTree &tree) {
|
|
||||||
QList<DeployableFile> filesToChmod;
|
|
||||||
for (const DeployableFile &file : std::as_const(storage->filesToUpload)) {
|
|
||||||
if (file.isExecutable())
|
|
||||||
filesToChmod << file;
|
|
||||||
}
|
|
||||||
QList<TaskItem> chmodList{optional, ParallelLimit(MaxConcurrentStatCalls)};
|
|
||||||
for (const DeployableFile &file : std::as_const(filesToChmod)) {
|
|
||||||
QTC_ASSERT(file.isValid(), continue);
|
|
||||||
chmodList.append(chmodTask(file));
|
|
||||||
}
|
|
||||||
tree.setupRoot({chmodList});
|
|
||||||
};
|
|
||||||
return Tree(setupChmodHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
Group GenericDirectUploadService::deployRecipe()
|
|
||||||
{
|
|
||||||
const auto preFilesToStat = [this](UploadStorage *storage) {
|
|
||||||
QList<DeployableFile> filesToStat;
|
|
||||||
for (const DeployableFile &file : std::as_const(d->deployableFiles)) {
|
|
||||||
if (d->incremental != IncrementalDeployment::Enabled || hasLocalFileChanged(file)) {
|
|
||||||
storage->filesToUpload.append(file);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (d->incremental == IncrementalDeployment::NotSupported)
|
|
||||||
continue;
|
|
||||||
filesToStat << file;
|
|
||||||
}
|
|
||||||
return filesToStat;
|
|
||||||
};
|
|
||||||
const auto preStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file,
|
|
||||||
const QDateTime ×tamp) {
|
|
||||||
if (!timestamp.isValid() || hasRemoteFileChanged(file, timestamp))
|
|
||||||
storage->filesToUpload.append(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto postFilesToStat = [this](UploadStorage *storage) {
|
|
||||||
return d->incremental == IncrementalDeployment::NotSupported
|
|
||||||
? QList<DeployableFile>() : storage->filesToUpload;
|
|
||||||
};
|
|
||||||
const auto postStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file,
|
|
||||||
const QDateTime ×tamp) {
|
|
||||||
Q_UNUSED(storage)
|
|
||||||
if (timestamp.isValid())
|
|
||||||
saveDeploymentTimeStamp(file, timestamp);
|
|
||||||
};
|
|
||||||
const auto doneHandler = [this] {
|
|
||||||
emit progressMessage(Tr::tr("All files successfully deployed."));
|
|
||||||
};
|
|
||||||
|
|
||||||
const TreeStorage<UploadStorage> storage;
|
|
||||||
const Group root {
|
|
||||||
Storage(storage),
|
|
||||||
d->statTree(storage, preFilesToStat, preStatEndHandler),
|
|
||||||
d->uploadTask(storage),
|
|
||||||
Group {
|
|
||||||
d->chmodTree(storage),
|
|
||||||
d->statTree(storage, postFilesToStat, postStatEndHandler)
|
|
||||||
},
|
|
||||||
OnGroupDone(doneHandler)
|
|
||||||
};
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
} //namespace RemoteLinux
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// Copyright (C) 2016 The Qt Company Ltd.
|
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "remotelinux_export.h"
|
|
||||||
|
|
||||||
#include "abstractremotelinuxdeploystep.h"
|
|
||||||
|
|
||||||
#include <QList>
|
|
||||||
|
|
||||||
namespace ProjectExplorer { class DeployableFile; }
|
|
||||||
|
|
||||||
namespace RemoteLinux {
|
|
||||||
namespace Internal { class GenericDirectUploadServicePrivate; }
|
|
||||||
|
|
||||||
enum class IncrementalDeployment { Enabled, Disabled, NotSupported };
|
|
||||||
|
|
||||||
class REMOTELINUX_EXPORT GenericDirectUploadService : public AbstractRemoteLinuxDeployService
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
GenericDirectUploadService(QObject *parent = nullptr);
|
|
||||||
~GenericDirectUploadService();
|
|
||||||
|
|
||||||
void setDeployableFiles(const QList<ProjectExplorer::DeployableFile> &deployableFiles);
|
|
||||||
void setIncrementalDeployment(IncrementalDeployment incremental);
|
|
||||||
void setIgnoreMissingFiles(bool ignoreMissingFiles);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool isDeploymentNecessary() const final;
|
|
||||||
Utils::Tasking::Group deployRecipe() final;
|
|
||||||
|
|
||||||
friend class Internal::GenericDirectUploadServicePrivate;
|
|
||||||
Internal::GenericDirectUploadServicePrivate * const d;
|
|
||||||
};
|
|
||||||
|
|
||||||
} //namespace RemoteLinux
|
|
||||||
@@ -3,18 +3,317 @@
|
|||||||
|
|
||||||
#include "genericdirectuploadstep.h"
|
#include "genericdirectuploadstep.h"
|
||||||
|
|
||||||
#include "genericdirectuploadservice.h"
|
|
||||||
#include "remotelinux_constants.h"
|
#include "remotelinux_constants.h"
|
||||||
#include "remotelinuxtr.h"
|
#include "remotelinuxtr.h"
|
||||||
|
|
||||||
|
#include <projectexplorer/deployablefile.h>
|
||||||
#include <projectexplorer/deploymentdata.h>
|
#include <projectexplorer/deploymentdata.h>
|
||||||
#include <projectexplorer/target.h>
|
#include <projectexplorer/devicesupport/filetransfer.h>
|
||||||
|
#include <projectexplorer/devicesupport/idevice.h>
|
||||||
#include <projectexplorer/runconfigurationaspects.h>
|
#include <projectexplorer/runconfigurationaspects.h>
|
||||||
|
#include <projectexplorer/target.h>
|
||||||
|
|
||||||
|
#include <utils/hostosinfo.h>
|
||||||
|
#include <utils/processinterface.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/qtcprocess.h>
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
using namespace ProjectExplorer;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
using namespace Utils::Tasking;
|
||||||
|
|
||||||
namespace RemoteLinux {
|
namespace RemoteLinux {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
const int MaxConcurrentStatCalls = 10;
|
||||||
|
|
||||||
|
struct UploadStorage
|
||||||
|
{
|
||||||
|
QList<DeployableFile> filesToUpload;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class IncrementalDeployment { Enabled, Disabled, NotSupported };
|
||||||
|
|
||||||
|
class GenericDirectUploadService : public AbstractRemoteLinuxDeployService
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GenericDirectUploadService(QObject *parent = nullptr)
|
||||||
|
: AbstractRemoteLinuxDeployService(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void setDeployableFiles(const QList<ProjectExplorer::DeployableFile> &deployableFiles);
|
||||||
|
void setIncrementalDeployment(IncrementalDeployment incremental);
|
||||||
|
void setIgnoreMissingFiles(bool ignoreMissingFiles);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isDeploymentNecessary() const final;
|
||||||
|
Utils::Tasking::Group deployRecipe() final;
|
||||||
|
|
||||||
|
QDateTime timestampFromStat(const DeployableFile &file, QtcProcess *statProc);
|
||||||
|
|
||||||
|
using FilesToStat = std::function<QList<DeployableFile>(UploadStorage *)>;
|
||||||
|
using StatEndHandler
|
||||||
|
= std::function<void(UploadStorage *, const DeployableFile &, const QDateTime &)>;
|
||||||
|
TaskItem statTask(UploadStorage *storage, const DeployableFile &file,
|
||||||
|
StatEndHandler statEndHandler);
|
||||||
|
TaskItem statTree(const TreeStorage<UploadStorage> &storage, FilesToStat filesToStat,
|
||||||
|
StatEndHandler statEndHandler);
|
||||||
|
TaskItem uploadTask(const TreeStorage<UploadStorage> &storage);
|
||||||
|
TaskItem chmodTask(const DeployableFile &file);
|
||||||
|
TaskItem chmodTree(const TreeStorage<UploadStorage> &storage);
|
||||||
|
|
||||||
|
IncrementalDeployment m_incremental = IncrementalDeployment::NotSupported;
|
||||||
|
bool m_ignoreMissingFiles = false;
|
||||||
|
mutable QList<DeployableFile> m_deployableFiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
QList<DeployableFile> collectFilesToUpload(const DeployableFile &deployable)
|
||||||
|
{
|
||||||
|
QList<DeployableFile> collected;
|
||||||
|
FilePath localFile = deployable.localFilePath();
|
||||||
|
if (localFile.isDir()) {
|
||||||
|
const FilePaths files = localFile.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
const QString remoteDir = deployable.remoteDirectory() + '/' + localFile.fileName();
|
||||||
|
for (const FilePath &localFilePath : files)
|
||||||
|
collected.append(collectFilesToUpload(DeployableFile(localFilePath, remoteDir)));
|
||||||
|
} else {
|
||||||
|
collected << deployable;
|
||||||
|
}
|
||||||
|
return collected;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
|
||||||
|
using namespace Internal;
|
||||||
|
|
||||||
|
void GenericDirectUploadService::setDeployableFiles(const QList<DeployableFile> &deployableFiles)
|
||||||
|
{
|
||||||
|
m_deployableFiles = deployableFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericDirectUploadService::setIncrementalDeployment(IncrementalDeployment incremental)
|
||||||
|
{
|
||||||
|
m_incremental = incremental;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericDirectUploadService::setIgnoreMissingFiles(bool ignoreMissingFiles)
|
||||||
|
{
|
||||||
|
m_ignoreMissingFiles = ignoreMissingFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenericDirectUploadService::isDeploymentNecessary() const
|
||||||
|
{
|
||||||
|
QList<DeployableFile> collected;
|
||||||
|
for (int i = 0; i < m_deployableFiles.count(); ++i)
|
||||||
|
collected.append(collectFilesToUpload(m_deployableFiles.at(i)));
|
||||||
|
|
||||||
|
QTC_CHECK(collected.size() >= m_deployableFiles.size());
|
||||||
|
m_deployableFiles = collected;
|
||||||
|
return !m_deployableFiles.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime GenericDirectUploadService::timestampFromStat(const DeployableFile &file,
|
||||||
|
QtcProcess *statProc)
|
||||||
|
{
|
||||||
|
bool succeeded = false;
|
||||||
|
QString error;
|
||||||
|
if (statProc->error() == QProcess::FailedToStart) {
|
||||||
|
error = Tr::tr("Failed to start \"stat\": %1").arg(statProc->errorString());
|
||||||
|
} else if (statProc->exitStatus() == QProcess::CrashExit) {
|
||||||
|
error = Tr::tr("\"stat\" crashed.");
|
||||||
|
} else if (statProc->exitCode() != 0) {
|
||||||
|
error = Tr::tr("\"stat\" failed with exit code %1: %2")
|
||||||
|
.arg(statProc->exitCode()).arg(statProc->cleanedStdErr());
|
||||||
|
} else {
|
||||||
|
succeeded = true;
|
||||||
|
}
|
||||||
|
if (!succeeded) {
|
||||||
|
emit warningMessage(Tr::tr("Failed to retrieve remote timestamp for file \"%1\". "
|
||||||
|
"Incremental deployment will not work. Error message was: %2")
|
||||||
|
.arg(file.remoteFilePath(), error));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const QByteArray output = statProc->readAllRawStandardOutput().trimmed();
|
||||||
|
const QString warningString(Tr::tr("Unexpected stat output for remote file \"%1\": %2")
|
||||||
|
.arg(file.remoteFilePath()).arg(QString::fromUtf8(output)));
|
||||||
|
if (!output.startsWith(file.remoteFilePath().toUtf8())) {
|
||||||
|
emit warningMessage(warningString);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const QByteArrayList columns = output.mid(file.remoteFilePath().toUtf8().size() + 1).split(' ');
|
||||||
|
if (columns.size() < 14) { // Normal Linux stat: 16 columns in total, busybox stat: 15 columns
|
||||||
|
emit warningMessage(warningString);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
bool isNumber;
|
||||||
|
const qint64 secsSinceEpoch = columns.at(11).toLongLong(&isNumber);
|
||||||
|
if (!isNumber) {
|
||||||
|
emit warningMessage(warningString);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return QDateTime::fromSecsSinceEpoch(secsSinceEpoch);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskItem GenericDirectUploadService::statTask(UploadStorage *storage,
|
||||||
|
const DeployableFile &file,
|
||||||
|
StatEndHandler statEndHandler)
|
||||||
|
{
|
||||||
|
const auto setupHandler = [=](QtcProcess &process) {
|
||||||
|
// We'd like to use --format=%Y, but it's not supported by busybox.
|
||||||
|
process.setCommand({deviceConfiguration()->filePath("stat"),
|
||||||
|
{"-t", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}});
|
||||||
|
};
|
||||||
|
const auto endHandler = [=](const QtcProcess &process) {
|
||||||
|
QtcProcess *proc = const_cast<QtcProcess *>(&process);
|
||||||
|
const QDateTime timestamp = timestampFromStat(file, proc);
|
||||||
|
statEndHandler(storage, file, timestamp);
|
||||||
|
};
|
||||||
|
return Process(setupHandler, endHandler, endHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskItem GenericDirectUploadService::statTree(const TreeStorage<UploadStorage> &storage,
|
||||||
|
FilesToStat filesToStat, StatEndHandler statEndHandler)
|
||||||
|
{
|
||||||
|
const auto setupHandler = [=](TaskTree &tree) {
|
||||||
|
UploadStorage *storagePtr = storage.activeStorage();
|
||||||
|
const QList<DeployableFile> files = filesToStat(storagePtr);
|
||||||
|
QList<TaskItem> statList{optional, ParallelLimit(MaxConcurrentStatCalls)};
|
||||||
|
for (const DeployableFile &file : std::as_const(files)) {
|
||||||
|
QTC_ASSERT(file.isValid(), continue);
|
||||||
|
statList.append(statTask(storagePtr, file, statEndHandler));
|
||||||
|
}
|
||||||
|
tree.setupRoot({statList});
|
||||||
|
};
|
||||||
|
return Tree(setupHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskItem GenericDirectUploadService::uploadTask(const TreeStorage<UploadStorage> &storage)
|
||||||
|
{
|
||||||
|
const auto setupHandler = [this, storage](FileTransfer &transfer) {
|
||||||
|
if (storage->filesToUpload.isEmpty()) {
|
||||||
|
emit progressMessage(Tr::tr("No files need to be uploaded."));
|
||||||
|
return TaskAction::StopWithDone;
|
||||||
|
}
|
||||||
|
emit progressMessage(Tr::tr("%n file(s) need to be uploaded.", "",
|
||||||
|
storage->filesToUpload.size()));
|
||||||
|
FilesToTransfer files;
|
||||||
|
for (const DeployableFile &file : std::as_const(storage->filesToUpload)) {
|
||||||
|
if (!file.localFilePath().exists()) {
|
||||||
|
const QString message = Tr::tr("Local file \"%1\" does not exist.")
|
||||||
|
.arg(file.localFilePath().toUserOutput());
|
||||||
|
if (m_ignoreMissingFiles) {
|
||||||
|
emit warningMessage(message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
emit errorMessage(message);
|
||||||
|
return TaskAction::StopWithError;
|
||||||
|
}
|
||||||
|
files.append({file.localFilePath(),
|
||||||
|
deviceConfiguration()->filePath(file.remoteFilePath())});
|
||||||
|
}
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
emit progressMessage(Tr::tr("No files need to be uploaded."));
|
||||||
|
return TaskAction::StopWithDone;
|
||||||
|
}
|
||||||
|
transfer.setFilesToTransfer(files);
|
||||||
|
QObject::connect(&transfer, &FileTransfer::progress,
|
||||||
|
this, &GenericDirectUploadService::progressMessage);
|
||||||
|
return TaskAction::Continue;
|
||||||
|
};
|
||||||
|
const auto errorHandler = [this](const FileTransfer &transfer) {
|
||||||
|
emit errorMessage(transfer.resultData().m_errorString);
|
||||||
|
};
|
||||||
|
|
||||||
|
return Transfer(setupHandler, {}, errorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskItem GenericDirectUploadService::chmodTask(const DeployableFile &file)
|
||||||
|
{
|
||||||
|
const auto setupHandler = [=](QtcProcess &process) {
|
||||||
|
process.setCommand({deviceConfiguration()->filePath("chmod"),
|
||||||
|
{"a+x", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}});
|
||||||
|
};
|
||||||
|
const auto errorHandler = [=](const QtcProcess &process) {
|
||||||
|
const QString error = process.errorString();
|
||||||
|
if (!error.isEmpty()) {
|
||||||
|
emit warningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2")
|
||||||
|
.arg(file.remoteFilePath(), error));
|
||||||
|
} else if (process.exitCode() != 0) {
|
||||||
|
emit warningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2")
|
||||||
|
.arg(file.remoteFilePath(), process.cleanedStdErr()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Process(setupHandler, {}, errorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskItem GenericDirectUploadService::chmodTree(const TreeStorage<UploadStorage> &storage)
|
||||||
|
{
|
||||||
|
const auto setupChmodHandler = [=](TaskTree &tree) {
|
||||||
|
QList<DeployableFile> filesToChmod;
|
||||||
|
for (const DeployableFile &file : std::as_const(storage->filesToUpload)) {
|
||||||
|
if (file.isExecutable())
|
||||||
|
filesToChmod << file;
|
||||||
|
}
|
||||||
|
QList<TaskItem> chmodList{optional, ParallelLimit(MaxConcurrentStatCalls)};
|
||||||
|
for (const DeployableFile &file : std::as_const(filesToChmod)) {
|
||||||
|
QTC_ASSERT(file.isValid(), continue);
|
||||||
|
chmodList.append(chmodTask(file));
|
||||||
|
}
|
||||||
|
tree.setupRoot({chmodList});
|
||||||
|
};
|
||||||
|
return Tree(setupChmodHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
Group GenericDirectUploadService::deployRecipe()
|
||||||
|
{
|
||||||
|
const auto preFilesToStat = [this](UploadStorage *storage) {
|
||||||
|
QList<DeployableFile> filesToStat;
|
||||||
|
for (const DeployableFile &file : std::as_const(m_deployableFiles)) {
|
||||||
|
if (m_incremental != IncrementalDeployment::Enabled || hasLocalFileChanged(file)) {
|
||||||
|
storage->filesToUpload.append(file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (m_incremental == IncrementalDeployment::NotSupported)
|
||||||
|
continue;
|
||||||
|
filesToStat << file;
|
||||||
|
}
|
||||||
|
return filesToStat;
|
||||||
|
};
|
||||||
|
const auto preStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file,
|
||||||
|
const QDateTime ×tamp) {
|
||||||
|
if (!timestamp.isValid() || hasRemoteFileChanged(file, timestamp))
|
||||||
|
storage->filesToUpload.append(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto postFilesToStat = [this](UploadStorage *storage) {
|
||||||
|
return m_incremental == IncrementalDeployment::NotSupported
|
||||||
|
? QList<DeployableFile>() : storage->filesToUpload;
|
||||||
|
};
|
||||||
|
const auto postStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file,
|
||||||
|
const QDateTime ×tamp) {
|
||||||
|
Q_UNUSED(storage)
|
||||||
|
if (timestamp.isValid())
|
||||||
|
saveDeploymentTimeStamp(file, timestamp);
|
||||||
|
};
|
||||||
|
const auto doneHandler = [this] {
|
||||||
|
emit progressMessage(Tr::tr("All files successfully deployed."));
|
||||||
|
};
|
||||||
|
|
||||||
|
const TreeStorage<UploadStorage> storage;
|
||||||
|
const Group root {
|
||||||
|
Storage(storage),
|
||||||
|
statTree(storage, preFilesToStat, preStatEndHandler),
|
||||||
|
uploadTask(storage),
|
||||||
|
Group {
|
||||||
|
chmodTree(storage),
|
||||||
|
statTree(storage, postFilesToStat, postStatEndHandler)
|
||||||
|
},
|
||||||
|
OnGroupDone(doneHandler)
|
||||||
|
};
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
GenericDirectUploadStep::GenericDirectUploadStep(BuildStepList *bsl, Utils::Id id,
|
GenericDirectUploadStep::GenericDirectUploadStep(BuildStepList *bsl, Utils::Id id,
|
||||||
bool offerIncrementalDeployment)
|
bool offerIncrementalDeployment)
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ Project {
|
|||||||
"deploymenttimeinfo.h",
|
"deploymenttimeinfo.h",
|
||||||
"customcommanddeploystep.cpp",
|
"customcommanddeploystep.cpp",
|
||||||
"customcommanddeploystep.h",
|
"customcommanddeploystep.h",
|
||||||
"genericdirectuploadservice.cpp",
|
|
||||||
"genericdirectuploadservice.h",
|
|
||||||
"genericdirectuploadstep.cpp",
|
"genericdirectuploadstep.cpp",
|
||||||
"genericdirectuploadstep.h",
|
"genericdirectuploadstep.h",
|
||||||
"genericlinuxdeviceconfigurationwidget.cpp",
|
"genericlinuxdeviceconfigurationwidget.cpp",
|
||||||
|
|||||||
Reference in New Issue
Block a user