forked from qt-creator/qt-creator
Use TaskAction for dynamic group setup. Get rid of possibility of selecting group's children to be started inside group setup. Use dynamic children's setup instead, so that each individual child may decide itself whether it wants to be started or not. Change-Id: Ie31f707791db0dc26be92663ca6238b8e49c3b98 Reviewed-by: hjk <hjk@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
387 lines
12 KiB
C++
387 lines
12 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#pragma once
|
|
|
|
#include "utils_global.h"
|
|
|
|
#include <QHash>
|
|
#include <QObject>
|
|
#include <QSharedPointer>
|
|
|
|
namespace Utils {
|
|
|
|
class TaskContainer;
|
|
|
|
class TaskTreePrivate;
|
|
|
|
namespace Tasking {
|
|
|
|
class QTCREATOR_UTILS_EXPORT TaskInterface : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
TaskInterface() = default;
|
|
virtual void start() = 0;
|
|
|
|
signals:
|
|
void done(bool success);
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT TreeStorageBase
|
|
{
|
|
public:
|
|
bool isValid() const;
|
|
|
|
protected:
|
|
using StorageConstructor = std::function<void *(void)>;
|
|
using StorageDestructor = std::function<void(void *)>;
|
|
|
|
TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor);
|
|
void *activeStorageVoid() const;
|
|
|
|
private:
|
|
int createStorage();
|
|
void deleteStorage(int id);
|
|
void activateStorage(int id);
|
|
|
|
friend bool operator==(const TreeStorageBase &first, const TreeStorageBase &second)
|
|
{ return first.m_storageData == second.m_storageData; }
|
|
|
|
friend bool operator!=(const TreeStorageBase &first, const TreeStorageBase &second)
|
|
{ return first.m_storageData != second.m_storageData; }
|
|
|
|
friend size_t qHash(const TreeStorageBase &storage, uint seed = 0)
|
|
{ return size_t(storage.m_storageData.get()) ^ seed; }
|
|
|
|
struct StorageData {
|
|
~StorageData();
|
|
StorageConstructor m_constructor = {};
|
|
StorageDestructor m_destructor = {};
|
|
QHash<int, void *> m_storageHash = {};
|
|
int m_activeStorage = 0; // 0 means no active storage
|
|
int m_storageCounter = 0;
|
|
};
|
|
QSharedPointer<StorageData> m_storageData;
|
|
friend TaskContainer;
|
|
friend TaskTreePrivate;
|
|
};
|
|
|
|
template <typename StorageStruct>
|
|
class TreeStorage : public TreeStorageBase
|
|
{
|
|
public:
|
|
TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {}
|
|
StorageStruct *operator->() const noexcept { return activeStorage(); }
|
|
StorageStruct *activeStorage() const {
|
|
return static_cast<StorageStruct *>(activeStorageVoid());
|
|
}
|
|
|
|
private:
|
|
static StorageConstructor ctor() { return [] { return new StorageStruct; }; }
|
|
static StorageDestructor dtor() {
|
|
return [](void *storage) { delete static_cast<StorageStruct *>(storage); };
|
|
}
|
|
};
|
|
|
|
// 4 policies:
|
|
// 1. When all children finished with done -> report done, otherwise:
|
|
// a) Report error on first error and stop executing other children (including their subtree)
|
|
// b) On first error - wait for all children to be finished and report error afterwards
|
|
// 2. When all children finished with error -> report error, otherwise:
|
|
// a) Report done on first done and stop executing other children (including their subtree)
|
|
// b) On first done - wait for all children to be finished and report done afterwards
|
|
|
|
enum class WorkflowPolicy {
|
|
StopOnError, // 1a - Will report error on any child error, otherwise done (if all children were done)
|
|
ContinueOnError, // 1b - the same. When no children it reports done.
|
|
StopOnDone, // 2a - Will report done on any child done, otherwise error (if all children were error)
|
|
ContinueOnDone, // 2b - the same. When no children it reports done. (?)
|
|
Optional // Returns always done after all children finished
|
|
};
|
|
|
|
enum class TaskAction
|
|
{
|
|
Continue,
|
|
StopWithDone,
|
|
StopWithError
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT TaskItem
|
|
{
|
|
public:
|
|
// Internal, provided by QTC_DECLARE_CUSTOM_TASK
|
|
using TaskCreateHandler = std::function<TaskInterface *(void)>;
|
|
// Called prior to task start, just after createHandler
|
|
using TaskSetupHandler = std::function<TaskAction(TaskInterface &)>;
|
|
// Called on task done / error
|
|
using TaskEndHandler = std::function<void(const TaskInterface &)>;
|
|
// Called when group entered / after group ended with success or failure
|
|
using GroupSimpleHandler = std::function<void()>;
|
|
// Called when group entered
|
|
using GroupSetupHandler = std::function<TaskAction()>;
|
|
|
|
struct TaskHandler {
|
|
TaskCreateHandler m_createHandler;
|
|
TaskSetupHandler m_setupHandler;
|
|
TaskEndHandler m_doneHandler;
|
|
TaskEndHandler m_errorHandler;
|
|
};
|
|
|
|
struct GroupHandler {
|
|
GroupSimpleHandler m_setupHandler;
|
|
GroupSimpleHandler m_doneHandler = {};
|
|
GroupSimpleHandler m_errorHandler = {};
|
|
GroupSetupHandler m_dynamicSetupHandler = {};
|
|
};
|
|
|
|
int parallelLimit() const { return m_parallelLimit; }
|
|
WorkflowPolicy workflowPolicy() const { return m_workflowPolicy; }
|
|
TaskHandler taskHandler() const { return m_taskHandler; }
|
|
GroupHandler groupHandler() const { return m_groupHandler; }
|
|
QList<TaskItem> children() const { return m_children; }
|
|
QList<TreeStorageBase> storageList() const { return m_storageList; }
|
|
|
|
protected:
|
|
enum class Type {
|
|
Group,
|
|
Storage,
|
|
Limit,
|
|
Policy,
|
|
TaskHandler,
|
|
GroupHandler
|
|
};
|
|
|
|
TaskItem() = default;
|
|
TaskItem(int parallelLimit)
|
|
: m_type(Type::Limit)
|
|
, m_parallelLimit(parallelLimit) {}
|
|
TaskItem(WorkflowPolicy policy)
|
|
: m_type(Type::Policy)
|
|
, m_workflowPolicy(policy) {}
|
|
TaskItem(const TaskHandler &handler)
|
|
: m_type(Type::TaskHandler)
|
|
, m_taskHandler(handler) {}
|
|
TaskItem(const GroupHandler &handler)
|
|
: m_type(Type::GroupHandler)
|
|
, m_groupHandler(handler) {}
|
|
TaskItem(const TreeStorageBase &storage)
|
|
: m_type(Type::Storage)
|
|
, m_storageList{storage} {}
|
|
void addChildren(const QList<TaskItem> &children);
|
|
|
|
private:
|
|
Type m_type = Type::Group;
|
|
int m_parallelLimit = 1; // 0 means unlimited
|
|
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
|
TaskHandler m_taskHandler;
|
|
GroupHandler m_groupHandler;
|
|
QList<TreeStorageBase> m_storageList;
|
|
QList<TaskItem> m_children;
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT Group : public TaskItem
|
|
{
|
|
public:
|
|
Group(const QList<TaskItem> &children) { addChildren(children); }
|
|
Group(std::initializer_list<TaskItem> children) { addChildren(children); }
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT Storage : public TaskItem
|
|
{
|
|
public:
|
|
Storage(const TreeStorageBase &storage) : TaskItem(storage) { }
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem
|
|
{
|
|
public:
|
|
ParallelLimit(int parallelLimit) : TaskItem(qMax(parallelLimit, 0)) {}
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT Workflow : public TaskItem
|
|
{
|
|
public:
|
|
Workflow(WorkflowPolicy policy) : TaskItem(policy) {}
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT OnGroupSetup : public TaskItem
|
|
{
|
|
public:
|
|
OnGroupSetup(const GroupSimpleHandler &handler) : TaskItem({handler}) {}
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT OnGroupDone : public TaskItem
|
|
{
|
|
public:
|
|
OnGroupDone(const GroupSimpleHandler &handler) : TaskItem({{}, handler}) {}
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT OnGroupError : public TaskItem
|
|
{
|
|
public:
|
|
OnGroupError(const GroupSimpleHandler &handler) : TaskItem({{}, {}, handler}) {}
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT DynamicSetup : public TaskItem
|
|
{
|
|
public:
|
|
DynamicSetup(const GroupSetupHandler &handler) : TaskItem({{}, {}, {}, handler}) {}
|
|
};
|
|
|
|
QTCREATOR_UTILS_EXPORT extern ParallelLimit sequential;
|
|
QTCREATOR_UTILS_EXPORT extern ParallelLimit parallel;
|
|
QTCREATOR_UTILS_EXPORT extern Workflow stopOnError;
|
|
QTCREATOR_UTILS_EXPORT extern Workflow continueOnError;
|
|
QTCREATOR_UTILS_EXPORT extern Workflow stopOnDone;
|
|
QTCREATOR_UTILS_EXPORT extern Workflow continueOnDone;
|
|
QTCREATOR_UTILS_EXPORT extern Workflow optional;
|
|
|
|
template <typename Task>
|
|
class TaskAdapter : public TaskInterface
|
|
{
|
|
public:
|
|
using Type = Task;
|
|
TaskAdapter() = default;
|
|
Task *task() { return &m_task; }
|
|
const Task *task() const { return &m_task; }
|
|
private:
|
|
Task m_task;
|
|
};
|
|
|
|
template <typename Adapter>
|
|
class CustomTask : public TaskItem
|
|
{
|
|
public:
|
|
using Task = typename Adapter::Type;
|
|
using EndHandler = std::function<void(const Task &)>;
|
|
static Adapter *createAdapter() { return new Adapter; }
|
|
template <typename SetupFunction>
|
|
CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {})
|
|
: TaskItem({&createAdapter, wrapSetup(function), wrapEnd(done), wrapEnd(error)}) {}
|
|
|
|
private:
|
|
template<typename SetupFunction>
|
|
using IsDynamic = typename std::is_same<TaskAction,
|
|
std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
|
|
|
|
template<typename SetupFunction>
|
|
using IsVoid = typename std::is_same<void,
|
|
std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
|
|
|
|
template<typename SetupFunction, std::enable_if_t<IsDynamic<SetupFunction>::value, bool> = true>
|
|
static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) {
|
|
return [=](TaskInterface &taskInterface) {
|
|
Adapter &adapter = static_cast<Adapter &>(taskInterface);
|
|
return std::invoke(function, *adapter.task());
|
|
};
|
|
};
|
|
|
|
template<typename SetupFunction, std::enable_if_t<IsVoid<SetupFunction>::value, bool> = true>
|
|
static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) {
|
|
return [=](TaskInterface &taskInterface) {
|
|
Adapter &adapter = static_cast<Adapter &>(taskInterface);
|
|
std::invoke(function, *adapter.task());
|
|
return TaskAction::Continue;
|
|
};
|
|
};
|
|
|
|
template<typename SetupFunction, std::enable_if_t<!IsDynamic<SetupFunction>::value
|
|
&& !IsVoid<SetupFunction>::value, bool> = true>
|
|
static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&) {
|
|
static_assert(IsDynamic<SetupFunction>::value || IsVoid<SetupFunction>::value,
|
|
"Task setup handler needs to take (Task &) as an argument and has to return "
|
|
"void or TaskAction. The passed handler doesn't fulfill these requirements.");
|
|
return {};
|
|
};
|
|
|
|
static TaskEndHandler wrapEnd(const EndHandler &handler) {
|
|
if (!handler)
|
|
return {};
|
|
return [handler](const TaskInterface &taskInterface) {
|
|
const Adapter &adapter = static_cast<const Adapter &>(taskInterface);
|
|
handler(*adapter.task());
|
|
};
|
|
};
|
|
};
|
|
|
|
} // namespace Tasking
|
|
|
|
class TaskTreePrivate;
|
|
|
|
class QTCREATOR_UTILS_EXPORT TaskTree : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
TaskTree();
|
|
TaskTree(const Tasking::Group &root);
|
|
~TaskTree();
|
|
|
|
void setupRoot(const Tasking::Group &root);
|
|
|
|
void start();
|
|
void stop();
|
|
bool isRunning() const;
|
|
|
|
int taskCount() const;
|
|
int progressMaximum() const { return taskCount(); }
|
|
int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded
|
|
|
|
template <typename StorageStruct, typename StorageHandler>
|
|
void onStorageSetup(const Tasking::TreeStorage<StorageStruct> &storage,
|
|
const StorageHandler &handler) {
|
|
setupStorageHandler(storage, wrapHandler<StorageStruct>(handler), {});
|
|
}
|
|
template <typename StorageStruct, typename StorageHandler>
|
|
void onStorageDone(const Tasking::TreeStorage<StorageStruct> &storage,
|
|
const StorageHandler &handler) {
|
|
setupStorageHandler(storage, {}, wrapHandler<StorageStruct>(handler));
|
|
}
|
|
|
|
signals:
|
|
void started();
|
|
void done();
|
|
void errorOccurred();
|
|
void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
|
|
|
|
private:
|
|
using StorageVoidHandler = std::function<void(void *)>;
|
|
void setupStorageHandler(const Tasking::TreeStorageBase &storage,
|
|
StorageVoidHandler setupHandler,
|
|
StorageVoidHandler doneHandler);
|
|
template <typename StorageStruct>
|
|
StorageVoidHandler wrapHandler(const std::function<void(StorageStruct *)> &handler) {
|
|
if (!handler)
|
|
return {};
|
|
return [handler](void *voidStruct) {
|
|
StorageStruct *storageStruct = static_cast<StorageStruct *>(voidStruct);
|
|
handler(storageStruct);
|
|
};
|
|
}
|
|
|
|
friend class TaskTreePrivate;
|
|
TaskTreePrivate *d;
|
|
};
|
|
|
|
class QTCREATOR_UTILS_EXPORT TaskTreeAdapter : public Tasking::TaskAdapter<TaskTree>
|
|
{
|
|
public:
|
|
TaskTreeAdapter();
|
|
void start() final;
|
|
};
|
|
|
|
} // namespace Utils
|
|
|
|
#define QTC_DECLARE_CUSTOM_TASK(CustomTaskName, TaskAdapterClass)\
|
|
namespace Utils::Tasking { using CustomTaskName = CustomTask<TaskAdapterClass>; }
|
|
|
|
#define QTC_DECLARE_CUSTOM_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\
|
|
namespace Utils::Tasking {\
|
|
template <typename ...Args>\
|
|
using CustomTaskName = CustomTask<TaskAdapterClass<Args...>>;\
|
|
} // namespace Utils::Tasking
|
|
|
|
QTC_DECLARE_CUSTOM_TASK(Tree, Utils::TaskTreeAdapter);
|