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 ¶ms
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 ¶ms = 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