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