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