/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "buildgraph.h" #include "artifact.h" #include "artifactsscriptvalue.h" #include "cycledetector.h" #include "dependencyparametersscriptvalue.h" #include "projectbuilddata.h" #include "productbuilddata.h" #include "rulenode.h" #include "scriptclasspropertyiterator.h" #include "transformer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace qbs { namespace Internal { static QString childItemsProperty() { return QStringLiteral("childItems"); } static QString exportsProperty() { return QStringLiteral("exports"); } // TODO: Introduce productscriptvalue.{h,cpp}. static QScriptValue getDataForProductScriptValue(QScriptEngine *engine, const ResolvedProduct *product) { QScriptValue data = engine->newObject(); QVariant v; v.setValue(reinterpret_cast(product)); data.setProperty(ProductPtrKey, engine->newVariant(v)); return data; } class ProductPropertyScriptClass : public QScriptClass { public: ProductPropertyScriptClass(QScriptEngine *engine) : QScriptClass(engine) { } private: QueryFlags queryProperty(const QScriptValue &object, const QScriptString &name, QueryFlags, uint *) override { if (name == StringConstants::parametersProperty()) { m_result = object.data().property(DependencyParametersKey); return HandlesReadAccess; } if (name == StringConstants::moduleNameProperty()) { m_result = object.data().property(ModuleNameKey); return HandlesReadAccess; } if (name == StringConstants::dependenciesProperty() || name == StringConstants::artifactsProperty() || name == exportsProperty()) { // The prototype is not backed by a QScriptClass. m_result = object.prototype().property(name); return HandlesReadAccess; } getProduct(object); QBS_ASSERT(m_product, return {}); const auto it = m_product->productProperties.find(name); // It is important that we reject unknown property names. Otherwise QtScript will forward // *everything* to us, including built-in stuff like the hasOwnProperty function. if (it == m_product->productProperties.cend()) return {}; qbsEngine()->addPropertyRequestedInScript(Property(m_product->uniqueName(), QString(), name, it.value(), Property::PropertyInProduct)); m_result = qbsEngine()->toScriptValue(it.value()); return HandlesReadAccess; } QScriptValue property(const QScriptValue &, const QScriptString &, uint) override { return m_result; } QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override { getProduct(object); QBS_ASSERT(m_product, return nullptr); // These two are in the prototype and are thus common to all product script values. std::vector additionalProperties({StringConstants::artifactsProperty(), StringConstants::dependenciesProperty(), exportsProperty()}); // The "moduleName" convenience property is only available for the "main product" in a rule, // and the "parameters" property exists only for elements of the "dependencies" array for // which dependency parameters are present. if (object.data().property(ModuleNameKey).isValid()) additionalProperties.push_back(StringConstants::moduleNameProperty()); else if (object.data().property(DependencyParametersKey).isValid()) additionalProperties.push_back(StringConstants::parametersProperty()); return new ScriptClassPropertyIterator(object, m_product->productProperties, additionalProperties); } void getProduct(const QScriptValue &object) { if (m_lastObjectId != object.objectId()) { m_lastObjectId = object.objectId(); m_product = reinterpret_cast( object.data().property(ProductPtrKey).toVariant().value()); } } ScriptEngine *qbsEngine() const { return static_cast(engine()); } qint64 m_lastObjectId = 0; const ResolvedProduct *m_product = nullptr; QScriptValue m_result; }; static QScriptValue setupProjectScriptValue(ScriptEngine *engine, const ResolvedProjectConstPtr &project) { QScriptValue &obj = engine->projectScriptValue(project.get()); if (obj.isValid()) return obj; obj = engine->newObject(); obj.setProperty(StringConstants::filePathProperty(), project->location.filePath()); obj.setProperty(StringConstants::pathProperty(), FileInfo::path(project->location.filePath())); const QVariantMap &projectProperties = project->projectProperties(); for (QVariantMap::const_iterator it = projectProperties.begin(); it != projectProperties.end(); ++it) { engine->setObservedProperty(obj, it.key(), engine->toScriptValue(it.value())); } engine->observer()->addProjectObjectId(obj.objectId(), project->name); return obj; } static QScriptValue setupProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product); class DependenciesFunction { public: DependenciesFunction(ScriptEngine *engine) : m_engine(engine) { } void init(QScriptValue &productScriptValue, QScriptValue &exportsScriptValue, const ResolvedProduct *product) { QScriptValue depfunc = m_engine->newFunction(&js_internalProductDependencies, product); productScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc, QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::PropertyGetter); depfunc = m_engine->newFunction(&js_exportedProductDependencies, product); exportsScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc, QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::PropertyGetter); } private: enum class DependencyType { Internal, Exported }; static QScriptValue js_productDependencies(QScriptContext *, ScriptEngine *engine, const ResolvedProduct *product, DependencyType depType) { QScriptValue result = engine->newArray(); quint32 idx = 0; const bool exportCase = depType == DependencyType::Exported; std::vector productDeps; if (exportCase) { if (!product->exportedModule.productDependencies.empty()) { const auto allProducts = product->topLevelProject()->allProducts(); const auto getProductForName = [&allProducts](const QString &name) { const auto cmp = [name](const ResolvedProductConstPtr &p) { return p->uniqueName() == name; }; const auto it = std::find_if(allProducts.cbegin(), allProducts.cend(), cmp); QBS_ASSERT(it != allProducts.cend(), return ResolvedProductPtr()); return *it; }; std::transform(product->exportedModule.productDependencies.cbegin(), product->exportedModule.productDependencies.cend(), std::back_inserter(productDeps), getProductForName); } } else { productDeps = product->dependencies; } for (const ResolvedProductPtr &dependency : qAsConst(productDeps)) { QScriptValue obj = engine->newObject(engine->productPropertyScriptClass()); obj.setPrototype(setupProductScriptValue(static_cast(engine), dependency.get())); const QVariantMap ¶ms = (exportCase ? product->exportedModule.dependencyParameters.value(dependency) : product->dependencyParameters.value(dependency)); QScriptValue data = getDataForProductScriptValue(engine, dependency.get()); data.setProperty(DependencyParametersKey, dependencyParametersValue( product->uniqueName(), dependency->name, params, engine)); obj.setData(data); result.setProperty(idx++, obj); } if (exportCase) { for (const ExportedModuleDependency &m : product->exportedModule.moduleDependencies) { QScriptValue obj = engine->newObject(); obj.setProperty(StringConstants::nameProperty(), m.name); QScriptValue exportsValue = engine->newObject(); obj.setProperty(exportsProperty(), exportsValue); exportsValue.setProperty(StringConstants::dependenciesProperty(), engine->newArray()); for (auto modIt = m.moduleProperties.begin(); modIt != m.moduleProperties.end(); ++modIt) { const QVariantMap entries = modIt.value().toMap(); if (entries.empty()) continue; QScriptValue moduleObj = engine->newObject(); ModuleProperties::setModuleScriptValue(exportsValue, moduleObj, modIt.key()); for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt) moduleObj.setProperty(valIt.key(), engine->toScriptValue(valIt.value())); } result.setProperty(idx++, obj); } return result; } for (const ResolvedModuleConstPtr &dependency : product->modules) { if (dependency->isProduct) continue; QScriptValue obj = engine->newObject(engine->modulePropertyScriptClass()); obj.setPrototype(engine->moduleScriptValuePrototype(dependency.get())); // The prototype must exist already, because we set it up for all modules // of the product in ModuleProperties::init(). QBS_ASSERT(obj.prototype().isValid(), ;); const QVariantMap ¶ms = product->moduleParameters.value(dependency); QScriptValue data = getDataForModuleScriptValue(engine, product, nullptr, dependency.get()); data.setProperty(DependencyParametersKey, dependencyParametersValue( product->uniqueName(), dependency->name, params, engine)); obj.setData(data); result.setProperty(idx++, obj); } return result; } static QScriptValue js_internalProductDependencies(QScriptContext *ctx, ScriptEngine *engine, const ResolvedProduct * const product) { engine->addDependenciesArrayRequested(product); return js_productDependencies(ctx, engine, product, DependencyType::Internal); } static QScriptValue js_exportedProductDependencies(QScriptContext *ctx, ScriptEngine *engine, const ResolvedProduct * const product) { return js_productDependencies(ctx, engine, product, DependencyType::Exported); } ScriptEngine *m_engine; }; static QScriptValue setupExportedPropertyScriptValue(const ExportedProperty &property, ScriptEngine *engine) { QScriptValue propertyScriptValue = engine->newObject(); propertyScriptValue.setProperty(StringConstants::nameProperty(), property.fullName); propertyScriptValue.setProperty(StringConstants::typeProperty(), PropertyDeclaration::typeString(property.type)); propertyScriptValue.setProperty(StringConstants::sourceCodeProperty(), property.sourceCode); propertyScriptValue.setProperty(QStringLiteral("isBuiltin"), property.isBuiltin); return propertyScriptValue; } static void setupExportedPropertiesScriptValue(QScriptValue &parentObject, const std::vector &properties, ScriptEngine *engine) { QScriptValue propertiesScriptValue = engine->newArray(static_cast(properties.size())); parentObject.setProperty(QStringLiteral("properties"), propertiesScriptValue); quint32 arrayIndex = 0; for (const ExportedProperty &p : properties) { propertiesScriptValue.setProperty(arrayIndex++, setupExportedPropertyScriptValue(p, engine)); } } static QScriptValue setupExportedItemScriptValue(const ExportedItem *item, ScriptEngine *engine) { QScriptValue itemScriptValue = engine->newObject(); itemScriptValue.setProperty(StringConstants::nameProperty(), item->name); setupExportedPropertiesScriptValue(itemScriptValue, item->properties, engine); QScriptValue childrenScriptValue = engine->newArray(static_cast(item->children.size())); itemScriptValue.setProperty(childItemsProperty(), childrenScriptValue); quint32 arrayIndex = 0; for (const auto &childItem : item->children) { childrenScriptValue.setProperty(arrayIndex++, setupExportedItemScriptValue(childItem.get(), engine)); } return itemScriptValue; } static QScriptValue setupExportsScriptValue(const ExportedModule &module, ScriptEngine *engine) { QScriptValue exportsScriptValue = engine->newObject(); for (auto it = module.propertyValues.cbegin(); it != module.propertyValues.cend(); ++it) exportsScriptValue.setProperty(it.key(), engine->toScriptValue(it.value())); setupExportedPropertiesScriptValue(exportsScriptValue, module.m_properties, engine); QScriptValue childrenScriptValue = engine->newArray(static_cast(module.children.size())); exportsScriptValue.setProperty(childItemsProperty(), childrenScriptValue); quint32 arrayIndex = 0; for (const auto &exportedItem : module.children) { childrenScriptValue.setProperty(arrayIndex++, setupExportedItemScriptValue(exportedItem.get(), engine)); } QScriptValue importsScriptValue = engine->newArray(module.importStatements.size()); exportsScriptValue.setProperty(StringConstants::importsProperty(), importsScriptValue); arrayIndex = 0; for (const QString &importStatement : module.importStatements) importsScriptValue.setProperty(arrayIndex++, importStatement); for (auto it = module.modulePropertyValues.cbegin(); it != module.modulePropertyValues.cend(); ++it) { const QVariantMap entries = it.value().toMap(); if (entries.empty()) continue; QScriptValue moduleObject = engine->newObject(); ModuleProperties::setModuleScriptValue(exportsScriptValue, moduleObject, it.key()); for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt) moduleObject.setProperty(valIt.key(), engine->toScriptValue(valIt.value())); } return exportsScriptValue; } static QScriptValue setupProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product) { QScriptValue &productScriptValue = engine->productScriptValuePrototype(product); if (productScriptValue.isValid()) return productScriptValue; productScriptValue = engine->newObject(); ModuleProperties::init(productScriptValue, product); QScriptValue artifactsFunc = engine->newFunction(&artifactsScriptValueForProduct, product); productScriptValue.setProperty(StringConstants::artifactsProperty(), artifactsFunc, QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::PropertyGetter); QScriptValue exportsScriptValue = setupExportsScriptValue(product->exportedModule, engine); DependenciesFunction(engine).init(productScriptValue, exportsScriptValue, product); engine->setObservedProperty(productScriptValue, exportsProperty(), exportsScriptValue); engine->observer()->addExportsObjectId(exportsScriptValue.objectId(), product); return productScriptValue; } void setupScriptEngineForFile(ScriptEngine *engine, const FileContextBaseConstPtr &fileContext, QScriptValue targetObject, const ObserveMode &observeMode) { engine->import(fileContext, targetObject, observeMode); JsExtensions::setupExtensions(fileContext->jsExtensions(), targetObject); } void setupScriptEngineForProduct(ScriptEngine *engine, ResolvedProduct *product, const ResolvedModule *module, QScriptValue targetObject, bool setBuildEnvironment) { QScriptValue projectScriptValue = setupProjectScriptValue(engine, product->project.lock()); targetObject.setProperty(StringConstants::projectVar(), projectScriptValue); if (setBuildEnvironment) { QVariant v; v.setValue(&product->buildEnvironment); engine->setProperty(StringConstants::qbsProcEnvVarInternal(), v); } QScriptClass *scriptClass = engine->productPropertyScriptClass(); if (!scriptClass) { scriptClass = new ProductPropertyScriptClass(engine); engine->setProductPropertyScriptClass(scriptClass); } QScriptValue productScriptValue = engine->newObject(scriptClass); productScriptValue.setPrototype(setupProductScriptValue(engine, product)); targetObject.setProperty(StringConstants::productVar(), productScriptValue); QScriptValue data = getDataForProductScriptValue(engine, product); // If the Rule is in a Module, set up the 'moduleName' property if (!module->name.isEmpty()) data.setProperty(ModuleNameKey, module->name); productScriptValue.setData(data); } bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList &path) { if (u == v) { path.push_back(v); return true; } for (BuildGraphNode * const childNode : qAsConst(u->children)) { if (findPath(childNode, v, path)) { path.prepend(u); return true; } } return false; } /* * Creates the build graph edge p -> c, which represents the dependency "c must be built before p". */ void connect(BuildGraphNode *p, BuildGraphNode *c) { QBS_CHECK(p != c); qCDebug(lcBuildGraph).noquote() << "connect" << p->toString() << "->" << c->toString(); if (c->type() == BuildGraphNode::ArtifactNodeType) { auto const ac = static_cast(c); for (const Artifact *child : filterByType(p->children)) { if (child == ac) return; const bool filePathsMustBeDifferent = child->artifactType == Artifact::Generated || child->product == ac->product || child->artifactType != ac->artifactType; if (filePathsMustBeDifferent && child->filePath() == ac->filePath()) { throw ErrorInfo(QStringLiteral("%1 already has a child artifact %2 as " "different object.").arg(p->toString(), ac->filePath()), CodeLocation(), true); } } } p->children.insert(c); c->parents.insert(p); p->product->topLevelProject()->buildData->setDirty(); } static bool existsPath_impl(BuildGraphNode *u, BuildGraphNode *v, NodeSet *seen) { if (u == v) return true; if (!seen->insert(u).second) return false; for (BuildGraphNode * const childNode : qAsConst(u->children)) { if (existsPath_impl(childNode, v, seen)) return true; } return false; } static bool existsPath(BuildGraphNode *u, BuildGraphNode *v) { NodeSet seen; return existsPath_impl(u, v, &seen); } static QStringList toStringList(const QList &path) { QStringList lst; for (BuildGraphNode *node : path) lst << node->toString(); return lst; } bool safeConnect(Artifact *u, Artifact *v) { QBS_CHECK(u != v); qCDebug(lcBuildGraph) << "safeConnect:" << relativeArtifactFileName(u) << "->" << relativeArtifactFileName(v); if (existsPath(v, u)) { QList circle; findPath(v, u, circle); qCDebug(lcBuildGraph) << "safeConnect: circle detected " << toStringList(circle); return false; } connect(u, v); return true; } void disconnect(BuildGraphNode *u, BuildGraphNode *v) { qCDebug(lcBuildGraph).noquote() << "disconnect:" << u->toString() << v->toString(); u->children.remove(v); v->parents.remove(u); u->onChildDisconnected(v); } void removeGeneratedArtifactFromDisk(Artifact *artifact, const Logger &logger) { if (artifact->artifactType != Artifact::Generated) return; removeGeneratedArtifactFromDisk(artifact->filePath(), logger); } void removeGeneratedArtifactFromDisk(const QString &filePath, const Logger &logger) { QFile file(filePath); if (!file.exists()) return; logger.qbsDebug() << "removing " << filePath; if (!file.remove()) logger.qbsWarning() << QStringLiteral("Cannot remove '%1'.").arg(filePath); } QString relativeArtifactFileName(const Artifact *artifact) { const QString &buildDir = artifact->product->topLevelProject()->buildDirectory; QString str = artifact->filePath(); if (str.startsWith(buildDir)) str.remove(0, buildDir.size()); if (str.startsWith(QLatin1Char('/'))) str.remove(0, 1); return str; } Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const ProjectBuildData *projectBuildData, const QString &dirPath, const QString &fileName, bool compareByName) { for (const auto &fileResource : projectBuildData->lookupFiles(dirPath, fileName)) { if (fileResource->fileType() != FileResourceBase::FileTypeArtifact) continue; const auto artifact = static_cast(fileResource); if (compareByName ? artifact->product->uniqueName() == product->uniqueName() : artifact->product == product) { return artifact; } } return nullptr; } Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &dirPath, const QString &fileName, bool compareByName) { return lookupArtifact(product, product->topLevelProject()->buildData.get(), dirPath, fileName, compareByName); } Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &filePath, bool compareByName) { QString dirPath, fileName; FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName); return lookupArtifact(product, dirPath, fileName, compareByName); } Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const ProjectBuildData *buildData, const QString &filePath, bool compareByName) { QString dirPath, fileName; FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName); return lookupArtifact(product, buildData, dirPath, fileName, compareByName); } Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact *artifact, bool compareByName) { return lookupArtifact(product, artifact->dirPath(), artifact->fileName(), compareByName); } Artifact *createArtifact(const ResolvedProductPtr &product, const SourceArtifactConstPtr &sourceArtifact) { const auto artifact = new Artifact; artifact->artifactType = Artifact::SourceFile; setArtifactData(artifact, sourceArtifact); insertArtifact(product, artifact); return artifact; } void setArtifactData(Artifact *artifact, const SourceArtifactConstPtr &sourceArtifact) { artifact->targetOfModule = sourceArtifact->targetOfModule; artifact->setFilePath(sourceArtifact->absoluteFilePath); artifact->setFileTags(sourceArtifact->fileTags); artifact->properties = sourceArtifact->properties; } void updateArtifactFromSourceArtifact(const ResolvedProductPtr &product, const SourceArtifactConstPtr &sourceArtifact) { Artifact * const artifact = lookupArtifact(product, sourceArtifact->absoluteFilePath, false); QBS_CHECK(artifact); const FileTags oldFileTags = artifact->fileTags(); const QVariantMap oldModuleProperties = artifact->properties->value(); setArtifactData(artifact, sourceArtifact); if (oldFileTags != artifact->fileTags() || oldModuleProperties != artifact->properties->value()) { invalidateArtifactAsRuleInputIfNecessary(artifact); } } void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact) { qCDebug(lcBuildGraph) << "insert artifact" << artifact->filePath(); QBS_CHECK(!artifact->product); QBS_CHECK(!artifact->filePath().isEmpty()); artifact->product = product; product->topLevelProject()->buildData->insertIntoLookupTable(artifact); product->buildData->addArtifact(artifact); } void provideFullFileTagsAndProperties(Artifact *artifact) { artifact->properties = artifact->product->moduleProperties; FileTags allTags = artifact->pureFileTags.empty() ? artifact->product->fileTagsForFileName(artifact->fileName()) : artifact->pureFileTags; for (const ArtifactPropertiesConstPtr &props : artifact->product->artifactProperties) { if (allTags.intersects(props->fileTagsFilter())) { artifact->properties = props->propertyMap(); allTags += props->extraFileTags(); break; } } artifact->setFileTags(allTags); // Let a positive value of qbs.install imply the file tag "installable". if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) artifact->addFileTag("installable"); } void applyPerArtifactProperties(Artifact *artifact) { if (artifact->pureProperties.empty()) return; QVariantMap props = artifact->properties->value(); for (const auto &property : artifact->pureProperties) setConfigProperty(props, property.first, property.second); artifact->properties = artifact->properties->clone(); artifact->properties->setValue(props); } void updateGeneratedArtifacts(ResolvedProduct *product) { if (!product->buildData) return; for (Artifact * const artifact : filterByType(product->buildData->allNodes())) { if (artifact->artifactType == Artifact::Generated) { const FileTags oldFileTags = artifact->fileTags(); const QVariantMap oldModuleProperties = artifact->properties->value(); provideFullFileTagsAndProperties(artifact); applyPerArtifactProperties(artifact); if (oldFileTags != artifact->fileTags() || oldModuleProperties != artifact->properties->value()) { invalidateArtifactAsRuleInputIfNecessary(artifact); } } } } // This is needed for artifacts which are inputs to rules whose outputArtifacts script // returned an empty array for this input. Since there is no transformer, our usual change // tracking procedure will not notice if the artifact's file tags or module properties have // changed, so we need to force a re-run of the outputArtifacts script. void invalidateArtifactAsRuleInputIfNecessary(Artifact *artifact) { for (RuleNode * const parentRuleNode : filterByType(artifact->parents)) { if (!parentRuleNode->rule()->isDynamic()) continue; bool artifactNeedsExplicitInvalidation = true; for (Artifact * const output : filterByType(parentRuleNode->parents)) { if (output->children.contains(artifact) && !output->childrenAddedByScanner.contains(artifact)) { artifactNeedsExplicitInvalidation = false; break; } } if (artifactNeedsExplicitInvalidation) parentRuleNode->removeOldInputArtifact(artifact); } } static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, const Set &allProducts, const Logger &logger) { qCDebug(lcBuildGraph) << "Sanity checking product" << product->uniqueName(); CycleDetector cycleDetector(logger); cycleDetector.visitProduct(product); const ProductBuildData * const buildData = product->buildData.get(); for (const ResolvedModuleConstPtr &m : product->modules) QBS_CHECK(m->product == product.get()); qCDebug(lcBuildGraph) << "enabled:" << product->enabled << "build data:" << buildData; if (product->enabled) QBS_CHECK(buildData); if (!product->buildData) return; for (BuildGraphNode * const node : qAsConst(buildData->rootNodes())) { qCDebug(lcBuildGraph).noquote() << "Checking root node" << node->toString(); QBS_CHECK(buildData->allNodes().contains(node)); } Set filePaths; for (BuildGraphNode * const node : qAsConst(buildData->allNodes())) { qCDebug(lcBuildGraph).noquote() << "Sanity checking node" << node->toString(); QBS_CHECK(node->product == product); for (const BuildGraphNode * const parent : qAsConst(node->parents)) QBS_CHECK(parent->children.contains(node)); for (BuildGraphNode * const child : qAsConst(node->children)) { QBS_CHECK(child->parents.contains(node)); QBS_CHECK(!child->product.expired()); QBS_CHECK(child->product->buildData); QBS_CHECK(child->product->buildData->allNodes().contains(child)); QBS_CHECK(allProducts.contains(child->product.lock())); } Artifact * const artifact = node->type() == BuildGraphNode::ArtifactNodeType ? static_cast(node) : nullptr; if (!artifact) { QBS_CHECK(node->type() == BuildGraphNode::RuleNodeType); auto const ruleNode = static_cast(node); QBS_CHECK(ruleNode->rule()); QBS_CHECK(ruleNode->rule()->product); QBS_CHECK(ruleNode->rule()->product == ruleNode->product.get()); QBS_CHECK(ruleNode->rule()->product == product.get()); QBS_CHECK(contains(product->rules, std::const_pointer_cast(ruleNode->rule()))); continue; } QBS_CHECK(product->topLevelProject()->buildData->fileDependencies.contains( artifact->fileDependencies)); QBS_CHECK(artifact->artifactType == Artifact::SourceFile || !filePaths.contains(artifact->filePath())); filePaths << artifact->filePath(); for (Artifact * const child : qAsConst(artifact->childrenAddedByScanner)) QBS_CHECK(artifact->children.contains(child)); const TransformerConstPtr transformer = artifact->transformer; if (artifact->artifactType == Artifact::SourceFile) continue; const auto parentRuleNodes = filterByType(artifact->children); QBS_CHECK(std::distance(parentRuleNodes.begin(), parentRuleNodes.end()) == 1); QBS_CHECK(transformer); QBS_CHECK(transformer->rule); QBS_CHECK(transformer->rule->product); QBS_CHECK(transformer->rule->product == artifact->product.get()); QBS_CHECK(transformer->rule->product == product.get()); QBS_CHECK(transformer->outputs.contains(artifact)); QBS_CHECK(contains(product->rules, std::const_pointer_cast(transformer->rule))); qCDebug(lcBuildGraph) << "The transformer has" << transformer->outputs.size() << "outputs."; ArtifactSet transformerOutputChildren; for (const Artifact * const output : qAsConst(transformer->outputs)) { QBS_CHECK(output->transformer == transformer); transformerOutputChildren.unite(ArtifactSet::filtered(output->children)); for (const Artifact *a : filterByType(output->children)) { for (const Artifact *other : filterByType(output->children)) { if (other != a && other->filePath() == a->filePath() && (other->artifactType != Artifact::SourceFile || a->artifactType != Artifact::SourceFile || other->product == a->product)) { throw ErrorInfo(QStringLiteral("There is more than one artifact for " "file '%1' in the child list for output '%2'.") .arg(a->filePath(), output->filePath()), CodeLocation(), true); } } } } if (lcBuildGraph().isDebugEnabled()) { qCDebug(lcBuildGraph) << "The transformer output children are:"; for (const Artifact * const a : qAsConst(transformerOutputChildren)) qCDebug(lcBuildGraph) << "\t" << a->fileName(); qCDebug(lcBuildGraph) << "The transformer inputs are:"; for (const Artifact * const a : qAsConst(transformer->inputs)) qCDebug(lcBuildGraph) << "\t" << a->fileName(); } QBS_CHECK(transformer->inputs.size() <= transformerOutputChildren.size()); for (Artifact * const transformerInput : qAsConst(transformer->inputs)) QBS_CHECK(transformerOutputChildren.contains(transformerInput)); transformer->artifactsMapRequestedInPrepareScript.doSanityChecks(); transformer->artifactsMapRequestedInCommands.doSanityChecks(); } } static void doSanityChecks(const ResolvedProjectPtr &project, const Set &allProducts, Set &productNames, const Logger &logger) { logger.qbsDebug() << "Sanity checking project '" << project->name << "'"; for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects)) doSanityChecks(subProject, allProducts, productNames, logger); for (const ResolvedProductConstPtr &product : project->products) { QBS_CHECK(product->project == project); QBS_CHECK(product->topLevelProject() == project->topLevelProject()); doSanityChecksForProduct(product, allProducts, logger); QBS_CHECK(!productNames.contains(product->uniqueName())); productNames << product->uniqueName(); } } void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger) { if (qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) return; Set productNames; const Set allProducts = Set::fromStdVector(project->allProducts()); doSanityChecks(project, allProducts, productNames, logger); } } // namespace Internal } // namespace qbs