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 "buildgraph.h"
40 
41 #include "artifact.h"
42 #include "artifactsscriptvalue.h"
43 #include "cycledetector.h"
44 #include "dependencyparametersscriptvalue.h"
45 #include "projectbuilddata.h"
46 #include "productbuilddata.h"
47 #include "rulenode.h"
48 #include "scriptclasspropertyiterator.h"
49 #include "transformer.h"
50 
51 #include <jsextensions/jsextensions.h>
52 #include <jsextensions/moduleproperties.h>
53 #include <language/artifactproperties.h>
54 #include <language/language.h>
55 #include <language/preparescriptobserver.h>
56 #include <language/propertymapinternal.h>
57 #include <language/resolvedfilecontext.h>
58 #include <language/scriptengine.h>
59 #include <logging/categories.h>
60 #include <logging/logger.h>
61 #include <language/property.h>
62 #include <logging/translator.h>
63 #include <tools/error.h>
64 #include <tools/fileinfo.h>
65 #include <tools/scripttools.h>
66 #include <tools/qbsassert.h>
67 #include <tools/qttools.h>
68 #include <tools/stringconstants.h>
69 
70 #include <QtCore/qdir.h>
71 #include <QtCore/qfile.h>
72 #include <QtScript/qscriptclass.h>
73 
74 #include <algorithm>
75 #include <iterator>
76 #include <vector>
77 
78 namespace qbs {
79 namespace Internal {
80 
childItemsProperty()81 static QString childItemsProperty() { return QStringLiteral("childItems"); }
exportsProperty()82 static QString exportsProperty() { return QStringLiteral("exports"); }
83 
84 // TODO: Introduce productscriptvalue.{h,cpp}.
getDataForProductScriptValue(QScriptEngine * engine,const ResolvedProduct * product)85 static QScriptValue getDataForProductScriptValue(QScriptEngine *engine,
86                                                  const ResolvedProduct *product)
87 {
88     QScriptValue data = engine->newObject();
89     QVariant v;
90     v.setValue<quintptr>(reinterpret_cast<quintptr>(product));
91     data.setProperty(ProductPtrKey, engine->newVariant(v));
92     return data;
93 }
94 
95 class ProductPropertyScriptClass : public QScriptClass
96 {
97 public:
ProductPropertyScriptClass(QScriptEngine * engine)98     ProductPropertyScriptClass(QScriptEngine *engine) : QScriptClass(engine) { }
99 
100 private:
queryProperty(const QScriptValue & object,const QScriptString & name,QueryFlags,uint *)101     QueryFlags queryProperty(const QScriptValue &object, const QScriptString &name, QueryFlags,
102                              uint *) override
103     {
104         if (name == StringConstants::parametersProperty()) {
105             m_result = object.data().property(DependencyParametersKey);
106             return HandlesReadAccess;
107         }
108         if (name == StringConstants::moduleNameProperty()) {
109             m_result = object.data().property(ModuleNameKey);
110             return HandlesReadAccess;
111         }
112         if (name == StringConstants::dependenciesProperty()
113                 || name == StringConstants::artifactsProperty()
114                 || name == exportsProperty()) {
115             // The prototype is not backed by a QScriptClass.
116             m_result = object.prototype().property(name);
117             return HandlesReadAccess;
118         }
119 
120         getProduct(object);
121         QBS_ASSERT(m_product, return {});
122 
123         const auto it = m_product->productProperties.find(name);
124 
125         // It is important that we reject unknown property names. Otherwise QtScript will forward
126         // *everything* to us, including built-in stuff like the hasOwnProperty function.
127         if (it == m_product->productProperties.cend())
128             return {};
129 
130         qbsEngine()->addPropertyRequestedInScript(Property(m_product->uniqueName(), QString(), name,
131                 it.value(), Property::PropertyInProduct));
132         m_result = qbsEngine()->toScriptValue(it.value());
133         return HandlesReadAccess;
134     }
135 
property(const QScriptValue &,const QScriptString &,uint)136     QScriptValue property(const QScriptValue &, const QScriptString &, uint) override
137     {
138         return m_result;
139     }
140 
newIterator(const QScriptValue & object)141     QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override
142     {
143         getProduct(object);
144         QBS_ASSERT(m_product, return nullptr);
145 
146         // These two are in the prototype and are thus common to all product script values.
147         std::vector<QString> additionalProperties({StringConstants::artifactsProperty(),
148                                                    StringConstants::dependenciesProperty(),
149                                                    exportsProperty()});
150 
151         // The "moduleName" convenience property is only available for the "main product" in a rule,
152         // and the "parameters" property exists only for elements of the "dependencies" array for
153         // which dependency parameters are present.
154         if (object.data().property(ModuleNameKey).isValid())
155             additionalProperties.push_back(StringConstants::moduleNameProperty());
156         else if (object.data().property(DependencyParametersKey).isValid())
157             additionalProperties.push_back(StringConstants::parametersProperty());
158         return new ScriptClassPropertyIterator(object, m_product->productProperties,
159                                                additionalProperties);
160     }
161 
getProduct(const QScriptValue & object)162     void getProduct(const QScriptValue &object)
163     {
164         if (m_lastObjectId != object.objectId()) {
165             m_lastObjectId = object.objectId();
166             m_product = reinterpret_cast<const ResolvedProduct *>(
167                         object.data().property(ProductPtrKey).toVariant().value<quintptr>());
168         }
169     }
170 
qbsEngine() const171     ScriptEngine *qbsEngine() const { return static_cast<ScriptEngine *>(engine()); }
172 
173     qint64 m_lastObjectId = 0;
174     const ResolvedProduct *m_product = nullptr;
175     QScriptValue m_result;
176 };
177 
setupProjectScriptValue(ScriptEngine * engine,const ResolvedProjectConstPtr & project)178 static QScriptValue setupProjectScriptValue(ScriptEngine *engine,
179         const ResolvedProjectConstPtr &project)
180 {
181     QScriptValue &obj = engine->projectScriptValue(project.get());
182     if (obj.isValid())
183         return obj;
184     obj = engine->newObject();
185     obj.setProperty(StringConstants::filePathProperty(), project->location.filePath());
186     obj.setProperty(StringConstants::pathProperty(), FileInfo::path(project->location.filePath()));
187     const QVariantMap &projectProperties = project->projectProperties();
188     for (QVariantMap::const_iterator it = projectProperties.begin();
189             it != projectProperties.end(); ++it) {
190         engine->setObservedProperty(obj, it.key(), engine->toScriptValue(it.value()));
191     }
192     engine->observer()->addProjectObjectId(obj.objectId(), project->name);
193     return obj;
194 }
195 
196 static QScriptValue setupProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product);
197 
198 class DependenciesFunction
199 {
200 public:
DependenciesFunction(ScriptEngine * engine)201     DependenciesFunction(ScriptEngine *engine)
202         : m_engine(engine)
203     {
204     }
205 
init(QScriptValue & productScriptValue,QScriptValue & exportsScriptValue,const ResolvedProduct * product)206     void init(QScriptValue &productScriptValue, QScriptValue &exportsScriptValue,
207               const ResolvedProduct *product)
208     {
209         QScriptValue depfunc = m_engine->newFunction(&js_internalProductDependencies, product);
210         productScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc,
211                                        QScriptValue::ReadOnly | QScriptValue::Undeletable
212                                        | QScriptValue::PropertyGetter);
213         depfunc = m_engine->newFunction(&js_exportedProductDependencies, product);
214         exportsScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc,
215                                        QScriptValue::ReadOnly | QScriptValue::Undeletable
216                                        | QScriptValue::PropertyGetter);
217     }
218 
219 private:
220     enum class DependencyType { Internal, Exported };
js_productDependencies(QScriptContext *,ScriptEngine * engine,const ResolvedProduct * product,DependencyType depType)221     static QScriptValue js_productDependencies(QScriptContext *, ScriptEngine *engine,
222             const ResolvedProduct *product, DependencyType depType)
223     {
224         QScriptValue result = engine->newArray();
225         quint32 idx = 0;
226         const bool exportCase = depType == DependencyType::Exported;
227         std::vector<ResolvedProductPtr> productDeps;
228         if (exportCase) {
229             if (!product->exportedModule.productDependencies.empty()) {
230                 const auto allProducts = product->topLevelProject()->allProducts();
231                 const auto getProductForName = [&allProducts](const QString &name) {
232                     const auto cmp = [name](const ResolvedProductConstPtr &p) {
233                         return p->uniqueName() == name;
234                     };
235                     const auto it = std::find_if(allProducts.cbegin(), allProducts.cend(), cmp);
236                     QBS_ASSERT(it != allProducts.cend(), return ResolvedProductPtr());
237                     return *it;
238                 };
239                 std::transform(product->exportedModule.productDependencies.cbegin(),
240                                product->exportedModule.productDependencies.cend(),
241                                std::back_inserter(productDeps), getProductForName);
242             }
243         } else {
244             productDeps = product->dependencies;
245         }
246         for (const ResolvedProductPtr &dependency : qAsConst(productDeps)) {
247             QScriptValue obj = engine->newObject(engine->productPropertyScriptClass());
248             obj.setPrototype(setupProductScriptValue(static_cast<ScriptEngine *>(engine),
249                                                      dependency.get()));
250             const QVariantMap &params
251                     = (exportCase ? product->exportedModule.dependencyParameters.value(dependency)
252                                   : product->dependencyParameters.value(dependency));
253             QScriptValue data = getDataForProductScriptValue(engine, dependency.get());
254             data.setProperty(DependencyParametersKey, dependencyParametersValue(
255                                  product->uniqueName(), dependency->name, params, engine));
256             obj.setData(data);
257             result.setProperty(idx++, obj);
258         }
259         if (exportCase) {
260             for (const ExportedModuleDependency &m : product->exportedModule.moduleDependencies) {
261                 QScriptValue obj = engine->newObject();
262                 obj.setProperty(StringConstants::nameProperty(), m.name);
263                 QScriptValue exportsValue = engine->newObject();
264                 obj.setProperty(exportsProperty(), exportsValue);
265                 exportsValue.setProperty(StringConstants::dependenciesProperty(),
266                                          engine->newArray());
267                 for (auto modIt = m.moduleProperties.begin(); modIt != m.moduleProperties.end();
268                      ++modIt) {
269                     const QVariantMap entries = modIt.value().toMap();
270                     if (entries.empty())
271                         continue;
272                     QScriptValue moduleObj = engine->newObject();
273                     ModuleProperties::setModuleScriptValue(exportsValue, moduleObj, modIt.key());
274                     for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt)
275                         moduleObj.setProperty(valIt.key(), engine->toScriptValue(valIt.value()));
276                 }
277                 result.setProperty(idx++, obj);
278             }
279             return result;
280         }
281         for (const auto &dependency : product->modules) {
282             if (dependency->isProduct)
283                 continue;
284             QScriptValue obj = engine->newObject(engine->modulePropertyScriptClass());
285             obj.setPrototype(engine->moduleScriptValuePrototype(dependency.get()));
286 
287             // The prototype must exist already, because we set it up for all modules
288             // of the product in ModuleProperties::init().
289             QBS_ASSERT(obj.prototype().isValid(), ;);
290 
291             const QVariantMap &params = product->moduleParameters.value(dependency);
292             QScriptValue data = getDataForModuleScriptValue(engine, product, nullptr,
293                                                             dependency.get());
294             data.setProperty(DependencyParametersKey, dependencyParametersValue(
295                                  product->uniqueName(), dependency->name, params, engine));
296             obj.setData(data);
297             result.setProperty(idx++, obj);
298         }
299         return result;
300     }
301 
js_internalProductDependencies(QScriptContext * ctx,ScriptEngine * engine,const ResolvedProduct * const product)302     static QScriptValue js_internalProductDependencies(QScriptContext *ctx, ScriptEngine *engine,
303                                                        const ResolvedProduct * const product)
304     {
305         engine->addDependenciesArrayRequested(product);
306         return js_productDependencies(ctx, engine, product, DependencyType::Internal);
307     }
308 
js_exportedProductDependencies(QScriptContext * ctx,ScriptEngine * engine,const ResolvedProduct * const product)309     static QScriptValue js_exportedProductDependencies(QScriptContext *ctx, ScriptEngine *engine,
310                                                        const ResolvedProduct * const product)
311     {
312         return js_productDependencies(ctx, engine, product, DependencyType::Exported);
313     }
314 
315     ScriptEngine *m_engine;
316 };
317 
setupExportedPropertyScriptValue(const ExportedProperty & property,ScriptEngine * engine)318 static QScriptValue setupExportedPropertyScriptValue(const ExportedProperty &property,
319                                                      ScriptEngine *engine)
320 {
321     QScriptValue propertyScriptValue = engine->newObject();
322     propertyScriptValue.setProperty(StringConstants::nameProperty(), property.fullName);
323     propertyScriptValue.setProperty(StringConstants::typeProperty(),
324                                     PropertyDeclaration::typeString(property.type));
325     propertyScriptValue.setProperty(StringConstants::sourceCodeProperty(), property.sourceCode);
326     propertyScriptValue.setProperty(QStringLiteral("isBuiltin"), property.isBuiltin);
327     return propertyScriptValue;
328 }
329 
setupExportedPropertiesScriptValue(QScriptValue & parentObject,const std::vector<ExportedProperty> & properties,ScriptEngine * engine)330 static void setupExportedPropertiesScriptValue(QScriptValue &parentObject,
331                                                const std::vector<ExportedProperty> &properties,
332                                                ScriptEngine *engine)
333 {
334     QScriptValue propertiesScriptValue = engine->newArray(static_cast<uint>(properties.size()));
335     parentObject.setProperty(QStringLiteral("properties"), propertiesScriptValue);
336     quint32 arrayIndex = 0;
337     for (const ExportedProperty &p : properties) {
338         propertiesScriptValue.setProperty(arrayIndex++,
339                                           setupExportedPropertyScriptValue(p, engine));
340     }
341 }
342 
setupExportedItemScriptValue(const ExportedItem * item,ScriptEngine * engine)343 static QScriptValue setupExportedItemScriptValue(const ExportedItem *item, ScriptEngine *engine)
344 {
345     QScriptValue itemScriptValue = engine->newObject();
346     itemScriptValue.setProperty(StringConstants::nameProperty(), item->name);
347     setupExportedPropertiesScriptValue(itemScriptValue, item->properties, engine);
348     QScriptValue childrenScriptValue = engine->newArray(static_cast<uint>(item->children.size()));
349     itemScriptValue.setProperty(childItemsProperty(), childrenScriptValue);
350     quint32 arrayIndex = 0;
351     for (const auto &childItem : item->children) {
352         childrenScriptValue.setProperty(arrayIndex++,
353                                         setupExportedItemScriptValue(childItem.get(), engine));
354     }
355     return itemScriptValue;
356 }
357 
setupExportsScriptValue(const ExportedModule & module,ScriptEngine * engine)358 static QScriptValue setupExportsScriptValue(const ExportedModule &module, ScriptEngine *engine)
359 {
360     QScriptValue exportsScriptValue = engine->newObject();
361     for (auto it = module.propertyValues.cbegin(); it != module.propertyValues.cend(); ++it)
362         exportsScriptValue.setProperty(it.key(), engine->toScriptValue(it.value()));
363     setupExportedPropertiesScriptValue(exportsScriptValue, module.m_properties, engine);
364     QScriptValue childrenScriptValue = engine->newArray(static_cast<uint>(module.children.size()));
365     exportsScriptValue.setProperty(childItemsProperty(), childrenScriptValue);
366     quint32 arrayIndex = 0;
367     for (const auto &exportedItem : module.children) {
368         childrenScriptValue.setProperty(arrayIndex++,
369                                         setupExportedItemScriptValue(exportedItem.get(), engine));
370     }
371     QScriptValue importsScriptValue = engine->newArray(module.importStatements.size());
372     exportsScriptValue.setProperty(StringConstants::importsProperty(), importsScriptValue);
373     arrayIndex = 0;
374     for (const QString &importStatement : module.importStatements)
375         importsScriptValue.setProperty(arrayIndex++, importStatement);
376     for (auto it = module.modulePropertyValues.cbegin(); it != module.modulePropertyValues.cend();
377          ++it) {
378         const QVariantMap entries = it.value().toMap();
379         if (entries.empty())
380             continue;
381         QScriptValue moduleObject = engine->newObject();
382         ModuleProperties::setModuleScriptValue(exportsScriptValue, moduleObject, it.key());
383         for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt)
384             moduleObject.setProperty(valIt.key(), engine->toScriptValue(valIt.value()));
385     }
386     return exportsScriptValue;
387 }
388 
setupProductScriptValue(ScriptEngine * engine,const ResolvedProduct * product)389 static QScriptValue setupProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product)
390 {
391     QScriptValue &productScriptValue = engine->productScriptValuePrototype(product);
392     if (productScriptValue.isValid())
393         return productScriptValue;
394     productScriptValue = engine->newObject();
395     ModuleProperties::init(productScriptValue, product);
396 
397     QScriptValue artifactsFunc = engine->newFunction(&artifactsScriptValueForProduct, product);
398     productScriptValue.setProperty(StringConstants::artifactsProperty(), artifactsFunc,
399                                    QScriptValue::ReadOnly | QScriptValue::Undeletable
400                                    | QScriptValue::PropertyGetter);
401 
402     QScriptValue exportsScriptValue = setupExportsScriptValue(product->exportedModule, engine);
403     DependenciesFunction(engine).init(productScriptValue, exportsScriptValue, product);
404     engine->setObservedProperty(productScriptValue, exportsProperty(), exportsScriptValue);
405     engine->observer()->addExportsObjectId(exportsScriptValue.objectId(), product);
406     return productScriptValue;
407 }
408 
setupScriptEngineForFile(ScriptEngine * engine,const FileContextBaseConstPtr & fileContext,QScriptValue targetObject,const ObserveMode & observeMode)409 void setupScriptEngineForFile(ScriptEngine *engine, const FileContextBaseConstPtr &fileContext,
410         QScriptValue targetObject, const ObserveMode &observeMode)
411 {
412     engine->import(fileContext, targetObject, observeMode);
413     JsExtensions::setupExtensions(fileContext->jsExtensions(), targetObject);
414 }
415 
setupScriptEngineForProduct(ScriptEngine * engine,ResolvedProduct * product,const ResolvedModule * module,QScriptValue targetObject,bool setBuildEnvironment)416 void setupScriptEngineForProduct(ScriptEngine *engine, ResolvedProduct *product,
417                                  const ResolvedModule *module, QScriptValue targetObject,
418                                  bool setBuildEnvironment)
419 {
420     QScriptValue projectScriptValue = setupProjectScriptValue(engine, product->project.lock());
421     targetObject.setProperty(StringConstants::projectVar(), projectScriptValue);
422 
423     if (setBuildEnvironment) {
424         QVariant v;
425         v.setValue<void*>(&product->buildEnvironment);
426         engine->setProperty(StringConstants::qbsProcEnvVarInternal(), v);
427     }
428     QScriptClass *scriptClass = engine->productPropertyScriptClass();
429     if (!scriptClass) {
430         scriptClass = new ProductPropertyScriptClass(engine);
431         engine->setProductPropertyScriptClass(scriptClass);
432     }
433     QScriptValue productScriptValue = engine->newObject(scriptClass);
434     productScriptValue.setPrototype(setupProductScriptValue(engine, product));
435     targetObject.setProperty(StringConstants::productVar(), productScriptValue);
436 
437     QScriptValue data = getDataForProductScriptValue(engine, product);
438     // If the Rule is in a Module, set up the 'moduleName' property
439     if (!module->name.isEmpty())
440         data.setProperty(ModuleNameKey, module->name);
441     productScriptValue.setData(data);
442 }
443 
findPath(BuildGraphNode * u,BuildGraphNode * v,QList<BuildGraphNode * > & path)444 bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList<BuildGraphNode *> &path)
445 {
446     if (u == v) {
447         path.push_back(v);
448         return true;
449     }
450 
451     for (BuildGraphNode * const childNode : qAsConst(u->children)) {
452         if (findPath(childNode, v, path)) {
453             path.prepend(u);
454             return true;
455         }
456     }
457 
458     return false;
459 }
460 
461 /*
462  * Creates the build graph edge p -> c, which represents the dependency "c must be built before p".
463  */
connect(BuildGraphNode * p,BuildGraphNode * c)464 void connect(BuildGraphNode *p, BuildGraphNode *c)
465 {
466     QBS_CHECK(p != c);
467     qCDebug(lcBuildGraph).noquote() << "connect" << p->toString() << "->" << c->toString();
468     if (c->type() == BuildGraphNode::ArtifactNodeType) {
469         auto const ac = static_cast<Artifact *>(c);
470         for (const Artifact *child : filterByType<Artifact>(p->children)) {
471             if (child == ac)
472                 return;
473             const bool filePathsMustBeDifferent = child->artifactType == Artifact::Generated
474                     || child->product == ac->product || child->artifactType != ac->artifactType;
475             if (filePathsMustBeDifferent && child->filePath() == ac->filePath()) {
476                 throw ErrorInfo(QStringLiteral("%1 already has a child artifact %2 as "
477                                                     "different object.").arg(p->toString(),
478                                                                              ac->filePath()),
479                                 CodeLocation(), true);
480             }
481         }
482     }
483     p->children.insert(c);
484     c->parents.insert(p);
485     p->product->topLevelProject()->buildData->setDirty();
486 }
487 
existsPath_impl(BuildGraphNode * u,BuildGraphNode * v,NodeSet * seen)488 static bool existsPath_impl(BuildGraphNode *u, BuildGraphNode *v, NodeSet *seen)
489 {
490     if (u == v)
491         return true;
492 
493     if (!seen->insert(u).second)
494         return false;
495 
496     for (BuildGraphNode * const childNode : qAsConst(u->children)) {
497         if (existsPath_impl(childNode, v, seen))
498             return true;
499     }
500 
501     return false;
502 }
503 
existsPath(BuildGraphNode * u,BuildGraphNode * v)504 static bool existsPath(BuildGraphNode *u, BuildGraphNode *v)
505 {
506     NodeSet seen;
507     return existsPath_impl(u, v, &seen);
508 }
509 
toStringList(const QList<BuildGraphNode * > & path)510 static QStringList toStringList(const QList<BuildGraphNode *> &path)
511 {
512     QStringList lst;
513     for (BuildGraphNode *node : path)
514         lst << node->toString();
515     return lst;
516 }
517 
safeConnect(Artifact * u,Artifact * v)518 bool safeConnect(Artifact *u, Artifact *v)
519 {
520     QBS_CHECK(u != v);
521     qCDebug(lcBuildGraph) << "safeConnect:" << relativeArtifactFileName(u)
522                           << "->" << relativeArtifactFileName(v);
523 
524     if (existsPath(v, u)) {
525         QList<BuildGraphNode *> circle;
526         findPath(v, u, circle);
527         qCDebug(lcBuildGraph) << "safeConnect: circle detected " << toStringList(circle);
528         return false;
529     }
530 
531     connect(u, v);
532     return true;
533 }
534 
disconnect(BuildGraphNode * u,BuildGraphNode * v)535 void disconnect(BuildGraphNode *u, BuildGraphNode *v)
536 {
537     qCDebug(lcBuildGraph).noquote() << "disconnect:" << u->toString() << v->toString();
538     u->children.remove(v);
539     v->parents.remove(u);
540     u->onChildDisconnected(v);
541 }
542 
removeGeneratedArtifactFromDisk(Artifact * artifact,const Logger & logger)543 void removeGeneratedArtifactFromDisk(Artifact *artifact, const Logger &logger)
544 {
545     if (artifact->artifactType != Artifact::Generated)
546         return;
547     removeGeneratedArtifactFromDisk(artifact->filePath(), logger);
548 }
549 
removeGeneratedArtifactFromDisk(const QString & filePath,const Logger & logger)550 void removeGeneratedArtifactFromDisk(const QString &filePath, const Logger &logger)
551 {
552     QFile file(filePath);
553     if (!file.exists())
554         return;
555     logger.qbsDebug() << "removing " << filePath;
556     if (!file.remove())
557         logger.qbsWarning() << QStringLiteral("Cannot remove '%1'.").arg(filePath);
558 }
559 
relativeArtifactFileName(const Artifact * artifact)560 QString relativeArtifactFileName(const Artifact *artifact)
561 {
562     const QString &buildDir = artifact->product->topLevelProject()->buildDirectory;
563     QString str = artifact->filePath();
564     if (str.startsWith(buildDir))
565         str.remove(0, buildDir.size());
566     if (str.startsWith(QLatin1Char('/')))
567         str.remove(0, 1);
568     return str;
569 }
570 
lookupArtifact(const ResolvedProductConstPtr & product,const ProjectBuildData * projectBuildData,const QString & dirPath,const QString & fileName,bool compareByName)571 Artifact *lookupArtifact(const ResolvedProductConstPtr &product,
572         const ProjectBuildData *projectBuildData, const QString &dirPath, const QString &fileName,
573         bool compareByName)
574 {
575     for (const auto &fileResource : projectBuildData->lookupFiles(dirPath, fileName)) {
576         if (fileResource->fileType() != FileResourceBase::FileTypeArtifact)
577             continue;
578         const auto artifact = static_cast<Artifact *>(fileResource);
579         if (compareByName
580                 ? artifact->product->uniqueName() == product->uniqueName()
581                 : artifact->product == product) {
582             return artifact;
583         }
584     }
585     return nullptr;
586 }
587 
lookupArtifact(const ResolvedProductConstPtr & product,const QString & dirPath,const QString & fileName,bool compareByName)588 Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &dirPath,
589                          const QString &fileName, bool compareByName)
590 {
591     return lookupArtifact(product, product->topLevelProject()->buildData.get(), dirPath, fileName,
592                           compareByName);
593 }
594 
lookupArtifact(const ResolvedProductConstPtr & product,const QString & filePath,bool compareByName)595 Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &filePath,
596                          bool compareByName)
597 {
598     QString dirPath, fileName;
599     FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName);
600     return lookupArtifact(product, dirPath, fileName, compareByName);
601 }
602 
lookupArtifact(const ResolvedProductConstPtr & product,const ProjectBuildData * buildData,const QString & filePath,bool compareByName)603 Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const ProjectBuildData *buildData,
604                          const QString &filePath, bool compareByName)
605 {
606     QString dirPath, fileName;
607     FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName);
608     return lookupArtifact(product, buildData, dirPath, fileName, compareByName);
609 }
610 
lookupArtifact(const ResolvedProductConstPtr & product,const Artifact * artifact,bool compareByName)611 Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact *artifact,
612                          bool compareByName)
613 {
614     return lookupArtifact(product, artifact->dirPath(), artifact->fileName(), compareByName);
615 }
616 
createArtifact(const ResolvedProductPtr & product,const SourceArtifactConstPtr & sourceArtifact)617 Artifact *createArtifact(const ResolvedProductPtr &product,
618                          const SourceArtifactConstPtr &sourceArtifact)
619 {
620     const auto artifact = new Artifact;
621     artifact->artifactType = Artifact::SourceFile;
622     setArtifactData(artifact, sourceArtifact);
623     insertArtifact(product, artifact);
624     return artifact;
625 }
626 
setArtifactData(Artifact * artifact,const SourceArtifactConstPtr & sourceArtifact)627 void setArtifactData(Artifact *artifact, const SourceArtifactConstPtr &sourceArtifact)
628 {
629     artifact->targetOfModule = sourceArtifact->targetOfModule;
630     artifact->setFilePath(sourceArtifact->absoluteFilePath);
631     artifact->setFileTags(sourceArtifact->fileTags);
632     artifact->properties = sourceArtifact->properties;
633 }
634 
updateArtifactFromSourceArtifact(const ResolvedProductPtr & product,const SourceArtifactConstPtr & sourceArtifact)635 void updateArtifactFromSourceArtifact(const ResolvedProductPtr &product,
636                                       const SourceArtifactConstPtr &sourceArtifact)
637 {
638     Artifact * const artifact = lookupArtifact(product, sourceArtifact->absoluteFilePath, false);
639     QBS_CHECK(artifact);
640     const FileTags oldFileTags = artifact->fileTags();
641     const QVariantMap oldModuleProperties = artifact->properties->value();
642     setArtifactData(artifact, sourceArtifact);
643     if (oldFileTags != artifact->fileTags()
644             || oldModuleProperties != artifact->properties->value()) {
645         invalidateArtifactAsRuleInputIfNecessary(artifact);
646     }
647 }
648 
insertArtifact(const ResolvedProductPtr & product,Artifact * artifact)649 void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact)
650 {
651     qCDebug(lcBuildGraph) << "insert artifact" << artifact->filePath();
652     QBS_CHECK(!artifact->product);
653     QBS_CHECK(!artifact->filePath().isEmpty());
654     artifact->product = product;
655     product->topLevelProject()->buildData->insertIntoLookupTable(artifact);
656     product->buildData->addArtifact(artifact);
657 }
658 
provideFullFileTagsAndProperties(Artifact * artifact)659 void provideFullFileTagsAndProperties(Artifact *artifact)
660 {
661     artifact->properties = artifact->product->moduleProperties;
662     FileTags allTags = artifact->pureFileTags.empty()
663             ? artifact->product->fileTagsForFileName(artifact->fileName()) : artifact->pureFileTags;
664     for (const auto &props : artifact->product->artifactProperties) {
665         if (allTags.intersects(props->fileTagsFilter())) {
666             artifact->properties = props->propertyMap();
667             allTags += props->extraFileTags();
668             break;
669         }
670     }
671     artifact->setFileTags(allTags);
672 
673     // Let a positive value of qbs.install imply the file tag "installable".
674     if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool())
675         artifact->addFileTag("installable");
676 }
677 
applyPerArtifactProperties(Artifact * artifact)678 void applyPerArtifactProperties(Artifact *artifact)
679 {
680     if (artifact->pureProperties.empty())
681         return;
682     QVariantMap props = artifact->properties->value();
683     for (const auto &property : artifact->pureProperties)
684         setConfigProperty(props, property.first, property.second);
685     artifact->properties = artifact->properties->clone();
686     artifact->properties->setValue(props);
687 }
688 
updateGeneratedArtifacts(ResolvedProduct * product)689 void updateGeneratedArtifacts(ResolvedProduct *product)
690 {
691     if (!product->buildData)
692         return;
693     for (Artifact * const artifact : filterByType<Artifact>(product->buildData->allNodes())) {
694         if (artifact->artifactType == Artifact::Generated) {
695             const FileTags oldFileTags = artifact->fileTags();
696             const QVariantMap oldModuleProperties = artifact->properties->value();
697             provideFullFileTagsAndProperties(artifact);
698             applyPerArtifactProperties(artifact);
699             if (oldFileTags != artifact->fileTags()
700                     || oldModuleProperties != artifact->properties->value()) {
701                 invalidateArtifactAsRuleInputIfNecessary(artifact);
702             }
703         }
704     }
705 }
706 
707 // This is needed for artifacts which are inputs to rules whose outputArtifacts script
708 // returned an empty array for this input. Since there is no transformer, our usual change
709 // tracking procedure will not notice if the artifact's file tags or module properties have
710 // changed, so we need to force a re-run of the outputArtifacts script.
invalidateArtifactAsRuleInputIfNecessary(Artifact * artifact)711 void invalidateArtifactAsRuleInputIfNecessary(Artifact *artifact)
712 {
713     for (RuleNode * const parentRuleNode : filterByType<RuleNode>(artifact->parents)) {
714         if (!parentRuleNode->rule()->isDynamic())
715             continue;
716         bool artifactNeedsExplicitInvalidation = true;
717         for (Artifact * const output : filterByType<Artifact>(parentRuleNode->parents)) {
718             if (output->children.contains(artifact)
719                     && !output->childrenAddedByScanner.contains(artifact)) {
720                 artifactNeedsExplicitInvalidation = false;
721                 break;
722             }
723         }
724         if (artifactNeedsExplicitInvalidation)
725             parentRuleNode->removeOldInputArtifact(artifact);
726     }
727 }
728 
doSanityChecksForProduct(const ResolvedProductConstPtr & product,const Set<ResolvedProductPtr> & allProducts,const Logger & logger)729 static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
730         const Set<ResolvedProductPtr> &allProducts, const Logger &logger)
731 {
732     qCDebug(lcBuildGraph) << "Sanity checking product" << product->uniqueName();
733     CycleDetector cycleDetector(logger);
734     cycleDetector.visitProduct(product);
735     const ProductBuildData * const buildData = product->buildData.get();
736     for (const auto &m : product->modules)
737         QBS_CHECK(m->product == product.get());
738     qCDebug(lcBuildGraph) << "enabled:" << product->enabled << "build data:" << buildData;
739     if (product->enabled)
740         QBS_CHECK(buildData);
741     if (!product->buildData)
742         return;
743     for (BuildGraphNode * const node : qAsConst(buildData->rootNodes())) {
744         qCDebug(lcBuildGraph).noquote() << "Checking root node" << node->toString();
745         QBS_CHECK(buildData->allNodes().contains(node));
746     }
747     Set<QString> filePaths;
748     for (BuildGraphNode * const node : qAsConst(buildData->allNodes())) {
749         qCDebug(lcBuildGraph).noquote() << "Sanity checking node" << node->toString();
750         QBS_CHECK(node->product == product);
751         for (const BuildGraphNode * const parent : qAsConst(node->parents))
752             QBS_CHECK(parent->children.contains(node));
753         for (BuildGraphNode * const child : qAsConst(node->children)) {
754             QBS_CHECK(child->parents.contains(node));
755             QBS_CHECK(!child->product.expired());
756             QBS_CHECK(child->product->buildData);
757             QBS_CHECK(child->product->buildData->allNodes().contains(child));
758             QBS_CHECK(allProducts.contains(child->product.lock()));
759         }
760 
761         Artifact * const artifact = node->type() == BuildGraphNode::ArtifactNodeType
762                 ? static_cast<Artifact *>(node) : nullptr;
763         if (!artifact) {
764             QBS_CHECK(node->type() == BuildGraphNode::RuleNodeType);
765             auto const ruleNode = static_cast<RuleNode *>(node);
766             QBS_CHECK(ruleNode->rule());
767             QBS_CHECK(ruleNode->rule()->product);
768             QBS_CHECK(ruleNode->rule()->product == ruleNode->product.get());
769             QBS_CHECK(ruleNode->rule()->product == product.get());
770             QBS_CHECK(contains(product->rules, std::const_pointer_cast<Rule>(ruleNode->rule())));
771             continue;
772         }
773 
774         QBS_CHECK(product->topLevelProject()->buildData->fileDependencies.contains(
775                       artifact->fileDependencies));
776         QBS_CHECK(artifact->artifactType == Artifact::SourceFile ||
777                   !filePaths.contains(artifact->filePath()));
778         filePaths << artifact->filePath();
779 
780         for (Artifact * const child : qAsConst(artifact->childrenAddedByScanner))
781             QBS_CHECK(artifact->children.contains(child));
782         const TransformerConstPtr transformer = artifact->transformer;
783         if (artifact->artifactType == Artifact::SourceFile)
784             continue;
785 
786         const auto parentRuleNodes = filterByType<RuleNode>(artifact->children);
787         QBS_CHECK(std::distance(parentRuleNodes.begin(), parentRuleNodes.end()) == 1);
788 
789         QBS_CHECK(transformer);
790         QBS_CHECK(transformer->rule);
791         QBS_CHECK(transformer->rule->product);
792         QBS_CHECK(transformer->rule->product == artifact->product.get());
793         QBS_CHECK(transformer->rule->product == product.get());
794         QBS_CHECK(transformer->outputs.contains(artifact));
795         QBS_CHECK(contains(product->rules, std::const_pointer_cast<Rule>(transformer->rule)));
796         qCDebug(lcBuildGraph)
797                 << "The transformer has" << transformer->outputs.size() << "outputs.";
798         ArtifactSet transformerOutputChildren;
799         for (const Artifact * const output : qAsConst(transformer->outputs)) {
800             QBS_CHECK(output->transformer == transformer);
801             transformerOutputChildren.unite(ArtifactSet::filtered(output->children));
802             for (const Artifact *a : filterByType<Artifact>(output->children)) {
803                 for (const Artifact *other : filterByType<Artifact>(output->children)) {
804                     if (other != a && other->filePath() == a->filePath()
805                             && (other->artifactType != Artifact::SourceFile
806                                 || a->artifactType != Artifact::SourceFile
807                                 || other->product == a->product)) {
808                         throw ErrorInfo(QStringLiteral("There is more than one artifact for "
809                                 "file '%1' in the child list for output '%2'.")
810                                 .arg(a->filePath(), output->filePath()), CodeLocation(), true);
811                     }
812                 }
813             }
814         }
815         if (lcBuildGraph().isDebugEnabled()) {
816             qCDebug(lcBuildGraph) << "The transformer output children are:";
817             for (const Artifact * const a : qAsConst(transformerOutputChildren))
818                 qCDebug(lcBuildGraph) << "\t" << a->fileName();
819             qCDebug(lcBuildGraph) << "The transformer inputs are:";
820             for (const Artifact * const a : qAsConst(transformer->inputs))
821                 qCDebug(lcBuildGraph) << "\t" << a->fileName();
822         }
823         QBS_CHECK(transformer->inputs.size() <= transformerOutputChildren.size());
824         for (Artifact * const transformerInput : qAsConst(transformer->inputs))
825             QBS_CHECK(transformerOutputChildren.contains(transformerInput));
826         transformer->artifactsMapRequestedInPrepareScript.doSanityChecks();
827         transformer->artifactsMapRequestedInCommands.doSanityChecks();
828     }
829 }
830 
doSanityChecks(const ResolvedProjectPtr & project,const Set<ResolvedProductPtr> & allProducts,Set<QString> & productNames,const Logger & logger)831 static void doSanityChecks(const ResolvedProjectPtr &project,
832                            const Set<ResolvedProductPtr> &allProducts, Set<QString> &productNames,
833                            const Logger &logger)
834 {
835     logger.qbsDebug() << "Sanity checking project '" << project->name << "'";
836     for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects))
837         doSanityChecks(subProject, allProducts, productNames, logger);
838 
839     for (const auto &product : project->products) {
840         QBS_CHECK(product->project == project);
841         QBS_CHECK(product->topLevelProject() == project->topLevelProject());
842         doSanityChecksForProduct(product, allProducts, logger);
843         QBS_CHECK(!productNames.contains(product->uniqueName()));
844         productNames << product->uniqueName();
845     }
846 }
847 
doSanityChecks(const ResolvedProjectPtr & project,const Logger & logger)848 void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger)
849 {
850     if (qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS"))
851         return;
852     Set<QString> productNames;
853     const auto allProducts = rangeTo<Set<ResolvedProductPtr>>(project->allProducts());
854     doSanityChecks(project, allProducts, productNames, logger);
855 }
856 
857 } // namespace Internal
858 } // namespace qbs
859