1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qbs.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 #include "executor.h"
40 
41 #include "buildgraph.h"
42 #include "emptydirectoriesremover.h"
43 #include "environmentscriptrunner.h"
44 #include "productbuilddata.h"
45 #include "projectbuilddata.h"
46 #include "cycledetector.h"
47 #include "executorjob.h"
48 #include "inputartifactscanner.h"
49 #include "productinstaller.h"
50 #include "rescuableartifactdata.h"
51 #include "rulecommands.h"
52 #include "rulenode.h"
53 #include "rulesevaluationcontext.h"
54 #include "transformerchangetracking.h"
55 
56 #include <buildgraph/transformer.h>
57 #include <language/language.h>
58 #include <language/propertymapinternal.h>
59 #include <language/scriptengine.h>
60 #include <logging/categories.h>
61 #include <logging/translator.h>
62 #include <tools/error.h>
63 #include <tools/fileinfo.h>
64 #include <tools/preferences.h>
65 #include <tools/profiling.h>
66 #include <tools/progressobserver.h>
67 #include <tools/qbsassert.h>
68 #include <tools/qttools.h>
69 #include <tools/settings.h>
70 #include <tools/stringconstants.h>
71 
72 #include <QtCore/qdir.h>
73 #include <QtCore/qtimer.h>
74 
75 #include <algorithm>
76 #include <climits>
77 #include <iterator>
78 #include <utility>
79 
80 namespace qbs {
81 namespace Internal {
82 
operator ()(const BuildGraphNode * x,const BuildGraphNode * y) const83 bool Executor::ComparePriority::operator() (const BuildGraphNode *x, const BuildGraphNode *y) const
84 {
85     return x->product->buildData->buildPriority() < y->product->buildData->buildPriority();
86 }
87 
88 
Executor(Logger logger,QObject * parent)89 Executor::Executor(Logger logger, QObject *parent)
90     : QObject(parent)
91     , m_productInstaller(nullptr)
92     , m_logger(std::move(logger))
93     , m_progressObserver(nullptr)
94     , m_state(ExecutorIdle)
95     , m_cancelationTimer(new QTimer(this))
96 {
97     m_inputArtifactScanContext = new InputArtifactScannerContext;
98     m_cancelationTimer->setSingleShot(false);
99     m_cancelationTimer->setInterval(1000);
100     connect(m_cancelationTimer, &QTimer::timeout, this, &Executor::checkForCancellation);
101 }
102 
~Executor()103 Executor::~Executor()
104 {
105     // jobs must be destroyed before deleting the m_inputArtifactScanContext
106     m_allJobs.clear();
107     delete m_inputArtifactScanContext;
108     delete m_productInstaller;
109 }
110 
recursiveFileTime(const QString & filePath) const111 FileTime Executor::recursiveFileTime(const QString &filePath) const
112 {
113     FileTime newest;
114     FileInfo fileInfo(filePath);
115     if (!fileInfo.exists()) {
116         const QString nativeFilePath = QDir::toNativeSeparators(filePath);
117         m_logger.qbsWarning() << Tr::tr("File '%1' not found.").arg(nativeFilePath);
118         return newest;
119     }
120     newest = std::max(fileInfo.lastModified(), fileInfo.lastStatusChange());
121     if (!fileInfo.isDir())
122         return newest;
123     const QStringList dirContents = QDir(filePath)
124             .entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
125     for (const QString &curFileName : dirContents) {
126         const FileTime ft = recursiveFileTime(filePath + QLatin1Char('/') + curFileName);
127         if (ft > newest)
128             newest = ft;
129     }
130     return newest;
131 }
132 
retrieveSourceFileTimestamp(Artifact * artifact) const133 void Executor::retrieveSourceFileTimestamp(Artifact *artifact) const
134 {
135     QBS_CHECK(artifact->artifactType == Artifact::SourceFile);
136 
137     if (m_buildOptions.changedFiles().empty())
138         artifact->setTimestamp(recursiveFileTime(artifact->filePath()));
139     else if (m_buildOptions.changedFiles().contains(artifact->filePath()))
140         artifact->setTimestamp(FileTime::currentTime());
141     else if (!artifact->timestamp().isValid())
142         artifact->setTimestamp(recursiveFileTime(artifact->filePath()));
143 
144     artifact->timestampRetrieved = true;
145     if (!artifact->timestamp().isValid())
146         throw ErrorInfo(Tr::tr("Source file '%1' has disappeared.").arg(artifact->filePath()));
147 }
148 
build()149 void Executor::build()
150 {
151     try {
152         m_partialBuild = size_t(m_productsToBuild.size()) != m_allProducts.size();
153         doBuild();
154     } catch (const ErrorInfo &e) {
155         handleError(e);
156     }
157 }
158 
setProject(const TopLevelProjectPtr & project)159 void Executor::setProject(const TopLevelProjectPtr &project)
160 {
161     m_project = project;
162     m_allProducts = project->allProducts();
163     m_projectsByName.clear();
164     m_projectsByName.insert(std::make_pair(project->name, project.get()));
165     for (const ResolvedProjectPtr &p : project->allSubProjects())
166         m_projectsByName.insert(std::make_pair(p->name, p.get()));
167 }
168 
setProducts(const QVector<ResolvedProductPtr> & productsToBuild)169 void Executor::setProducts(const QVector<ResolvedProductPtr> &productsToBuild)
170 {
171     m_productsToBuild = productsToBuild;
172     m_productsByName.clear();
173     for (const ResolvedProductPtr &p : productsToBuild)
174         m_productsByName.insert(std::make_pair(p->uniqueName(), p.get()));
175 }
176 
177 class ProductPrioritySetter
178 {
179     const std::vector<ResolvedProductPtr> &m_allProducts;
180     unsigned int m_priority = 0;
181     Set<ResolvedProductPtr> m_seenProducts;
182 public:
ProductPrioritySetter(const std::vector<ResolvedProductPtr> & allProducts)183     ProductPrioritySetter(const std::vector<ResolvedProductPtr> &allProducts) // TODO: Use only products to build?
184         : m_allProducts(allProducts)
185     {
186     }
187 
apply()188     void apply()
189     {
190         Set<ResolvedProductPtr> allDependencies;
191         for (const ResolvedProductPtr &product : m_allProducts) {
192             for (const ResolvedProductPtr &dep : product->dependencies)
193                 allDependencies += dep;
194         }
195         const Set<ResolvedProductPtr> rootProducts
196                 = rangeTo<Set<ResolvedProductPtr>>(m_allProducts) - allDependencies;
197         m_priority = UINT_MAX;
198         m_seenProducts.clear();
199         for (const ResolvedProductPtr &rootProduct : rootProducts)
200             traverse(rootProduct);
201     }
202 
203 private:
traverse(const ResolvedProductPtr & product)204     void traverse(const ResolvedProductPtr &product)
205     {
206         if (!m_seenProducts.insert(product).second)
207             return;
208         for (const ResolvedProductPtr &dependency : qAsConst(product->dependencies))
209             traverse(dependency);
210         if (!product->buildData)
211             return;
212         product->buildData->setBuildPriority(m_priority--);
213     }
214 };
215 
doBuild()216 void Executor::doBuild()
217 {
218     if (m_buildOptions.maxJobCount() <= 0) {
219         m_buildOptions.setMaxJobCount(BuildOptions::defaultMaxJobCount());
220         qCDebug(lcExec) << "max job count not explicitly set, using value of"
221                         << m_buildOptions.maxJobCount();
222     }
223     QBS_CHECK(m_state == ExecutorIdle);
224     m_leaves = Leaves();
225     m_error.clear();
226     m_explicitlyCanceled = false;
227     m_activeFileTags = FileTags::fromStringList(m_buildOptions.activeFileTags());
228     m_tagsOfFilesToConsider.clear();
229     m_tagsNeededForFilesToConsider.clear();
230     m_productsOfFilesToConsider.clear();
231     m_artifactsRemovedFromDisk.clear();
232     m_jobCountPerPool.clear();
233 
234     setupJobLimits();
235 
236     // TODO: The "filesToConsider" thing is badly designed; we should know exactly which artifact
237     //       it is. Remove this from the BuildOptions class and introduce Project::buildSomeFiles()
238     //       instead.
239     const QStringList &filesToConsider = m_buildOptions.filesToConsider();
240     if (!filesToConsider.empty()) {
241         for (const QString &fileToConsider : filesToConsider) {
242             const auto &files = m_project->buildData->lookupFiles(fileToConsider);
243             for (const FileResourceBase * const file : files) {
244                 if (file->fileType() != FileResourceBase::FileTypeArtifact)
245                     continue;
246                 auto const artifact = static_cast<const Artifact *>(file);
247                 if (contains(m_productsToBuild, artifact->product.lock())) {
248                     m_tagsOfFilesToConsider.unite(artifact->fileTags());
249                     m_productsOfFilesToConsider << artifact->product.lock();
250                 }
251             }
252         }
253     }
254 
255     setState(ExecutorRunning);
256 
257     if (m_productsToBuild.empty()) {
258         qCDebug(lcExec) << "No products to build, finishing.";
259         QTimer::singleShot(0, this, &Executor::finish); // Don't call back on the caller.
260         return;
261     }
262 
263     doSanityChecks();
264     QBS_CHECK(!m_project->buildData->evaluationContext);
265     m_project->buildData->evaluationContext = std::make_shared<RulesEvaluationContext>(m_logger);
266     m_evalContext = m_project->buildData->evaluationContext;
267 
268     m_elapsedTimeRules = m_elapsedTimeScanners = m_elapsedTimeInstalling = 0;
269     m_evalContext->engine()->enableProfiling(m_buildOptions.logElapsedTime());
270 
271     InstallOptions installOptions;
272     installOptions.setDryRun(m_buildOptions.dryRun());
273     installOptions.setInstallRoot(m_productsToBuild.front()->moduleProperties
274             ->qbsPropertyValue(StringConstants::installRootProperty()).toString());
275     installOptions.setKeepGoing(m_buildOptions.keepGoing());
276     m_productInstaller = new ProductInstaller(m_project, m_productsToBuild, installOptions,
277                                               m_progressObserver, m_logger);
278     if (m_buildOptions.removeExistingInstallation())
279         m_productInstaller->removeInstallRoot();
280 
281     addExecutorJobs();
282     syncFileDependencies();
283     prepareAllNodes();
284     prepareProducts();
285     setupRootNodes();
286     prepareReachableNodes();
287     setupProgressObserver();
288     initLeaves();
289     if (!scheduleJobs()) {
290         qCDebug(lcExec) << "Nothing to do at all, finishing.";
291         QTimer::singleShot(0, this, &Executor::finish); // Don't call back on the caller.
292     }
293     if (m_progressObserver)
294         m_cancelationTimer->start();
295 }
296 
setBuildOptions(const BuildOptions & buildOptions)297 void Executor::setBuildOptions(const BuildOptions &buildOptions)
298 {
299     m_buildOptions = buildOptions;
300 }
301 
302 
initLeaves()303 void Executor::initLeaves()
304 {
305     updateLeaves(m_roots);
306 }
307 
updateLeaves(const NodeSet & nodes)308 void Executor::updateLeaves(const NodeSet &nodes)
309 {
310     NodeSet seenNodes;
311     for (BuildGraphNode * const node : nodes)
312         updateLeaves(node, seenNodes);
313 }
314 
updateLeaves(BuildGraphNode * node,NodeSet & seenNodes)315 void Executor::updateLeaves(BuildGraphNode *node, NodeSet &seenNodes)
316 {
317     if (!seenNodes.insert(node).second)
318         return;
319 
320     // Artifacts that appear in the build graph after
321     // prepareBuildGraph() has been called, must be initialized.
322     if (node->buildState == BuildGraphNode::Untouched) {
323         node->buildState = BuildGraphNode::Buildable;
324         if (node->type() == BuildGraphNode::ArtifactNodeType) {
325             auto const artifact = static_cast<Artifact *>(node);
326             if (artifact->artifactType == Artifact::SourceFile)
327                 retrieveSourceFileTimestamp(artifact);
328         }
329     }
330 
331     bool isLeaf = true;
332     for (BuildGraphNode *child : qAsConst(node->children)) {
333         if (child->buildState != BuildGraphNode::Built) {
334             isLeaf = false;
335             updateLeaves(child, seenNodes);
336         }
337     }
338 
339     if (isLeaf) {
340         qCDebug(lcExec).noquote() << "adding leaf" << node->toString();
341         m_leaves.push(node);
342     }
343 }
344 
345 // Returns true if some artifacts are still waiting to be built or currently building.
scheduleJobs()346 bool Executor::scheduleJobs()
347 {
348     QBS_CHECK(m_state == ExecutorRunning);
349     std::vector<BuildGraphNode *> delayedLeaves;
350     while (!m_leaves.empty() && !m_availableJobs.empty()) {
351         BuildGraphNode * const nodeToBuild = m_leaves.top();
352         m_leaves.pop();
353 
354         switch (nodeToBuild->buildState) {
355         case BuildGraphNode::Untouched:
356             QBS_ASSERT(!"untouched node in leaves list",
357                        qDebug("%s", qPrintable(nodeToBuild->toString())));
358             break;
359         case BuildGraphNode::Buildable: // This is the only state in which we want to build a node.
360             // TODO: It's a bit annoying that we have to check this here already, when we
361             //       don't know whether the transformer needs to run at all. Investigate
362             //       moving the whole job allocation logic to runTransformer().
363             if (schedulingBlockedByJobLimit(nodeToBuild)) {
364                 qCDebug(lcExec).noquote() << "node delayed due to occupied job pool:"
365                                           << nodeToBuild->toString();
366                 delayedLeaves.push_back(nodeToBuild);
367             } else {
368                 nodeToBuild->accept(this);
369             }
370             break;
371         case BuildGraphNode::Building:
372             qCDebug(lcExec).noquote() << nodeToBuild->toString();
373             qCDebug(lcExec) << "node is currently being built. Skipping.";
374             break;
375         case BuildGraphNode::Built:
376             qCDebug(lcExec).noquote() << nodeToBuild->toString();
377             qCDebug(lcExec) << "node already built. Skipping.";
378             break;
379         }
380     }
381     for (BuildGraphNode * const delayedLeaf : delayedLeaves)
382         m_leaves.push(delayedLeaf);
383     return !m_leaves.empty() || !m_processingJobs.empty();
384 }
385 
schedulingBlockedByJobLimit(const BuildGraphNode * node)386 bool Executor::schedulingBlockedByJobLimit(const BuildGraphNode *node)
387 {
388     if (node->type() != BuildGraphNode::ArtifactNodeType)
389         return false;
390     const auto artifact = static_cast<const Artifact *>(node);
391     if (artifact->artifactType == Artifact::SourceFile)
392         return false;
393 
394     const Transformer * const transformer = artifact->transformer.get();
395     for (const QString &jobPool : transformer->jobPools()) {
396         const int currentJobCount = m_jobCountPerPool[jobPool];
397         if (currentJobCount == 0)
398             continue;
399         const auto jobLimitIsExceeded = [currentJobCount, jobPool, this](const Transformer *t) {
400             const int maxJobCount = m_jobLimitsPerProduct.at(t->product().get())
401                     .getLimit(jobPool);
402             return maxJobCount > 0 && currentJobCount >= maxJobCount;
403         };
404 
405         // Different products can set different limits. The effective limit is the minimum of what
406         // is set in this transformer's product and in the products of all currently
407         // running transformers.
408         if (jobLimitIsExceeded(transformer))
409             return true;
410         const auto runningJobs = m_processingJobs.keys();
411         for (const ExecutorJob * const runningJob : runningJobs) {
412             if (!runningJob->jobPools().contains(jobPool))
413                 continue;
414             const Transformer * const runningTransformer = runningJob->transformer();
415             if (!runningTransformer)
416                 continue; // This can happen if the ExecutorJob has already finished.
417             if (runningTransformer->product() == transformer->product())
418                 continue; // We have already checked this product's job limit.
419             if (jobLimitIsExceeded(runningTransformer))
420                 return true;
421         }
422     }
423     return false;
424 }
425 
isUpToDate(Artifact * artifact) const426 bool Executor::isUpToDate(Artifact *artifact) const
427 {
428     QBS_CHECK(artifact->artifactType == Artifact::Generated);
429 
430     qCDebug(lcUpToDateCheck) << "check" << artifact->filePath()
431                              << artifact->timestamp().toString();
432 
433     if (m_buildOptions.forceTimestampCheck()) {
434         artifact->setTimestamp(FileInfo(artifact->filePath()).lastModified());
435         qCDebug(lcUpToDateCheck) << "timestamp retrieved from filesystem:"
436                                  << artifact->timestamp().toString();
437     }
438 
439     if (!artifact->timestamp().isValid()) {
440         qCDebug(lcUpToDateCheck) << "invalid timestamp. Out of date.";
441         return false;
442     }
443 
444     for (Artifact *childArtifact : filterByType<Artifact>(artifact->children)) {
445         QBS_CHECK(!childArtifact->alwaysUpdated || childArtifact->timestamp().isValid());
446         qCDebug(lcUpToDateCheck) << "child timestamp"
447                                  << childArtifact->timestamp().toString()
448                                  << childArtifact->filePath();
449         if (artifact->timestamp() < childArtifact->timestamp())
450             return false;
451     }
452 
453     for (FileDependency *fileDependency : qAsConst(artifact->fileDependencies)) {
454         if (!fileDependency->timestamp().isValid()) {
455             qCDebug(lcUpToDateCheck) << "file dependency doesn't exist"
456                                      << fileDependency->filePath();
457             return false;
458         }
459         qCDebug(lcUpToDateCheck) << "file dependency timestamp"
460                                  << fileDependency->timestamp().toString()
461                                  << fileDependency->filePath();
462         if (artifact->timestamp() < fileDependency->timestamp())
463             return false;
464     }
465 
466     return true;
467 }
468 
mustExecuteTransformer(const TransformerPtr & transformer) const469 bool Executor::mustExecuteTransformer(const TransformerPtr &transformer) const
470 {
471     if (transformer->alwaysRun)
472         return true;
473     if (transformer->markedForRerun) {
474         qCDebug(lcUpToDateCheck) << "explicitly marked for re-run.";
475         return true;
476     }
477 
478     bool hasAlwaysUpdatedArtifacts = false;
479     bool hasUpToDateNotAlwaysUpdatedArtifacts = false;
480     for (Artifact *artifact : qAsConst(transformer->outputs)) {
481         if (isUpToDate(artifact)) {
482             if (artifact->alwaysUpdated)
483                 hasAlwaysUpdatedArtifacts = true;
484             else
485                 hasUpToDateNotAlwaysUpdatedArtifacts = true;
486         } else if (artifact->alwaysUpdated || m_buildOptions.forceTimestampCheck()) {
487             return true;
488         }
489     }
490 
491     if (commandsNeedRerun(transformer.get(), transformer->product().get(), m_productsByName,
492                           m_projectsByName)) {
493         return true;
494     }
495 
496     // If all artifacts in a transformer have "alwaysUpdated" set to false, that transformer is
497     // run if and only if *all* of them are out of date.
498     return !hasAlwaysUpdatedArtifacts && !hasUpToDateNotAlwaysUpdatedArtifacts;
499 }
500 
buildArtifact(Artifact * artifact)501 void Executor::buildArtifact(Artifact *artifact)
502 {
503     qCDebug(lcExec) << relativeArtifactFileName(artifact);
504 
505     QBS_CHECK(artifact->buildState == BuildGraphNode::Buildable);
506 
507     if (artifact->artifactType != Artifact::SourceFile && !checkNodeProduct(artifact))
508         return;
509 
510     // skip artifacts without transformer
511     if (artifact->artifactType != Artifact::Generated) {
512         // For source artifacts, that were not reachable when initializing the build, we must
513         // retrieve timestamps. This can happen, if a dependency that's added during the build
514         // makes the source artifact reachable.
515         if (artifact->artifactType == Artifact::SourceFile && !artifact->timestampRetrieved)
516             retrieveSourceFileTimestamp(artifact);
517 
518         qCDebug(lcExec) << "artifact type" << toString(artifact->artifactType) << "Skipping.";
519         finishArtifact(artifact);
520         return;
521     }
522 
523     // Every generated artifact must have a transformer.
524     QBS_CHECK(artifact->transformer);
525     potentiallyRunTransformer(artifact->transformer);
526 }
527 
executeRuleNode(RuleNode * ruleNode)528 void Executor::executeRuleNode(RuleNode *ruleNode)
529 {
530     AccumulatingTimer rulesTimer(m_buildOptions.logElapsedTime() ? &m_elapsedTimeRules : nullptr);
531 
532     if (!checkNodeProduct(ruleNode))
533         return;
534 
535     QBS_CHECK(!m_evalContext->engine()->isActive());
536 
537     RuleNode::ApplicationResult result;
538     ruleNode->apply(m_logger, m_productsByName, m_projectsByName, &result);
539     updateLeaves(result.createdArtifacts);
540     updateLeaves(result.invalidatedArtifacts);
541     m_artifactsRemovedFromDisk << result.removedArtifacts;
542 
543     if (m_progressObserver) {
544         const int transformerCount = ruleNode->transformerCount();
545         if (transformerCount == 0) {
546             m_progressObserver->incrementProgressValue();
547         } else {
548             m_pendingTransformersPerRule.insert(std::make_pair(ruleNode->rule().get(),
549                                                                transformerCount));
550         }
551     }
552 
553     finishNode(ruleNode);
554 }
555 
finishJob(ExecutorJob * job,bool success)556 void Executor::finishJob(ExecutorJob *job, bool success)
557 {
558     QBS_CHECK(job);
559     QBS_CHECK(m_state != ExecutorIdle);
560 
561     const JobMap::Iterator it = m_processingJobs.find(job);
562     QBS_CHECK(it != m_processingJobs.end());
563     const TransformerPtr transformer = it.value();
564     m_processingJobs.erase(it);
565     m_availableJobs.push_back(job);
566     updateJobCounts(transformer.get(), -1);
567     if (success) {
568         m_project->buildData->setDirty();
569         for (Artifact * const artifact : qAsConst(transformer->outputs)) {
570             if (artifact->alwaysUpdated) {
571                 artifact->setTimestamp(FileTime::currentTime());
572                 for (Artifact * const parent : artifact->parentArtifacts())
573                     parent->transformer->markedForRerun = true;
574                 if (m_buildOptions.forceOutputCheck()
575                         && !m_buildOptions.dryRun() && !FileInfo(artifact->filePath()).exists()) {
576                     if (transformer->rule) {
577                         if (!transformer->rule->name.isEmpty()) {
578                             throw ErrorInfo(tr("Rule '%1' declares artifact '%2', "
579                                                "but the artifact was not produced.")
580                                             .arg(transformer->rule->name, artifact->filePath()));
581                         }
582                         throw ErrorInfo(tr("Rule declares artifact '%1', "
583                                            "but the artifact was not produced.")
584                                         .arg(artifact->filePath()));
585                     }
586                     throw ErrorInfo(tr("Transformer declares artifact '%1', "
587                                        "but the artifact was not produced.")
588                                     .arg(artifact->filePath()));
589                 }
590             } else {
591                 artifact->setTimestamp(FileInfo(artifact->filePath()).lastModified());
592             }
593         }
594         finishTransformer(transformer);
595     }
596 
597     if (!success && !m_buildOptions.keepGoing())
598         cancelJobs();
599 
600     if (m_state == ExecutorRunning && m_progressObserver && m_progressObserver->canceled()) {
601         qCDebug(lcExec) << "Received cancel request; canceling build.";
602         m_explicitlyCanceled = true;
603         cancelJobs();
604     }
605 
606     if (m_state == ExecutorCanceling) {
607         if (m_processingJobs.empty()) {
608             qCDebug(lcExec) << "All pending jobs are done, finishing.";
609             finish();
610         }
611         return;
612     }
613 
614     if (!scheduleJobs()) {
615         qCDebug(lcExec) << "Nothing left to build; finishing.";
616         finish();
617     }
618 }
619 
allChildrenBuilt(BuildGraphNode * node)620 static bool allChildrenBuilt(BuildGraphNode *node)
621 {
622     return std::all_of(node->children.cbegin(), node->children.cend(),
623                        std::mem_fn(&BuildGraphNode::isBuilt));
624 }
625 
finishNode(BuildGraphNode * leaf)626 void Executor::finishNode(BuildGraphNode *leaf)
627 {
628     leaf->buildState = BuildGraphNode::Built;
629     for (BuildGraphNode * const parent : qAsConst(leaf->parents)) {
630         if (parent->buildState != BuildGraphNode::Buildable) {
631             qCDebug(lcExec).noquote() << "parent" << parent->toString()
632                                       << "build state:" << toString(parent->buildState);
633             continue;
634         }
635 
636         if (allChildrenBuilt(parent)) {
637             m_leaves.push(parent);
638             qCDebug(lcExec).noquote() << "finishNode adds leaf"
639                                       << parent->toString() << toString(parent->buildState);
640         } else {
641             qCDebug(lcExec).noquote() << "parent" << parent->toString()
642                                       << "build state:" << toString(parent->buildState);
643         }
644     }
645 }
646 
finishArtifact(Artifact * leaf)647 void Executor::finishArtifact(Artifact *leaf)
648 {
649     QBS_CHECK(leaf);
650     qCDebug(lcExec) << "finishArtifact" << relativeArtifactFileName(leaf);
651     finishNode(leaf);
652 }
653 
configString() const654 QString Executor::configString() const
655 {
656     return tr(" for configuration %1").arg(m_project->id());
657 }
658 
transformerHasMatchingOutputTags(const TransformerConstPtr & transformer) const659 bool Executor::transformerHasMatchingOutputTags(const TransformerConstPtr &transformer) const
660 {
661     if (m_activeFileTags.empty())
662         return true; // No filtering requested.
663 
664     return std::any_of(transformer->outputs.cbegin(), transformer->outputs.cend(),
665                        [this](const Artifact *a) { return artifactHasMatchingOutputTags(a); });
666 }
667 
artifactHasMatchingOutputTags(const Artifact * artifact) const668 bool Executor::artifactHasMatchingOutputTags(const Artifact *artifact) const
669 {
670     return m_activeFileTags.intersects(artifact->fileTags())
671             || m_tagsNeededForFilesToConsider.intersects(artifact->fileTags());
672 }
673 
transformerHasMatchingInputFiles(const TransformerConstPtr & transformer) const674 bool Executor::transformerHasMatchingInputFiles(const TransformerConstPtr &transformer) const
675 {
676     if (m_buildOptions.filesToConsider().empty())
677         return true; // No filtering requested.
678     if (!m_productsOfFilesToConsider.contains(transformer->product()))
679         return false;
680     if (transformer->inputs.empty())
681         return true;
682     for (const Artifact * const input : qAsConst(transformer->inputs)) {
683         const auto files = m_buildOptions.filesToConsider();
684         for (const QString &filePath : files) {
685             if (input->filePath() == filePath
686                     || input->fileTags().intersects(m_tagsNeededForFilesToConsider)) {
687                 return true;
688             }
689         }
690     }
691 
692     return false;
693 }
694 
setupJobLimits()695 void Executor::setupJobLimits()
696 {
697     Settings settings(m_buildOptions.settingsDirectory());
698     for (const auto &p : qAsConst(m_productsToBuild)) {
699         const Preferences prefs(&settings, p->profile());
700         const JobLimits &jobLimitsFromSettings = prefs.jobLimits();
701         JobLimits effectiveJobLimits;
702         if (m_buildOptions.projectJobLimitsTakePrecedence()) {
703             effectiveJobLimits.update(jobLimitsFromSettings).update(m_buildOptions.jobLimits())
704                     .update(p->jobLimits);
705         } else {
706             effectiveJobLimits.update(p->jobLimits).update(jobLimitsFromSettings)
707                     .update(m_buildOptions.jobLimits());
708         }
709         m_jobLimitsPerProduct.insert(std::make_pair(p.get(), effectiveJobLimits));
710     }
711 }
712 
updateJobCounts(const Transformer * transformer,int diff)713 void Executor::updateJobCounts(const Transformer *transformer, int diff)
714 {
715     for (const QString &jobPool : transformer->jobPools())
716         m_jobCountPerPool[jobPool] += diff;
717 }
718 
cancelJobs()719 void Executor::cancelJobs()
720 {
721     if (m_state == ExecutorCanceling)
722         return;
723     qCDebug(lcExec) << "Canceling all jobs.";
724     setState(ExecutorCanceling);
725     const auto jobs = m_processingJobs.keys();
726     for (ExecutorJob *job : jobs)
727         job->cancel();
728 }
729 
setupProgressObserver()730 void Executor::setupProgressObserver()
731 {
732     if (!m_progressObserver)
733         return;
734     int totalEffort = 1; // For the effort after the last rule application;
735     for (const auto &product : qAsConst(m_productsToBuild)) {
736         QBS_CHECK(product->buildData);
737         const auto filtered = filterByType<RuleNode>(product->buildData->allNodes());
738         totalEffort += std::distance(filtered.begin(), filtered.end());
739     }
740     m_progressObserver->initialize(tr("Building%1").arg(configString()), totalEffort);
741 }
742 
doSanityChecks()743 void Executor::doSanityChecks()
744 {
745     QBS_CHECK(m_project);
746     QBS_CHECK(!m_productsToBuild.empty());
747     for (const auto &product : qAsConst(m_productsToBuild)) {
748         QBS_CHECK(product->buildData);
749         QBS_CHECK(product->topLevelProject() == m_project.get());
750     }
751 }
752 
handleError(const ErrorInfo & error)753 void Executor::handleError(const ErrorInfo &error)
754 {
755     const auto items = error.items();
756     for (const ErrorItem &ei : items)
757         m_error.append(ei);
758     if (m_processingJobs.empty())
759         finish();
760     else
761         cancelJobs();
762 }
763 
addExecutorJobs()764 void Executor::addExecutorJobs()
765 {
766     const int count = m_buildOptions.maxJobCount();
767     qCDebug(lcExec) << "preparing executor for" << count << "jobs in parallel";
768     m_allJobs.reserve(count);
769     m_availableJobs.reserve(count);
770     for (int i = 1; i <= count; i++) {
771         m_allJobs.push_back(std::make_unique<ExecutorJob>(m_logger));
772         const auto job = m_allJobs.back().get();
773         job->setMainThreadScriptEngine(m_evalContext->engine());
774         job->setObjectName(QStringLiteral("J%1").arg(i));
775         job->setDryRun(m_buildOptions.dryRun());
776         job->setEchoMode(m_buildOptions.echoMode());
777         m_availableJobs.push_back(job);
778         connect(job, &ExecutorJob::reportCommandDescription,
779                 this, &Executor::reportCommandDescription);
780         connect(job, &ExecutorJob::reportProcessResult, this, &Executor::reportProcessResult);
781         connect(job, &ExecutorJob::finished,
782                 this, &Executor::onJobFinished, Qt::QueuedConnection);
783     }
784 }
785 
rescueOldBuildData(Artifact * artifact,bool * childrenAdded=nullptr)786 void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = nullptr)
787 {
788     if (childrenAdded)
789         *childrenAdded = false;
790     if (!artifact->oldDataPossiblyPresent)
791         return;
792     artifact->oldDataPossiblyPresent = false;
793     if (artifact->artifactType != Artifact::Generated)
794         return;
795 
796     ResolvedProduct * const product = artifact->product.get();
797     const RescuableArtifactData rad
798             = product->buildData->removeFromRescuableArtifactData(artifact->filePath());
799     if (!rad.isValid())
800         return;
801     qCDebug(lcBuildGraph) << "Attempting to rescue data of artifact" << artifact->fileName();
802 
803     std::vector<Artifact *> childrenToConnect;
804     bool canRescue = artifact->transformer->commands == rad.commands;
805     if (canRescue) {
806         ResolvedProductPtr pseudoProduct = ResolvedProduct::create();
807         for (const RescuableArtifactData::ChildData &cd : rad.children) {
808             pseudoProduct->name = cd.productName;
809             pseudoProduct->multiplexConfigurationId = cd.productMultiplexId;
810             Artifact * const child = lookupArtifact(pseudoProduct, m_project->buildData.get(),
811                                                     cd.childFilePath, true);
812             if (artifact->children.contains(child))
813                 continue;
814             if (!child)  {
815                 // If a child has disappeared, we must re-build even if the commands
816                 // are the same. Example: Header file included in cpp file does not exist anymore.
817                 canRescue = false;
818                 qCDebug(lcBuildGraph) << "Former child artifact" << cd.childFilePath
819                                       << "does not exist anymore.";
820                 const RescuableArtifactData childRad
821                         = product->buildData->removeFromRescuableArtifactData(cd.childFilePath);
822                 if (childRad.isValid()) {
823                     m_artifactsRemovedFromDisk << artifact->filePath();
824                     removeGeneratedArtifactFromDisk(cd.childFilePath, m_logger);
825                 }
826             }
827             if (!cd.addedByScanner) {
828                 // If an artifact has disappeared from the list of children, the commands
829                 // might need to run again.
830                 canRescue = false;
831                 qCDebug(lcBuildGraph) << "Former child artifact" << cd.childFilePath <<
832                                          "is no longer in the list of children";
833             }
834             if (canRescue)
835                 childrenToConnect.push_back(child);
836         }
837         for (const QString &depPath : rad.fileDependencies) {
838             const auto &depList = m_project->buildData->lookupFiles(depPath);
839             if (depList.empty()) {
840                 canRescue = false;
841                 qCDebug(lcBuildGraph) << "File dependency" << depPath
842                                       << "not in the project's list of dependencies anymore.";
843                 break;
844             }
845             const auto depFinder = [](const FileResourceBase *f) {
846                 return f->fileType() == FileResourceBase::FileTypeDependency;
847             };
848             const auto depIt = std::find_if(depList.cbegin(), depList.cend(), depFinder);
849             if (depIt == depList.cend()) {
850                 canRescue = false;
851                 qCDebug(lcBuildGraph) << "File dependency" << depPath
852                                       << "not in the project's list of dependencies anymore.";
853                 break;
854             }
855             artifact->fileDependencies.insert(static_cast<FileDependency *>(*depIt));
856         }
857 
858         if (canRescue) {
859             const TypeFilter<Artifact> childArtifacts(artifact->children);
860             const size_t newChildCount = childrenToConnect.size()
861                     + std::distance(childArtifacts.begin(), childArtifacts.end());
862             QBS_CHECK(newChildCount >= rad.children.size());
863             if (newChildCount > rad.children.size()) {
864                 canRescue = false;
865                 qCDebug(lcBuildGraph) << "Artifact has children not present in rescue data.";
866             }
867         }
868     } else {
869         qCDebug(lcBuildGraph) << "Transformer commands changed.";
870     }
871 
872     if (canRescue) {
873         artifact->transformer->propertiesRequestedInPrepareScript
874                 = rad.propertiesRequestedInPrepareScript;
875         artifact->transformer->propertiesRequestedInCommands
876                 = rad.propertiesRequestedInCommands;
877         artifact->transformer->propertiesRequestedFromArtifactInPrepareScript
878                 = rad.propertiesRequestedFromArtifactInPrepareScript;
879         artifact->transformer->propertiesRequestedFromArtifactInCommands
880                 = rad.propertiesRequestedFromArtifactInCommands;
881         artifact->transformer->importedFilesUsedInPrepareScript
882                 = rad.importedFilesUsedInPrepareScript;
883         artifact->transformer->importedFilesUsedInCommands = rad.importedFilesUsedInCommands;
884         artifact->transformer->depsRequestedInPrepareScript = rad.depsRequestedInPrepareScript;
885         artifact->transformer->depsRequestedInCommands = rad.depsRequestedInCommands;
886         artifact->transformer->artifactsMapRequestedInPrepareScript
887                 = rad.artifactsMapRequestedInPrepareScript;
888         artifact->transformer->artifactsMapRequestedInCommands
889                 = rad.artifactsMapRequestedInCommands;
890         artifact->transformer->exportedModulesAccessedInPrepareScript
891                 = rad.exportedModulesAccessedInPrepareScript;
892         artifact->transformer->exportedModulesAccessedInCommands
893                 = rad.exportedModulesAccessedInCommands;
894         artifact->transformer->lastCommandExecutionTime = rad.lastCommandExecutionTime;
895         artifact->transformer->lastPrepareScriptExecutionTime = rad.lastPrepareScriptExecutionTime;
896         artifact->transformer->commandsNeedChangeTracking = true;
897         artifact->setTimestamp(rad.timeStamp);
898         artifact->transformer->markedForRerun
899                 = artifact->transformer->markedForRerun || rad.knownOutOfDate;
900         if (childrenAdded && !childrenToConnect.empty())
901             *childrenAdded = true;
902         for (Artifact * const child : childrenToConnect) {
903             if (safeConnect(artifact, child))
904                 artifact->childrenAddedByScanner << child;
905         }
906         qCDebug(lcBuildGraph) << "Data was rescued.";
907     } else {
908         removeGeneratedArtifactFromDisk(artifact, m_logger);
909         m_artifactsRemovedFromDisk << artifact->filePath();
910         qCDebug(lcBuildGraph) << "Data not rescued.";
911     }
912 }
913 
checkForUnbuiltDependencies(Artifact * artifact)914 bool Executor::checkForUnbuiltDependencies(Artifact *artifact)
915 {
916     bool buildingDependenciesFound = false;
917     NodeSet unbuiltDependencies;
918     for (BuildGraphNode * const dependency : qAsConst(artifact->children)) {
919         switch (dependency->buildState) {
920         case BuildGraphNode::Untouched:
921         case BuildGraphNode::Buildable:
922             qCDebug(lcExec).noquote() << "unbuilt dependency:" << dependency->toString();
923             unbuiltDependencies += dependency;
924             break;
925         case BuildGraphNode::Building: {
926             qCDebug(lcExec).noquote() << "dependency in state 'Building':" << dependency->toString();
927             buildingDependenciesFound = true;
928             break;
929         }
930         case BuildGraphNode::Built:
931             // do nothing
932             break;
933         }
934     }
935     if (!unbuiltDependencies.empty()) {
936         artifact->inputsScanned = false;
937         updateLeaves(unbuiltDependencies);
938         return true;
939     }
940     if (buildingDependenciesFound) {
941         artifact->inputsScanned = false;
942         return true;
943     }
944     return false;
945 }
946 
potentiallyRunTransformer(const TransformerPtr & transformer)947 void Executor::potentiallyRunTransformer(const TransformerPtr &transformer)
948 {
949     for (Artifact * const output : qAsConst(transformer->outputs)) {
950         // Rescuing build data can introduce new dependencies, potentially delaying execution of
951         // this transformer.
952         bool childrenAddedDueToRescue;
953         rescueOldBuildData(output, &childrenAddedDueToRescue);
954         if (childrenAddedDueToRescue && checkForUnbuiltDependencies(output))
955             return;
956     }
957 
958     if (!transformerHasMatchingOutputTags(transformer)) {
959         qCDebug(lcExec) << "file tags do not match. Skipping.";
960         finishTransformer(transformer);
961         return;
962     }
963 
964     if (!transformerHasMatchingInputFiles(transformer)) {
965         qCDebug(lcExec) << "input files do not match. Skipping.";
966         finishTransformer(transformer);
967         return;
968     }
969 
970     const bool mustExecute = mustExecuteTransformer(transformer);
971     if (mustExecute || m_buildOptions.forceTimestampCheck()) {
972         for (Artifact * const output : qAsConst(transformer->outputs)) {
973             // Scan all input artifacts. If new dependencies were found during scanning, delay
974             // execution of this transformer.
975             InputArtifactScanner scanner(output, m_inputArtifactScanContext, m_logger);
976             AccumulatingTimer scanTimer(m_buildOptions.logElapsedTime()
977                                         ? &m_elapsedTimeScanners : nullptr);
978             scanner.scan();
979             scanTimer.stop();
980             if (scanner.newDependencyAdded() && checkForUnbuiltDependencies(output))
981                 return;
982         }
983     }
984 
985     if (!mustExecute) {
986         qCDebug(lcExec) << "Up to date. Skipping.";
987         finishTransformer(transformer);
988         return;
989     }
990 
991     if (m_buildOptions.executeRulesOnly())
992         finishTransformer(transformer);
993     else
994         runTransformer(transformer);
995 }
996 
runTransformer(const TransformerPtr & transformer)997 void Executor::runTransformer(const TransformerPtr &transformer)
998 {
999     QBS_CHECK(transformer);
1000 
1001     // create the output directories
1002     if (!m_buildOptions.dryRun()) {
1003         for (Artifact * const output : qAsConst(transformer->outputs)) {
1004             QDir outDir = QFileInfo(output->filePath()).absoluteDir();
1005             if (!outDir.exists() && !outDir.mkpath(StringConstants::dot())) {
1006                     throw ErrorInfo(tr("Failed to create directory '%1'.")
1007                                     .arg(QDir::toNativeSeparators(outDir.absolutePath())));
1008             }
1009         }
1010     }
1011 
1012     QBS_CHECK(!m_availableJobs.empty());
1013     ExecutorJob *job = m_availableJobs.takeFirst();
1014     for (Artifact * const artifact : qAsConst(transformer->outputs))
1015         artifact->buildState = BuildGraphNode::Building;
1016     m_processingJobs.insert(job, transformer);
1017     updateJobCounts(transformer.get(), 1);
1018     job->run(transformer.get());
1019 }
1020 
finishTransformer(const TransformerPtr & transformer)1021 void Executor::finishTransformer(const TransformerPtr &transformer)
1022 {
1023     transformer->markedForRerun = false;
1024     for (Artifact * const artifact : qAsConst(transformer->outputs)) {
1025         possiblyInstallArtifact(artifact);
1026         finishArtifact(artifact);
1027     }
1028     if (m_progressObserver) {
1029         const auto it = m_pendingTransformersPerRule.find(transformer->rule.get());
1030         QBS_CHECK(it != m_pendingTransformersPerRule.cend());
1031         if (--it->second == 0) {
1032             m_progressObserver->incrementProgressValue();
1033             m_pendingTransformersPerRule.erase(it);
1034         }
1035     }
1036 }
1037 
possiblyInstallArtifact(const Artifact * artifact)1038 void Executor::possiblyInstallArtifact(const Artifact *artifact)
1039 {
1040     AccumulatingTimer installTimer(m_buildOptions.logElapsedTime()
1041                                    ? &m_elapsedTimeInstalling : nullptr);
1042 
1043     if (m_buildOptions.install() && !m_buildOptions.executeRulesOnly()
1044             && (m_activeFileTags.empty() || artifactHasMatchingOutputTags(artifact))
1045             && artifact->properties->qbsPropertyValue(StringConstants::installProperty())
1046                     .toBool()) {
1047             m_productInstaller->copyFile(artifact);
1048     }
1049 }
1050 
onJobFinished(const qbs::ErrorInfo & err)1051 void Executor::onJobFinished(const qbs::ErrorInfo &err)
1052 {
1053     try {
1054         auto const job = qobject_cast<ExecutorJob *>(sender());
1055         QBS_CHECK(job);
1056         if (m_evalContext->engine()->isActive()) {
1057             qCDebug(lcExec) << "Executor job finished while rule execution is pausing. "
1058                                "Delaying slot execution.";
1059             QTimer::singleShot(0, job, [job, err] { job->finished(err); });
1060             return;
1061         }
1062 
1063         if (err.hasError()) {
1064             if (m_buildOptions.keepGoing()) {
1065                 ErrorInfo fullWarning(err);
1066                 fullWarning.prepend(Tr::tr("Ignoring the following errors on user request:"));
1067                 m_logger.printWarning(fullWarning);
1068             } else {
1069                 if (!m_error.hasError())
1070                     m_error = err; // All but the first one could be due to canceling.
1071             }
1072         }
1073 
1074         finishJob(job, !err.hasError());
1075     } catch (const ErrorInfo &error) {
1076         handleError(error);
1077     }
1078 }
1079 
checkForUnbuiltProducts()1080 void Executor::checkForUnbuiltProducts()
1081 {
1082     if (m_buildOptions.executeRulesOnly())
1083         return;
1084     std::vector<ResolvedProductPtr> unbuiltProducts;
1085     for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) {
1086         bool productBuilt = true;
1087         for (BuildGraphNode *rootNode : qAsConst(product->buildData->rootNodes())) {
1088             if (rootNode->buildState != BuildGraphNode::Built) {
1089                 productBuilt = false;
1090                 unbuiltProducts.push_back(product);
1091                 break;
1092             }
1093         }
1094         if (productBuilt) {
1095             // Any element still left after a successful build has not been re-created
1096             // by any rule and therefore does not exist anymore as an artifact.
1097             const AllRescuableArtifactData rad = product->buildData->rescuableArtifactData();
1098             for (auto it = rad.cbegin(); it != rad.cend(); ++it) {
1099                 removeGeneratedArtifactFromDisk(it.key(), m_logger);
1100                 product->buildData->removeFromRescuableArtifactData(it.key());
1101                 m_artifactsRemovedFromDisk << it.key();
1102             }
1103         }
1104     }
1105 
1106     if (unbuiltProducts.empty()) {
1107         m_logger.qbsInfo() << Tr::tr("Build done%1.").arg(configString());
1108     } else {
1109         m_error.append(Tr::tr("The following products could not be built%1:").arg(configString()));
1110         QStringList productNames;
1111         std::transform(unbuiltProducts.cbegin(), unbuiltProducts.cend(),
1112                        std::back_inserter(productNames),
1113                        [](const ResolvedProductConstPtr &p) { return p->fullDisplayName(); });
1114         std::sort(productNames.begin(), productNames.end());
1115         m_error.append(productNames.join(QLatin1String(", ")));
1116     }
1117 }
1118 
checkNodeProduct(BuildGraphNode * node)1119 bool Executor::checkNodeProduct(BuildGraphNode *node)
1120 {
1121     if (!m_partialBuild || contains(m_productsToBuild, node->product.lock()))
1122         return true;
1123 
1124     // TODO: Turn this into a warning once we have a reliable C++ scanner.
1125     qCDebug(lcExec).noquote()
1126             << "Ignoring node " << node->toString() << ", which belongs to a "
1127                "product that is not part of the list of products to build. "
1128                "Possible reasons are an erroneous project design or a false "
1129                "positive from a dependency scanner.";
1130     finishNode(node);
1131     return false;
1132 }
1133 
finish()1134 void Executor::finish()
1135 {
1136     QBS_ASSERT(m_state != ExecutorIdle, /* ignore */);
1137     QBS_ASSERT(!m_evalContext || !m_evalContext->engine()->isActive(), /* ignore */);
1138 
1139     checkForUnbuiltProducts();
1140     if (m_explicitlyCanceled) {
1141         QString message = Tr::tr(m_buildOptions.executeRulesOnly()
1142                                  ? "Rule execution canceled" : "Build canceled");
1143         m_error.append(Tr::tr("%1%2.").arg(message, configString()));
1144     }
1145     setState(ExecutorIdle);
1146     if (m_progressObserver) {
1147         m_progressObserver->setFinished();
1148         m_cancelationTimer->stop();
1149     }
1150 
1151     EmptyDirectoriesRemover(m_project.get(), m_logger)
1152             .removeEmptyParentDirectories(m_artifactsRemovedFromDisk);
1153 
1154     if (m_buildOptions.logElapsedTime()) {
1155         m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Rule execution took %1.")
1156                                              .arg(elapsedTimeString(m_elapsedTimeRules));
1157         m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Artifact scanning took %1.")
1158                                              .arg(elapsedTimeString(m_elapsedTimeScanners));
1159         m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Installing artifacts took %1.")
1160                                              .arg(elapsedTimeString(m_elapsedTimeInstalling));
1161     }
1162 
1163     emit finished();
1164 }
1165 
checkForCancellation()1166 void Executor::checkForCancellation()
1167 {
1168     QBS_ASSERT(m_progressObserver, return);
1169     if (m_state == ExecutorRunning && m_progressObserver->canceled()) {
1170         cancelJobs();
1171         if (m_evalContext->engine()->isActive())
1172             m_evalContext->engine()->cancel();
1173     }
1174 }
1175 
visit(Artifact * artifact)1176 bool Executor::visit(Artifact *artifact)
1177 {
1178     QBS_CHECK(artifact->buildState != BuildGraphNode::Untouched);
1179     buildArtifact(artifact);
1180     return false;
1181 }
1182 
visit(RuleNode * ruleNode)1183 bool Executor::visit(RuleNode *ruleNode)
1184 {
1185     QBS_CHECK(ruleNode->buildState != BuildGraphNode::Untouched);
1186     executeRuleNode(ruleNode);
1187     return false;
1188 }
1189 
1190 /**
1191   * Sets the state of all artifacts in the graph to "untouched".
1192   * This must be done before doing a build.
1193   *
1194   * Retrieves the timestamps of source artifacts.
1195   *
1196   * This function also fills the list of changed source files.
1197   */
prepareAllNodes()1198 void Executor::prepareAllNodes()
1199 {
1200     for (const ResolvedProductPtr &product : m_allProducts) {
1201         if (product->enabled) {
1202             QBS_CHECK(product->buildData);
1203             for (BuildGraphNode * const node : qAsConst(product->buildData->allNodes()))
1204                 node->buildState = BuildGraphNode::Untouched;
1205         }
1206     }
1207     for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) {
1208         QBS_CHECK(product->buildData);
1209         for (Artifact * const artifact : filterByType<Artifact>(product->buildData->allNodes()))
1210             prepareArtifact(artifact);
1211     }
1212 }
1213 
syncFileDependencies()1214 void Executor::syncFileDependencies()
1215 {
1216     Set<FileDependency *> &globalFileDepList = m_project->buildData->fileDependencies;
1217     for (auto it = globalFileDepList.begin(); it != globalFileDepList.end(); ) {
1218         FileDependency * const dep = *it;
1219         FileInfo fi(dep->filePath());
1220         if (fi.exists()) {
1221             dep->setTimestamp(fi.lastModified());
1222             ++it;
1223             continue;
1224         }
1225         qCDebug(lcBuildGraph()) << "file dependency" << dep->filePath() << "no longer exists; "
1226                                    "removing from lookup table";
1227         m_project->buildData->removeFromLookupTable(dep);
1228         bool isReferencedByArtifact = false;
1229         for (const auto &product : m_allProducts) {
1230             if (!product->buildData)
1231                 continue;
1232             const auto artifactList = filterByType<Artifact>(product->buildData->allNodes());
1233             isReferencedByArtifact = std::any_of(artifactList.begin(), artifactList.end(),
1234                     [dep](const Artifact *a) { return a->fileDependencies.contains(dep); });
1235             // TODO: Would it be safe to mark the artifact as "not up to date" here and clear
1236             //       its list of file dependencies, rather than doing the check again in
1237             //       isUpToDate()?
1238             if (isReferencedByArtifact)
1239                 break;
1240         }
1241         if (!isReferencedByArtifact) {
1242             qCDebug(lcBuildGraph()) << "dependency is not referenced by any artifact, deleting";
1243             it = globalFileDepList.erase(it);
1244             delete dep;
1245         } else {
1246             dep->clearTimestamp();
1247             ++it;
1248         }
1249     }
1250 }
1251 
prepareArtifact(Artifact * artifact)1252 void Executor::prepareArtifact(Artifact *artifact)
1253 {
1254     artifact->inputsScanned = false;
1255     artifact->timestampRetrieved = false;
1256 
1257     if (artifact->artifactType == Artifact::SourceFile) {
1258         retrieveSourceFileTimestamp(artifact);
1259         possiblyInstallArtifact(artifact);
1260     }
1261 }
1262 
setupForBuildingSelectedFiles(const BuildGraphNode * node)1263 void Executor::setupForBuildingSelectedFiles(const BuildGraphNode *node)
1264 {
1265     if (node->type() != BuildGraphNode::RuleNodeType)
1266         return;
1267     if (m_buildOptions.filesToConsider().empty())
1268         return;
1269     if (!m_productsOfFilesToConsider.contains(node->product.lock()))
1270         return;
1271     const auto ruleNode = static_cast<const RuleNode *>(node);
1272     const Rule * const rule = ruleNode->rule().get();
1273     if (rule->inputs.intersects(m_tagsOfFilesToConsider)) {
1274         FileTags otherInputs = rule->auxiliaryInputs;
1275         otherInputs.unite(rule->explicitlyDependsOn).subtract(rule->excludedInputs);
1276         m_tagsNeededForFilesToConsider.unite(otherInputs);
1277     } else if (rule->collectedOutputFileTags().intersects(m_tagsNeededForFilesToConsider)) {
1278         FileTags allInputs = rule->inputs;
1279         allInputs.unite(rule->auxiliaryInputs).unite(rule->explicitlyDependsOn)
1280                 .subtract(rule->excludedInputs);
1281         m_tagsNeededForFilesToConsider.unite(allInputs);
1282     }
1283 }
1284 
1285 /**
1286  * Walk the build graph top-down from the roots and for each reachable node N
1287  *  - mark N as buildable.
1288  */
prepareReachableNodes()1289 void Executor::prepareReachableNodes()
1290 {
1291     for (BuildGraphNode * const root : qAsConst(m_roots))
1292         prepareReachableNodes_impl(root);
1293 }
1294 
prepareReachableNodes_impl(BuildGraphNode * node)1295 void Executor::prepareReachableNodes_impl(BuildGraphNode *node)
1296 {
1297     setupForBuildingSelectedFiles(node);
1298 
1299     if (node->buildState != BuildGraphNode::Untouched)
1300         return;
1301 
1302     node->buildState = BuildGraphNode::Buildable;
1303     for (BuildGraphNode *child : qAsConst(node->children))
1304         prepareReachableNodes_impl(child);
1305 }
1306 
prepareProducts()1307 void Executor::prepareProducts()
1308 {
1309     ProductPrioritySetter prioritySetter(m_allProducts);
1310     prioritySetter.apply();
1311     for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) {
1312         EnvironmentScriptRunner(product.get(), m_evalContext.get(), m_project->environment)
1313                 .setupForBuild();
1314     }
1315 }
1316 
setupRootNodes()1317 void Executor::setupRootNodes()
1318 {
1319     m_roots.clear();
1320     for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild))
1321         m_roots += product->buildData->rootNodes();
1322 }
1323 
setState(ExecutorState s)1324 void Executor::setState(ExecutorState s)
1325 {
1326     if (m_state == s)
1327         return;
1328     m_state = s;
1329 }
1330 
1331 } // namespace Internal
1332 } // namespace qbs
1333