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 "projectbuilddata.h"
40
41 #include "artifact.h"
42 #include "buildgraph.h"
43 #include "buildgraphvisitor.h"
44 #include "productbuilddata.h"
45 #include "rulecommands.h"
46 #include "rulegraph.h"
47 #include "rulenode.h"
48 #include "rulesevaluationcontext.h"
49 #include "transformer.h"
50
51 #include <language/artifactproperties.h>
52 #include <language/language.h>
53 #include <language/preparescriptobserver.h>
54 #include <language/scriptengine.h>
55 #include <logging/categories.h>
56 #include <logging/translator.h>
57 #include <tools/error.h>
58 #include <tools/fileinfo.h>
59 #include <tools/qbsassert.h>
60 #include <tools/qttools.h>
61 #include <tools/stlutils.h>
62
63 #include <memory>
64
65 namespace qbs {
66 namespace Internal {
67
findDependentProducts(const ResolvedProductPtr & product)68 static Set<ResolvedProductPtr> findDependentProducts(const ResolvedProductPtr &product)
69 {
70 Set<ResolvedProductPtr> result;
71 for (const ResolvedProductPtr &parent : product->topLevelProject()->allProducts()) {
72 if (contains(parent->dependencies, product))
73 result += parent;
74 }
75 return result;
76 }
77
ProjectBuildData(const ProjectBuildData * other)78 ProjectBuildData::ProjectBuildData(const ProjectBuildData *other)
79 {
80 // This is needed for temporary duplication of build data when doing change tracking.
81 if (other) {
82 *this = *other;
83 m_doCleanupInDestructor = false;
84 }
85 }
86
~ProjectBuildData()87 ProjectBuildData::~ProjectBuildData()
88 {
89 if (!m_doCleanupInDestructor)
90 return;
91 qDeleteAll(fileDependencies);
92 }
93
deriveBuildGraphFilePath(const QString & buildDir,const QString & projectId)94 QString ProjectBuildData::deriveBuildGraphFilePath(const QString &buildDir, const QString &projectId)
95 {
96 return buildDir + QLatin1Char('/') + projectId + QStringLiteral(".bg");
97 }
98
insertIntoLookupTable(FileResourceBase * fileres)99 void ProjectBuildData::insertIntoLookupTable(FileResourceBase *fileres)
100 {
101 auto &lst = m_artifactLookupTable[{fileres->fileName(), fileres->dirPath()}];
102 const auto * const artifact = fileres->fileType() == FileResourceBase::FileTypeArtifact
103 ? static_cast<Artifact *>(fileres) : nullptr;
104 if (artifact && artifact->artifactType == Artifact::Generated) {
105 for (const auto *file : lst) {
106 if (file->fileType() != FileResourceBase::FileTypeArtifact)
107 continue;
108 const auto * const otherArtifact = static_cast<const Artifact *>(file);
109 ErrorInfo error;
110 error.append(Tr::tr("Conflicting artifacts for file path '%1'.")
111 .arg(artifact->filePath()));
112 error.append(Tr::tr("The first artifact comes from product '%1'.")
113 .arg(otherArtifact->product->fullDisplayName()),
114 otherArtifact->product->location);
115 error.append(Tr::tr("The second artifact comes from product '%1'.")
116 .arg(artifact->product->fullDisplayName()),
117 artifact->product->location);
118 throw error;
119 }
120 }
121 QBS_CHECK(!contains(lst, fileres));
122 lst.push_back(fileres);
123 m_isDirty = true;
124 }
125
removeFromLookupTable(FileResourceBase * fileres)126 void ProjectBuildData::removeFromLookupTable(FileResourceBase *fileres)
127 {
128 removeOne(m_artifactLookupTable[{fileres->fileName(), fileres->dirPath()}], fileres);
129 }
130
lookupFiles(const QString & filePath) const131 const std::vector<FileResourceBase *> &ProjectBuildData::lookupFiles(const QString &filePath) const
132 {
133 QString dirPath, fileName;
134 FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName);
135 return lookupFiles(dirPath, fileName);
136 }
137
lookupFiles(const QString & dirPath,const QString & fileName) const138 const std::vector<FileResourceBase *> &ProjectBuildData::lookupFiles(const QString &dirPath,
139 const QString &fileName) const
140 {
141 static const std::vector<FileResourceBase *> emptyResult;
142 const auto it = m_artifactLookupTable.find({fileName, dirPath});
143 return it != m_artifactLookupTable.end() ? it->second : emptyResult;
144 }
145
lookupFiles(const Artifact * artifact) const146 const std::vector<FileResourceBase *> &ProjectBuildData::lookupFiles(const Artifact *artifact) const
147 {
148 return lookupFiles(artifact->dirPath(), artifact->fileName());
149 }
150
insertFileDependency(FileDependency * dependency)151 void ProjectBuildData::insertFileDependency(FileDependency *dependency)
152 {
153 fileDependencies += dependency;
154 insertIntoLookupTable(dependency);
155 }
156
disconnectArtifactChildren(Artifact * artifact)157 static void disconnectArtifactChildren(Artifact *artifact)
158 {
159 qCDebug(lcBuildGraph) << "disconnect children of" << relativeArtifactFileName(artifact);
160 for (BuildGraphNode * const child : qAsConst(artifact->children))
161 child->parents.remove(artifact);
162 artifact->children.clear();
163 artifact->childrenAddedByScanner.clear();
164 }
165
disconnectArtifactParents(Artifact * artifact)166 static void disconnectArtifactParents(Artifact *artifact)
167 {
168 qCDebug(lcBuildGraph) << "disconnect parents of" << relativeArtifactFileName(artifact);
169 for (BuildGraphNode * const parent : qAsConst(artifact->parents)) {
170 parent->children.remove(artifact);
171 if (parent->type() != BuildGraphNode::ArtifactNodeType)
172 continue;
173 auto const parentArtifact = static_cast<Artifact *>(parent);
174 QBS_CHECK(parentArtifact->transformer);
175 parentArtifact->childrenAddedByScanner.remove(artifact);
176 parentArtifact->transformer->inputs.remove(artifact);
177 parentArtifact->transformer->explicitlyDependsOn.remove(artifact);
178 }
179
180 artifact->parents.clear();
181 }
182
disconnectArtifact(Artifact * artifact)183 static void disconnectArtifact(Artifact *artifact)
184 {
185 disconnectArtifactChildren(artifact);
186 disconnectArtifactParents(artifact);
187 }
188
189 /*!
190 * Removes the artifact and all the artifacts that depend exclusively on it.
191 * Example: if you remove a cpp artifact then the obj artifact is removed but
192 * not the resulting application (if there's more then one cpp artifact).
193 */
removeArtifactAndExclusiveDependents(Artifact * artifact,const Logger & logger,bool removeFromProduct,ArtifactSet * removedArtifacts)194 void ProjectBuildData::removeArtifactAndExclusiveDependents(Artifact *artifact,
195 const Logger &logger, bool removeFromProduct,
196 ArtifactSet *removedArtifacts)
197 {
198 if (removedArtifacts)
199 removedArtifacts->insert(artifact);
200
201 // Iterate over a copy of the artifact's parents, because we'll change
202 // artifact->parents with the disconnect call.
203 const NodeSet parentsCopy = artifact->parents;
204 for (Artifact *parent : filterByType<Artifact>(parentsCopy)) {
205 bool removeParent = false;
206 disconnect(parent, artifact);
207 if (parent->children.empty()) {
208 removeParent = true;
209 } else if (parent->transformer) {
210 parent->transformer->inputs.remove(artifact);
211 removeParent = parent->transformer->inputs.empty();
212 }
213 if (removeParent) {
214 removeArtifactAndExclusiveDependents(parent, logger, removeFromProduct,
215 removedArtifacts);
216 } else {
217 parent->clearTimestamp();
218 }
219 }
220 const bool removeFromDisk = artifact->artifactType == Artifact::Generated;
221 removeArtifact(artifact, logger, removeFromDisk, removeFromProduct);
222 }
223
removeFromRuleNodes(Artifact * artifact)224 static void removeFromRuleNodes(Artifact *artifact)
225 {
226 for (RuleNode * const ruleNode : filterByType<RuleNode>(artifact->parents))
227 ruleNode->removeOldInputArtifact(artifact);
228 }
229
removeArtifact(Artifact * artifact,const Logger & logger,bool removeFromDisk,bool removeFromProduct)230 void ProjectBuildData::removeArtifact(Artifact *artifact,
231 const Logger &logger, bool removeFromDisk, bool removeFromProduct)
232 {
233 qCDebug(lcBuildGraph) << "remove artifact" << relativeArtifactFileName(artifact);
234 if (removeFromDisk)
235 removeGeneratedArtifactFromDisk(artifact, logger);
236 removeFromLookupTable(artifact);
237 removeFromRuleNodes(artifact);
238 disconnectArtifact(artifact);
239 if (artifact->transformer)
240 artifact->transformer->outputs.remove(artifact);
241 if (removeFromProduct)
242 artifact->product->buildData->removeArtifact(artifact);
243 m_isDirty = false;
244 }
245
setDirty()246 void ProjectBuildData::setDirty()
247 {
248 qCDebug(lcBuildGraph) << "Marking build graph as dirty";
249 m_isDirty = true;
250 }
251
setClean()252 void ProjectBuildData::setClean()
253 {
254 qCDebug(lcBuildGraph) << "Marking build graph as clean";
255 m_isDirty = false;
256 }
257
load(PersistentPool & pool)258 void ProjectBuildData::load(PersistentPool &pool)
259 {
260 serializationOp<PersistentPool::Load>(pool);
261 for (FileDependency * const dep : qAsConst(fileDependencies))
262 insertIntoLookupTable(dep);
263 m_isDirty = false;
264 }
265
store(PersistentPool & pool)266 void ProjectBuildData::store(PersistentPool &pool)
267 {
268 serializationOp<PersistentPool::Store>(pool);
269 }
270
271
BuildDataResolver(Logger logger)272 BuildDataResolver::BuildDataResolver(Logger logger) : m_logger(std::move(logger))
273 {
274 }
275
resolveBuildData(const TopLevelProjectPtr & resolvedProject,const RulesEvaluationContextPtr & evalContext)276 void BuildDataResolver::resolveBuildData(const TopLevelProjectPtr &resolvedProject,
277 const RulesEvaluationContextPtr &evalContext)
278 {
279 QBS_CHECK(!resolvedProject->buildData);
280 m_project = resolvedProject;
281 resolvedProject->buildData = std::make_unique<ProjectBuildData>();
282 resolvedProject->buildData->evaluationContext = evalContext;
283 const std::vector<ResolvedProductPtr> &allProducts = resolvedProject->allProducts();
284 evalContext->initializeObserver(Tr::tr("Setting up build graph for configuration %1")
285 .arg(resolvedProject->id()), int(allProducts.size()) + 1);
286 for (const auto &rProduct : allProducts) {
287 if (rProduct->enabled)
288 resolveProductBuildData(rProduct);
289 evalContext->incrementProgressValue();
290 }
291 evalContext->incrementProgressValue();
292 doSanityChecks(resolvedProject, m_logger);
293 }
294
resolveProductBuildDataForExistingProject(const TopLevelProjectPtr & project,const std::vector<ResolvedProductPtr> & freshProducts)295 void BuildDataResolver::resolveProductBuildDataForExistingProject(const TopLevelProjectPtr &project,
296 const std::vector<ResolvedProductPtr> &freshProducts)
297 {
298 m_project = project;
299 for (const ResolvedProductPtr &product : freshProducts) {
300 if (product->enabled)
301 resolveProductBuildData(product);
302 }
303
304 QHash<ResolvedProductPtr, std::vector<ResolvedProductPtr>> dependencyMap;
305 for (const ResolvedProductPtr &product : freshProducts) {
306 if (!product->enabled)
307 continue;
308 QBS_CHECK(product->buildData);
309 const Set<ResolvedProductPtr> dependents = findDependentProducts(product);
310 for (const ResolvedProductPtr &dependentProduct : dependents) {
311 if (!dependentProduct->enabled)
312 continue;
313 if (!contains(dependencyMap[dependentProduct], product))
314 dependencyMap[dependentProduct].push_back(product);
315 }
316 }
317 for (auto it = dependencyMap.cbegin(); it != dependencyMap.cend(); ++it) {
318 if (!contains(freshProducts, it.key()))
319 connectRulesToDependencies(it.key(), it.value());
320 }
321 }
322
323 class CreateRuleNodes : public RuleGraphVisitor
324 {
325 public:
CreateRuleNodes(const ResolvedProductPtr & product)326 CreateRuleNodes(const ResolvedProductPtr &product)
327 : m_product(product)
328 {
329 }
330
331 private:
332 const ResolvedProductPtr &m_product;
333 QHash<RuleConstPtr, RuleNode *> m_nodePerRule;
334 Set<const Rule *> m_rulesOnPath;
335 QList<const Rule *> m_rulePath;
336
visit(const RuleConstPtr & parentRule,const RuleConstPtr & rule)337 void visit(const RuleConstPtr &parentRule, const RuleConstPtr &rule) override
338 {
339 if (!m_rulesOnPath.insert(rule.get()).second) {
340 QString pathstr;
341 for (const Rule *r : qAsConst(m_rulePath)) {
342 pathstr += QLatin1Char('\n') + r->toString() + QLatin1Char('\t')
343 + r->prepareScript.location().toString();
344 }
345 throw ErrorInfo(Tr::tr("Cycle detected in rule dependencies: %1").arg(pathstr));
346 }
347 m_rulePath.push_back(rule.get());
348 RuleNode *node = m_nodePerRule.value(rule);
349 if (!node) {
350 node = new RuleNode;
351 m_nodePerRule.insert(rule, node);
352 node->product = m_product;
353 node->setRule(rule);
354 m_product->buildData->addNode(node);
355 qCDebug(lcBuildGraph).noquote() << "create" << node->toString()
356 << "for product" << m_product->uniqueName();
357 }
358 if (parentRule) {
359 RuleNode *parent = m_nodePerRule.value(parentRule);
360 QBS_CHECK(parent);
361 connect(parent, node);
362 } else {
363 m_product->buildData->addRootNode(node);
364 }
365 }
366
endVisit(const RuleConstPtr & rule)367 void endVisit(const RuleConstPtr &rule) override
368 {
369 m_rulesOnPath.remove(rule.get());
370 m_rulePath.removeLast();
371 }
372 };
373
areRulesCompatible(const RuleNode * ruleNode,const RuleNode * dependencyRule)374 static bool areRulesCompatible(const RuleNode *ruleNode, const RuleNode *dependencyRule)
375 {
376 const FileTags outTags = dependencyRule->rule()->collectedOutputFileTags();
377 if (ruleNode->rule()->excludedInputs.intersects(outTags))
378 return false;
379 if (ruleNode->rule()->inputsFromDependencies.intersects(outTags))
380 return true;
381 if (!dependencyRule->product->fileTags.intersects(outTags))
382 return false;
383 if (ruleNode->rule()->explicitlyDependsOnFromDependencies.intersects(outTags))
384 return true;
385 return ruleNode->rule()->auxiliaryInputs.intersects(outTags);
386 }
387
resolveProductBuildData(const ResolvedProductPtr & product)388 void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &product)
389 {
390 if (product->buildData)
391 return;
392
393 evalContext()->checkForCancelation();
394
395 product->buildData = std::make_unique<ProductBuildData>();
396 ArtifactSetByFileTag artifactsPerFileTag;
397
398 for (const auto &dependency : qAsConst(product->dependencies)) {
399 QBS_CHECK(dependency->enabled);
400 resolveProductBuildData(dependency);
401 }
402
403 //add qbsFile artifact
404 Artifact *qbsFileArtifact = lookupArtifact(product, product->location.filePath());
405 if (!qbsFileArtifact) {
406 qbsFileArtifact = new Artifact;
407 qbsFileArtifact->artifactType = Artifact::SourceFile;
408 qbsFileArtifact->setFilePath(product->location.filePath());
409 qbsFileArtifact->properties = product->moduleProperties;
410 insertArtifact(product, qbsFileArtifact);
411 }
412 qbsFileArtifact->addFileTag("qbs");
413 artifactsPerFileTag["qbs"].insert(qbsFileArtifact);
414
415 // read sources
416 for (const auto &sourceArtifact : product->allEnabledFiles()) {
417 QString filePath = sourceArtifact->absoluteFilePath;
418 if (lookupArtifact(product, filePath))
419 continue; // ignore duplicate artifacts
420
421 Artifact *artifact = createArtifact(product, sourceArtifact);
422 for (const FileTag &fileTag : artifact->fileTags())
423 artifactsPerFileTag[fileTag].insert(artifact);
424 }
425
426 RuleGraph ruleGraph;
427 ruleGraph.build(product->rules, product->fileTags);
428 CreateRuleNodes crn(product);
429 ruleGraph.accept(&crn);
430
431 connectRulesToDependencies(product, product->dependencies);
432 }
433
isRootRuleNode(RuleNode * ruleNode)434 static bool isRootRuleNode(RuleNode *ruleNode)
435 {
436 return ruleNode->product->buildData->rootNodes().contains(ruleNode);
437 }
438
connectRulesToDependencies(const ResolvedProductPtr & product,const std::vector<ResolvedProductPtr> & dependencies)439 void BuildDataResolver::connectRulesToDependencies(const ResolvedProductPtr &product,
440 const std::vector<ResolvedProductPtr> &dependencies)
441 {
442 // Connect the rules of this product to the compatible rules of all product dependencies.
443 // Rules that take "installable" artifacts are connected to all root rules of product
444 // dependencies.
445 std::vector<RuleNode *> ruleNodes;
446 for (RuleNode *ruleNode : filterByType<RuleNode>(product->buildData->allNodes()))
447 ruleNodes.push_back(ruleNode);
448 for (const auto &dep : dependencies) {
449 if (!dep->buildData)
450 continue;
451 for (RuleNode *depRuleNode : filterByType<RuleNode>(dep->buildData->allNodes())) {
452 for (RuleNode *ruleNode : ruleNodes) {
453 static const FileTag installableTag("installable");
454 if (areRulesCompatible(ruleNode, depRuleNode)
455 || ((ruleNode->rule()->inputsFromDependencies.contains(installableTag)
456 || ruleNode->rule()->auxiliaryInputs.contains(installableTag)
457 || ruleNode->rule()->explicitlyDependsOnFromDependencies.contains(
458 installableTag))
459 && isRootRuleNode(depRuleNode))) {
460 connect(ruleNode, depRuleNode);
461 }
462 }
463 }
464 }
465 }
466
evalContext() const467 RulesEvaluationContextPtr BuildDataResolver::evalContext() const
468 {
469 return m_project->buildData->evaluationContext;
470 }
471
engine() const472 ScriptEngine *BuildDataResolver::engine() const
473 {
474 return evalContext()->engine();
475 }
476
scope() const477 QScriptValue BuildDataResolver::scope() const
478 {
479 return evalContext()->scope();
480 }
481
482 } // namespace Internal
483 } // namespace qbs
484