BuildManager: Employ task tree for running

Task-number: QTCREATORBUG-29168
Change-Id: Ibdc1882f3a019855bc06e7a7e49e2ae6e3bf17f1
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Jarek Kobus
2023-07-17 08:07:30 +02:00
parent 86e11befca
commit a140a93067
3 changed files with 202 additions and 241 deletions

View File

@@ -260,31 +260,23 @@ public:
Internal::CompileOutputWindow *m_outputWindow = nullptr;
Internal::TaskWindow *m_taskWindow = nullptr;
QMetaObject::Connection m_scheduledBuild;
QList<BuildItem> m_pendingQueue;
QList<BuildItem> m_buildQueue;
int m_progress = 0;
int m_maxProgress = 0;
bool m_poppedUpTaskWindow = false;
bool m_running = false;
bool m_isDeploying = false;
// is set to true while canceling, so that nextBuildStep knows that the BuildStep finished because of canceling
bool m_skipDisabled = false;
bool m_canceling = false;
bool m_lastStepSucceeded = true;
bool m_allStepsSucceeded = true;
BuildStep *m_currentBuildStep = nullptr;
QString m_currentConfiguration;
// used to decide if we are building a project to decide when to emit buildStateChanged(Project *)
QHash<Project *, int> m_activeBuildSteps;
QHash<Target *, int> m_activeBuildStepsPerTarget;
QHash<ProjectConfiguration *, int> m_activeBuildStepsPerProjectConfiguration;
Project *m_previousBuildStepProject = nullptr;
// Progress reporting to the progress manager
QFutureInterface<void> *m_progressFutureInterface = nullptr;
QFutureWatcher<void> m_progressWatcher;
QPointer<FutureProgress> m_futureProgress;
std::unique_ptr<TaskTree> m_taskTree;
QElapsedTimer m_elapsed;
};
@@ -475,7 +467,7 @@ void BuildManager::aboutToRemoveProject(Project *p)
bool BuildManager::isBuilding()
{
// we are building even if we are not running yet
return !d->m_buildQueue.isEmpty() || d->m_running;
return !d->m_pendingQueue.isEmpty() || !d->m_buildQueue.isEmpty();
}
bool BuildManager::isDeploying()
@@ -506,20 +498,51 @@ QString BuildManager::displayNameForStepId(Id stepId)
return Tr::tr("Build");
}
void BuildManager::cleanupBuild()
{
const QList<BuildItem> buildQueue = d->m_buildQueue;
d->m_buildQueue.clear();
for (const BuildItem &item : buildQueue) {
decrementActiveBuildSteps(item.buildStep);
disconnect(item.buildStep, nullptr, m_instance, nullptr);
}
if (d->m_progressFutureInterface) {
d->m_progressFutureInterface->reportFinished();
d->m_progressWatcher.setFuture(QFuture<void>());
delete d->m_progressFutureInterface;
d->m_progressFutureInterface = nullptr;
}
d->m_progress = 0;
d->m_maxProgress = 0;
d->m_futureProgress = nullptr;
}
void BuildManager::cancel()
{
if (d->m_scheduledBuild) {
disconnect(d->m_scheduledBuild);
d->m_scheduledBuild = {};
clearBuildQueue();
if (!d->m_taskTree)
return;
d->m_taskTree.reset();
const QList<BuildItem> pendingQueue = d->m_pendingQueue;
d->m_pendingQueue.clear();
for (const BuildItem &item : pendingQueue) {
decrementActiveBuildSteps(item.buildStep);
disconnect(item.buildStep, nullptr, m_instance, nullptr);
}
if (d->m_running) {
if (d->m_canceling)
return;
d->m_canceling = true;
d->m_currentBuildStep->cancel();
d->m_poppedUpTaskWindow = false;
d->m_isDeploying = false;
if (d->m_progressFutureInterface) {
d->m_progressFutureInterface->setProgressValueAndText(100 * d->m_progress,
Tr::tr("Build/Deployment canceled"));
d->m_progressFutureInterface->reportCanceled();
}
cleanupBuild();
addToOutputWindow(Tr::tr("Canceled build/deployment."), BuildStep::OutputFormat::ErrorMessage);
emit m_instance->buildQueueFinished(false);
}
void BuildManager::updateTaskCount()
@@ -541,39 +564,6 @@ void BuildManager::finish()
QApplication::alert(ICore::dialogParent(), 3000);
}
void BuildManager::emitCancelMessage()
{
addToOutputWindow(Tr::tr("Canceled build/deployment."), BuildStep::OutputFormat::ErrorMessage);
}
void BuildManager::clearBuildQueue()
{
for (const BuildItem &item : std::as_const(d->m_buildQueue)) {
decrementActiveBuildSteps(item.buildStep);
disconnectOutput(item.buildStep);
}
d->m_buildQueue.clear();
d->m_running = false;
d->m_poppedUpTaskWindow = false;
d->m_isDeploying = false;
d->m_previousBuildStepProject = nullptr;
d->m_currentBuildStep = nullptr;
if (d->m_progressFutureInterface) {
d->m_progressFutureInterface->reportCanceled();
d->m_progressFutureInterface->reportFinished();
d->m_progressWatcher.setFuture(QFuture<void>());
delete d->m_progressFutureInterface;
d->m_progressFutureInterface = nullptr;
}
d->m_futureProgress = nullptr;
d->m_maxProgress = 0;
emit m_instance->buildQueueFinished(false);
}
void BuildManager::toggleOutputWindow()
{
d->m_outputWindow->toggle(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
@@ -600,58 +590,137 @@ bool BuildManager::tasksAvailable()
void BuildManager::startBuildQueue()
{
if (d->m_buildQueue.isEmpty()) {
emit m_instance->buildQueueFinished(true);
return;
}
if (compileOutputSettings().popUp())
d->m_outputWindow->popup(IOutputPane::NoModeSwitch);
const auto onAwaiterSetup = [](QSet<BuildSystem *> &buildSystems) {
// Delay if any of the involved build systems are currently parsing.
const auto buildSystems = transform<QSet<BuildSystem *>>(d->m_buildQueue,
[](const BuildItem &item) { return item.buildStep->buildSystem(); });
for (const BuildSystem * const bs : buildSystems) {
if (!bs || !bs->isParsing())
continue;
d->m_scheduledBuild = QObject::connect(bs, &BuildSystem::parsingFinished,
BuildManager::instance(),
[](bool parsingSuccess) {
if (!d->m_scheduledBuild)
return;
QObject::disconnect(d->m_scheduledBuild);
d->m_scheduledBuild = {};
if (parsingSuccess)
startBuildQueue();
else
clearBuildQueue();
}, Qt::QueuedConnection);
return;
buildSystems = transform<QSet<BuildSystem *>>(
d->m_buildQueue, [](const BuildItem &item) { return item.buildStep->buildSystem(); });
};
const GroupItem abortPolicy
= ProjectExplorerPlugin::projectExplorerSettings().abortBuildAllOnError
? stopOnError : continueOnError;
QList<GroupItem> topLevel { abortPolicy, ParserAwaiterTask(onAwaiterSetup) };
Project *lastProject = nullptr;
Target *lastTarget = nullptr;
QList<GroupItem> targetTasks;
d->m_progress = 0;
d->m_maxProgress = 0;
for (const BuildItem &item : std::as_const(d->m_buildQueue)) {
BuildStep *buildStep = item.buildStep;
Target *target = buildStep->target();
if (lastTarget != target) {
if (!targetTasks.isEmpty())
topLevel.append(Group(targetTasks));
targetTasks.clear();
lastTarget = target;
}
if (!d->m_running) {
d->m_elapsed.start();
Project *project = buildStep->project();
if (lastProject != project) {
targetTasks.append(Sync([projectName = buildStep->project()->displayName()] {
addToOutputWindow(Tr::tr("Running steps for project %1...")
.arg(projectName), BuildStep::OutputFormat::NormalMessage);
}));
lastProject = project;
}
if (!item.enabled) {
targetTasks.append(Sync([name = buildStep->displayName()] {
addToOutputWindow(Tr::tr("Skipping disabled step %1.")
.arg(name), BuildStep::OutputFormat::NormalMessage);
}));
continue;
}
++d->m_maxProgress;
const auto onRecipeSetup = [buildStep, name = item.name] {
d->m_outputWindow->reset();
buildStep->setupOutputFormatter(d->m_outputWindow->outputFormatter());
connect(buildStep, &BuildStep::progress, instance(), &BuildManager::progressChanged);
if (d->m_futureProgress)
d->m_futureProgress.data()->setTitle(name);
};
const auto onRecipeDone = [buildStep] {
disconnect(buildStep, &BuildStep::progress, instance(), nullptr);
d->m_outputWindow->flush();
++d->m_progress;
d->m_progressFutureInterface->setProgressValueAndText(
100 * d->m_progress, msgProgress(d->m_progress, d->m_maxProgress));
};
const auto onRecipeError = [buildStep, target, onRecipeDone] {
onRecipeDone();
const QString projectName = buildStep->project()->displayName();
const QString targetName = target->displayName();
addToOutputWindow(Tr::tr("Error while building/deploying project %1 (kit: %2)")
.arg(projectName, targetName), BuildStep::OutputFormat::Stderr);
const Tasks kitTasks = target->kit()->validate();
if (!kitTasks.isEmpty()) {
addToOutputWindow(Tr::tr("The kit %1 has configuration issues which might "
"be the root cause for this problem.")
.arg(targetName), BuildStep::OutputFormat::Stderr);
}
addToOutputWindow(Tr::tr("When executing step \"%1\"")
.arg(buildStep->displayName()), BuildStep::OutputFormat::Stderr);
};
const Group recipeGroup {
onGroupSetup(onRecipeSetup),
buildStep->runRecipe(),
onGroupDone(onRecipeDone),
onGroupError(onRecipeError),
};
targetTasks.append(recipeGroup);
}
if (!targetTasks.isEmpty())
topLevel.append(Group(targetTasks));
d->m_taskTree.reset(new TaskTree(Group{topLevel}));
const auto endHandler = [](bool success) {
d->m_taskTree.release()->deleteLater();
if (!success && d->m_progressFutureInterface)
d->m_progressFutureInterface->reportCanceled();
cleanupBuild();
if (d->m_pendingQueue.isEmpty()) {
d->m_poppedUpTaskWindow = false;
d->m_isDeploying = false;
}
emit m_instance->buildQueueFinished(success);
if (!d->m_pendingQueue.isEmpty()) {
d->m_buildQueue = d->m_pendingQueue;
d->m_pendingQueue.clear();
startBuildQueue();
}
};
connect(d->m_taskTree.get(), &TaskTree::done, instance(), [endHandler] { endHandler(true); });
connect(d->m_taskTree.get(), &TaskTree::errorOccurred, instance(),
[endHandler] { endHandler(false); });
// Progress Reporting
d->m_progressFutureInterface = new QFutureInterface<void>;
d->m_progressWatcher.setFuture(d->m_progressFutureInterface->future());
ProgressManager::setApplicationLabel(QString());
ProgressManager::setApplicationLabel({});
d->m_futureProgress = ProgressManager::addTask(d->m_progressFutureInterface->future(),
QString(), "ProjectExplorer.Task.Build",
{}, "ProjectExplorer.Task.Build",
ProgressManager::KeepOnFinish | ProgressManager::ShowInApplicationIcon);
connect(d->m_futureProgress.data(), &FutureProgress::clicked,
m_instance, &BuildManager::showBuildResults);
d->m_futureProgress.data()->setWidget(new Internal::BuildProgress(d->m_taskWindow));
d->m_futureProgress.data()->setStatusBarWidget(new Internal::BuildProgress(d->m_taskWindow,
d->m_futureProgress.data()->setWidget(new BuildProgress(d->m_taskWindow));
d->m_futureProgress.data()->setStatusBarWidget(new BuildProgress(d->m_taskWindow,
Qt::Horizontal));
d->m_progress = 0;
d->m_progressFutureInterface->setProgressRange(0, d->m_maxProgress * 100);
d->m_running = true;
d->m_allStepsSucceeded = true;
d->m_progressFutureInterface->reportStarted();
nextStep();
} else {
// Already running
d->m_progressFutureInterface->setProgressRange(0, d->m_maxProgress * 100);
d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, msgProgress(d->m_progress, d->m_maxProgress));
}
d->m_elapsed.start();
d->m_taskTree->start();
}
void BuildManager::showBuildResults()
@@ -684,127 +753,15 @@ void BuildManager::addToOutputWindow(const QString &string, BuildStep::OutputFor
d->m_outputWindow->appendText(stringToWrite, format);
}
void BuildManager::nextBuildQueue()
{
d->m_outputWindow->flush();
disconnectOutput(d->m_currentBuildStep);
decrementActiveBuildSteps(d->m_currentBuildStep);
if (d->m_canceling) {
d->m_canceling = false;
QTimer::singleShot(0, m_instance, &BuildManager::emitCancelMessage);
//TODO NBS fix in qtconcurrent
d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100,
Tr::tr("Build/Deployment canceled"));
clearBuildQueue();
return;
}
if (!d->m_skipDisabled)
++d->m_progress;
d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, msgProgress(d->m_progress, d->m_maxProgress));
const bool success = d->m_skipDisabled || d->m_lastStepSucceeded;
if (success) {
nextStep();
} else {
// Build Failure
d->m_allStepsSucceeded = false;
Target *t = d->m_currentBuildStep->target();
const QString projectName = d->m_currentBuildStep->project()->displayName();
const QString targetName = t->displayName();
addToOutputWindow(Tr::tr("Error while building/deploying project %1 (kit: %2)").arg(projectName, targetName), BuildStep::OutputFormat::Stderr);
const Tasks kitTasks = t->kit()->validate();
if (!kitTasks.isEmpty()) {
addToOutputWindow(Tr::tr("The kit %1 has configuration issues which might be the root cause for this problem.")
.arg(targetName), BuildStep::OutputFormat::Stderr);
}
addToOutputWindow(Tr::tr("When executing step \"%1\"").arg(d->m_currentBuildStep->displayName()), BuildStep::OutputFormat::Stderr);
bool abort = ProjectExplorerPlugin::projectExplorerSettings().abortBuildAllOnError;
if (!abort) {
while (!d->m_buildQueue.isEmpty()
&& d->m_buildQueue.front().buildStep->target() == t) {
BuildStep * const nextStepForFailedTarget = d->m_buildQueue.takeFirst().buildStep;
disconnectOutput(nextStepForFailedTarget);
decrementActiveBuildSteps(nextStepForFailedTarget);
}
if (d->m_buildQueue.isEmpty())
abort = true;
}
if (abort) {
// NBS TODO fix in qtconcurrent
d->m_progressFutureInterface->setProgressValueAndText(d->m_progress * 100,
Tr::tr("Error while building/deploying project %1 (kit: %2)")
.arg(projectName, targetName));
clearBuildQueue();
} else {
nextStep();
}
}
}
void BuildManager::progressChanged(int percent, const QString &text)
{
if (d->m_progressFutureInterface)
d->m_progressFutureInterface->setProgressValueAndText(percent + 100 * d->m_progress, text);
}
void BuildManager::nextStep()
{
if (!d->m_buildQueue.empty()) {
const BuildItem item = d->m_buildQueue.takeFirst();
d->m_currentBuildStep = item.buildStep;
d->m_skipDisabled = !item.enabled;
if (d->m_futureProgress)
d->m_futureProgress.data()->setTitle(item.name);
if (d->m_currentBuildStep->project() != d->m_previousBuildStepProject) {
const QString projectName = d->m_currentBuildStep->project()->displayName();
addToOutputWindow(Tr::tr("Running steps for project %1...")
.arg(projectName), BuildStep::OutputFormat::NormalMessage);
d->m_previousBuildStepProject = d->m_currentBuildStep->project();
}
if (d->m_skipDisabled) {
addToOutputWindow(Tr::tr("Skipping disabled step %1.")
.arg(d->m_currentBuildStep->displayName()), BuildStep::OutputFormat::NormalMessage);
nextBuildQueue();
return;
}
static const auto finishedHandler = [](bool success) {
d->m_outputWindow->flush();
d->m_lastStepSucceeded = success;
disconnect(d->m_currentBuildStep, nullptr, instance(), nullptr);
BuildManager::nextBuildQueue();
};
connect(d->m_currentBuildStep, &BuildStep::finished, instance(), finishedHandler);
connect(d->m_currentBuildStep, &BuildStep::progress,
instance(), &BuildManager::progressChanged);
d->m_outputWindow->reset();
d->m_currentBuildStep->setupOutputFormatter(d->m_outputWindow->outputFormatter());
d->m_currentBuildStep->run();
} else {
d->m_running = false;
d->m_poppedUpTaskWindow = false;
d->m_isDeploying = false;
d->m_previousBuildStepProject = nullptr;
d->m_progressFutureInterface->reportFinished();
d->m_progressWatcher.setFuture(QFuture<void>());
d->m_currentBuildStep = nullptr;
delete d->m_progressFutureInterface;
d->m_progressFutureInterface = nullptr;
d->m_maxProgress = 0;
emit m_instance->buildQueueFinished(d->m_allStepsSucceeded);
}
}
bool BuildManager::buildQueueAppend(const QList<BuildItem> &items, const QStringList &preambleMessage)
{
if (!d->m_running) {
if (!d->m_taskTree) {
d->m_outputWindow->clearContents();
if (ProjectExplorerPlugin::projectExplorerSettings().clearIssuesOnRebuild) {
TaskHub::clearTasks(Constants::TASK_CATEGORY_COMPILE);
@@ -819,33 +776,45 @@ bool BuildManager::buildQueueAppend(const QList<BuildItem> &items, const QString
QList<BuildStep *> connectedSteps;
int enabledCount = 0;
for (const BuildItem &item : items) {
connect(item.buildStep, &BuildStep::addTask, m_instance, &BuildManager::addToTaskWindow);
connect(item.buildStep, &BuildStep::addOutput, m_instance, &BuildManager::addToOutputWindow);
connectedSteps.append(item.buildStep);
BuildStep *buildStep = item.buildStep;
connect(buildStep, &BuildStep::addTask, m_instance, &BuildManager::addToTaskWindow);
connect(buildStep, &BuildStep::addOutput, m_instance, &BuildManager::addToOutputWindow);
connectedSteps.append(buildStep);
if (!item.enabled)
continue;
++enabledCount;
if (item.buildStep->init())
if (!isBuilding(buildStep) && buildStep->init())
continue;
// init() failed, print something for the user...
const QString projectName = item.buildStep->project()->displayName();
const QString targetName = item.buildStep->target()->displayName();
const QString projectName = buildStep->project()->displayName();
const QString targetName = buildStep->target()->displayName();
addToOutputWindow(Tr::tr("Error while building/deploying project %1 (kit: %2)")
.arg(projectName, targetName), BuildStep::OutputFormat::Stderr);
addToOutputWindow(Tr::tr("When executing step \"%1\"")
.arg(item.buildStep->displayName()), BuildStep::OutputFormat::Stderr);
.arg(buildStep->displayName()), BuildStep::OutputFormat::Stderr);
for (BuildStep *buildStep : std::as_const(connectedSteps))
disconnectOutput(buildStep);
connect(buildStep, nullptr, m_instance, nullptr);
d->m_outputWindow->popup(IOutputPane::NoModeSwitch);
return false;
}
d->m_buildQueue << items;
d->m_maxProgress += enabledCount;
for (const BuildItem &item : items)
incrementActiveBuildSteps(item.buildStep);
if (d->m_taskTree)
d->m_pendingQueue << items;
else
d->m_buildQueue = items;
if (d->m_buildQueue.isEmpty() && d->m_pendingQueue.isEmpty()) {
if (compileOutputSettings().popUp())
d->m_outputWindow->popup(IOutputPane::NoModeSwitch);
emit m_instance->buildQueueFinished(true);
return true;
}
for (const BuildItem &item : items)
incrementActiveBuildSteps(item.buildStep);
if (!d->m_taskTree)
startBuildQueue();
return true;
}
@@ -857,6 +826,7 @@ bool BuildManager::buildList(BuildStepList *bsl)
bool BuildManager::buildLists(const QList<BuildStepList *> &bsls, const QStringList &preambleMessage)
{
const bool wasDeploying = d->m_isDeploying;
QList<BuildItem> buildItems;
for (BuildStepList *list : bsls) {
const QString name = displayNameForStepId(list->id());
@@ -869,7 +839,7 @@ bool BuildManager::buildLists(const QList<BuildStepList *> &bsls, const QStringL
if (buildQueueAppend(buildItems, preambleMessage))
return true;
d->m_isDeploying = false;
d->m_isDeploying = wasDeploying;
return false;
}
@@ -905,8 +875,8 @@ bool BuildManager::isBuilding(const ProjectConfiguration *p)
bool BuildManager::isBuilding(BuildStep *step)
{
return (d->m_currentBuildStep == step) || Utils::anyOf(
d->m_buildQueue, [step](const BuildItem &item) { return item.buildStep == step; });
const auto checker = [step](const BuildItem &item) { return item.buildStep == step; };
return Utils::anyOf(d->m_buildQueue, checker) || Utils::anyOf(d->m_pendingQueue, checker);
}
template <class T> bool increment(QHash<T *, int> &hash, T *key)
@@ -956,10 +926,4 @@ void BuildManager::decrementActiveBuildSteps(BuildStep *bs)
emit m_instance->buildStateChanged(bs->project());
}
void BuildManager::disconnectOutput(BuildStep *bs)
{
disconnect(bs, &BuildStep::addTask, m_instance, nullptr);
disconnect(bs, &BuildStep::addOutput, m_instance, nullptr);
}
} // namespace ProjectExplorer

View File

@@ -78,25 +78,21 @@ signals:
void buildQueueFinished(bool success);
private:
static void cleanupBuild();
static void addToTaskWindow(const ProjectExplorer::Task &task, int linkedOutputLines, int skipLines);
static void addToOutputWindow(const QString &string, BuildStep::OutputFormat format,
BuildStep::OutputNewlineSetting newlineSettings = BuildStep::DoAppendNewline);
static void nextBuildQueue();
static void progressChanged(int percent, const QString &text);
static void emitCancelMessage();
static void showBuildResults();
static void updateTaskCount();
static void finish();
static void startBuildQueue();
static void nextStep();
static void clearBuildQueue();
static bool buildQueueAppend(const QList<BuildItem> &items,
const QStringList &preambleMessage = {});
static void incrementActiveBuildSteps(BuildStep *bs);
static void decrementActiveBuildSteps(BuildStep *bs);
static void disconnectOutput(BuildStep *bs);
};
} // namespace ProjectExplorer

View File

@@ -132,6 +132,7 @@ protected:
private:
using ProjectConfiguration::parent;
friend class BuildManager;
virtual Tasking::GroupItem runRecipe() = 0;
BuildStepList * const m_stepList;