/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing ** ** This file is part of Qbs. ** ** 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 http://www.qt.io/terms-conditions. For further information ** use the contact form at http://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 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "moduleloader.h" #include "builtindeclarations.h" #include "builtinvalue.h" #include "evaluator.h" #include "filecontext.h" #include "item.h" #include "itemreader.h" #include "modulemerger.h" #include "qualifiedid.h" #include "scriptengine.h" #include "value.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace qbs { namespace Internal { class ModuleLoader::ItemModuleList : public QList {}; const QString moduleSearchSubDir = QLatin1String("modules"); class ModuleLoader::ProductSortByDependencies { public: ProductSortByDependencies(TopLevelProjectContext &tlp) : m_tlp(tlp) { } void apply() { QHash productsMap; QList allProducts; for (auto projIt = m_tlp.projects.begin(); projIt != m_tlp.projects.end(); ++projIt) { QVector &products = (*projIt)->products; for (auto prodIt = products.begin(); prodIt != products.end(); ++prodIt) { allProducts << prodIt; productsMap.insert(prodIt->name, prodIt); } } QSet allDependencies; foreach (auto productContext, allProducts) { auto &productDependencies = m_dependencyMap[productContext]; foreach (const auto &dep, productContext->info.usedProducts) { if (!dep.productTypes.isEmpty()) continue; QBS_CHECK(!dep.name.isEmpty()); ProductContext * const depProduct = productsMap.value(dep.name); QBS_CHECK(depProduct); productDependencies << depProduct; allDependencies << depProduct; } } QSet rootProducts = allProducts.toSet() - allDependencies; foreach (ProductContext * const rootProduct, rootProducts) traverse(rootProduct); } // No product at position i has dependencies to a product at position j > i. QList sortedProducts() { return m_sortedProducts; } private: void traverse(ModuleLoader::ProductContext *product) { if (m_seenProducts.contains(product)) return; m_seenProducts << product; foreach (auto dependency, m_dependencyMap.value(product)) traverse(dependency); m_sortedProducts << product; } TopLevelProjectContext &m_tlp; QHash> m_dependencyMap; QSet m_seenProducts; QList m_sortedProducts; }; class SearchPathsManager { public: SearchPathsManager(ItemReader *itemReader, const QStringList &extraSearchPaths) : m_itemReader(itemReader) { m_itemReader->pushExtraSearchPaths(extraSearchPaths); } ~SearchPathsManager() { m_itemReader->popExtraSearchPaths(); } private: ItemReader * const m_itemReader; }; ModuleLoader::ModuleLoader(ScriptEngine *engine, const Logger &logger) : m_engine(engine) , m_pool(0) , m_logger(logger) , m_progressObserver(0) , m_reader(new ItemReader(logger)) , m_evaluator(new Evaluator(engine, logger)) { } ModuleLoader::~ModuleLoader() { delete m_evaluator; delete m_reader; } void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver) { m_progressObserver = progressObserver; } static void addExtraModuleSearchPath(QStringList &list, const QString &searchPath) { list += FileInfo::resolvePath(searchPath, moduleSearchSubDir); } void ModuleLoader::setSearchPaths(const QStringList &searchPaths) { m_reader->setSearchPaths(searchPaths); m_moduleDirListCache.clear(); m_moduleSearchPaths.clear(); foreach (const QString &path, searchPaths) addExtraModuleSearchPath(m_moduleSearchPaths, path); if (m_logger.traceEnabled()) { m_logger.qbsTrace() << "[MODLDR] module search paths:"; foreach (const QString &path, m_moduleSearchPaths) m_logger.qbsTrace() << " " << path; } } ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] load" << parameters.projectFilePath(); m_parameters = parameters; m_productModuleCache.clear(); m_modulePrototypeItemCache.clear(); m_disabledItems.clear(); m_reader->clearExtraSearchPathsStack(); ModuleLoaderResult result; m_pool = result.itemPool.data(); m_reader->setPool(m_pool); const QStringList topLevelSearchPaths = parameters.finalBuildConfigurationTree() .value(QLatin1String("project")).toMap() .value(QLatin1String("qbsSearchPaths")).toStringList(); Item *root; { SearchPathsManager searchPathsManager(m_reader, topLevelSearchPaths); root = m_reader->readFile(parameters.projectFilePath()); if (!root) return ModuleLoaderResult(); } root = wrapInProjectIfNecessary(root); const QString buildDirectory = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(), TopLevelProject::deriveId(parameters.topLevelProfile(), parameters.finalBuildConfigurationTree())); root->setProperty(QLatin1String("sourceDirectory"), VariantValue::create(QFileInfo(root->file()->filePath()).absolutePath())); root->setProperty(QLatin1String("buildDirectory"), VariantValue::create(buildDirectory)); root->setProperty(QLatin1String("profile"), VariantValue::create(m_parameters.topLevelProfile())); handleTopLevelProject(&result, root, buildDirectory, QSet() << QDir::cleanPath(parameters.projectFilePath())); result.root = root; result.qbsFiles = m_reader->filesRead(); return result; } static void handlePropertyError(const ErrorInfo &error, const SetupProjectParameters ¶ms, Logger logger) { if (params.propertyCheckingMode() == ErrorHandlingMode::Strict) throw error; logger.printWarning(error); } class PropertyDeclarationCheck : public ValueHandler { const QSet &m_disabledItems; Item *m_parentItem; QString m_currentName; SetupProjectParameters m_params; Logger m_logger; public: PropertyDeclarationCheck(const QSet &disabledItems, const SetupProjectParameters ¶ms, const Logger &logger) : m_disabledItems(disabledItems) , m_parentItem(0) , m_params(params) , m_logger(logger) { } void operator()(Item *item) { handleItem(item); } private: void handle(JSSourceValue *value) { if (!value->createdByPropertiesBlock()) { const ErrorInfo error(Tr::tr("Property '%1' is not declared.") .arg(m_currentName), value->location()); handlePropertyError(error, m_params, m_logger); } } void handle(ItemValue *value) { if (value->item()->type() != ItemType::ModuleInstance && value->item()->type() != ItemType::ModulePrefix && m_parentItem->file() && (!m_parentItem->file()->idScope() || !m_parentItem->file()->idScope()->hasProperty(m_currentName)) && !value->createdByPropertiesBlock()) { const ErrorInfo error(Tr::tr("Item '%1' is not declared. " "Did you forget to add a Depends item?").arg(m_currentName), value->location().isValid() ? value->location() : m_parentItem->location()); handlePropertyError(error, m_params, m_logger); } else { handleItem(value->item()); } } void handleItem(Item *item) { if (m_disabledItems.contains(item) // TODO: We never checked module prototypes, apparently. Should we? // It's currently not possible because of e.g. things like "cpp.staticLibraries" // inside Artifact items... || item->type() == ItemType::Module // The Properties child of a SubProject item is not a regular item. || item->type() == ItemType::PropertiesInSubProject) { return; } Item *oldParentItem = m_parentItem; m_parentItem = item; for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); it != item->properties().constEnd(); ++it) { if (item->propertyDeclaration(it.key()).isValid()) continue; m_currentName = it.key(); it.value()->apply(this); } m_parentItem = oldParentItem; foreach (Item *child, item->children()) { if (child->type () != ItemType::Export) handleItem(child); } // Properties that don't refer to an existing module with a matching Depends item // only exist in the prototype of an Export item, not in the instance. // Example 1 - setting a property of an unknown module: Export { abc.def: true } // Example 2 - setting a non-existing Export property: Export { blubb: true } if (item->type() == ItemType::ModuleInstance && item->prototype()) handleItem(item->prototype()); } void handle(VariantValue *) { /* only created internally - no need to check */ } void handle(BuiltinValue *) { /* only created internally - no need to check */ } }; void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem, const QString &buildDirectory, const QSet &referencedFilePaths) { TopLevelProjectContext tlp; tlp.buildDirectory = buildDirectory; handleProject(loadResult, &tlp, projectItem, referencedFilePaths); foreach (ProjectContext *projectContext, tlp.projects) { m_reader->setExtraSearchPathsStack(projectContext->searchPathsStack); for (auto it = projectContext->products.begin(); it != projectContext->products.end(); ++it) { try { setupProductDependencies(it); } catch (const ErrorInfo &err) { if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict || it->name.isEmpty()) { throw err; } m_logger.printWarning(err); ModuleLoaderResult::ProductInfo pi; pi.hasError = true; it->project->result->productInfos.insert(it->item, pi); m_disabledItems << it->item; } } } ProductSortByDependencies productSorter(tlp); productSorter.apply(); foreach (ProductContext * const p, productSorter.sortedProducts()) handleProduct(p); m_reader->clearExtraSearchPathsStack(); checkItemTypes(projectItem); PropertyDeclarationCheck check(m_disabledItems, m_parameters, m_logger); check(projectItem); } void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, TopLevelProjectContext *topLevelProjectContext, Item *projectItem, const QSet &referencedFilePaths) { auto *p = new ProjectContext; auto &projectContext = *p; projectContext.topLevelProject = topLevelProjectContext; projectContext.result = loadResult; ItemValuePtr itemValue = ItemValue::create(projectItem); projectContext.scope = Item::create(m_pool); projectContext.scope->setFile(projectItem->file()); projectContext.scope->setProperty(QLatin1String("project"), itemValue); ProductContext dummyProductContext; dummyProductContext.project = &projectContext; dummyProductContext.moduleProperties = m_parameters.finalBuildConfigurationTree(); loadBaseModule(&dummyProductContext, projectItem); overrideItemProperties(projectItem, QLatin1String("project"), m_parameters.overriddenValuesTree()); const QString projectName = m_evaluator->stringValue(projectItem, QLatin1String("name")); if (!projectName.isEmpty()) overrideItemProperties(projectItem, projectName, m_parameters.overriddenValuesTree()); if (!checkItemCondition(projectItem)) { delete p; return; } topLevelProjectContext->projects << &projectContext; m_reader->pushExtraSearchPaths(readExtraSearchPaths(projectItem) << projectItem->file()->dirPath()); projectContext.searchPathsStack = m_reader->extraSearchPathsStack(); projectContext.item = projectItem; const QString minVersionStr = m_evaluator->stringValue(projectItem, QLatin1String("minimumQbsVersion"), QLatin1String("1.3.0")); const Version minVersion = Version::fromString(minVersionStr); if (!minVersion.isValid()) { throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion " "is not a valid version string.").arg(minVersionStr), projectItem->location()); } if (!m_qbsVersion.isValid()) m_qbsVersion = Version::fromString(QLatin1String(QBS_VERSION)); if (m_qbsVersion < minVersion) { throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but " "this is qbs version %2.").arg(minVersion.toString(), m_qbsVersion.toString())); } foreach (Item *child, projectItem->children()) { child->setScope(projectContext.scope); if (child->type() == ItemType::Product) { foreach (Item * const additionalProductItem, multiplexProductItem(&dummyProductContext, child)) { Item::addChild(projectItem, additionalProductItem); } } } foreach (Item *child, projectItem->children()) { switch (child->type()) { case ItemType::Product: prepareProduct(&projectContext, child); break; case ItemType::SubProject: handleSubProject(&projectContext, child, referencedFilePaths); break; case ItemType::Project: copyProperties(projectItem, child); handleProject(loadResult, topLevelProjectContext, child, referencedFilePaths); break; default: break; } } const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); const QStringList refs = m_evaluator->stringListValue(projectItem, QLatin1String("references")); typedef QPair ItemAndRefPath; QList additionalProjectChildren; foreach (const QString &filePath, refs) { QString absReferencePath = FileInfo::resolvePath(projectFileDirPath, filePath); if (FileInfo(absReferencePath).isDir()) { QString qbsFilePath; QDirIterator dit(absReferencePath, QStringList(QLatin1String("*.qbs"))); while (dit.hasNext()) { if (!qbsFilePath.isEmpty()) { throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " "qbs file.").arg(absReferencePath), projectItem->property(QLatin1String("references"))->location()); } qbsFilePath = dit.next(); } if (qbsFilePath.isEmpty()) { throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") .arg(absReferencePath), projectItem->property(QLatin1String("references"))->location()); } absReferencePath = qbsFilePath; } if (referencedFilePaths.contains(absReferencePath)) throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(filePath), projectItem->property(QLatin1String("references"))->location()); Item *subItem = m_reader->readFile(absReferencePath); subItem->setScope(projectContext.scope); subItem->setParent(projectContext.item); additionalProjectChildren << qMakePair(subItem, absReferencePath); if (subItem->type() == ItemType::Product) { foreach (Item * const additionalProductItem, multiplexProductItem(&dummyProductContext, subItem)) { additionalProjectChildren << qMakePair(additionalProductItem, absReferencePath); } } } foreach (const ItemAndRefPath &irp, additionalProjectChildren) { Item * const subItem = irp.first; Item::addChild(projectContext.item, subItem); switch (subItem->type()) { case ItemType::Product: prepareProduct(&projectContext, subItem); break; case ItemType::Project: copyProperties(projectItem, subItem); handleProject(loadResult, topLevelProjectContext, subItem, QSet(referencedFilePaths) << irp.second); break; default: throw ErrorInfo(Tr::tr("The top-level item of a file in a \"references\" list must be " "a Product or a Project, but it is \"%1\".") .arg(subItem->typeName()), subItem->location()); } } m_reader->popExtraSearchPaths(); } QList ModuleLoader::multiplexProductItem(ProductContext *dummyContext, Item *productItem) { // Temporarily attach the qbs module here, in case we need to access one of its properties // to evaluate the profiles property. const QString qbsKey = QLatin1String("qbs"); ValuePtr qbsValue = productItem->property(qbsKey); // Retrieve now to restore later. if (qbsValue) qbsValue = qbsValue->clone(); loadBaseModule(dummyContext, productItem); // Overriding the product item properties must be done here already, because otherwise // the "profiles" property would not be overridable. QString productName = m_evaluator->stringValue(productItem, QLatin1String("name")); if (productName.isEmpty()) { productName = FileInfo::completeBaseName(productItem->file()->filePath()); productItem->setProperty(QLatin1String("name"), VariantValue::create(productName)); } overrideItemProperties(productItem, productName, m_parameters.overriddenValuesTree()); const QString profilesKey = QLatin1String("profiles"); const ValueConstPtr profilesValue = productItem->property(profilesKey); QBS_CHECK(profilesValue); // Default value set in BuiltinDeclarations. const QStringList profileNames = m_evaluator->stringListValue(productItem, profilesKey); if (profileNames.isEmpty()) { throw ErrorInfo(Tr::tr("The 'profiles' property cannot be an empty list."), profilesValue->location()); } foreach (const QString &profileName, profileNames) { if (profileNames.count(profileName) > 1) { throw ErrorInfo(Tr::tr("The profile '%1' appears in the 'profiles' list twice, " "which is not allowed.").arg(profileName), profilesValue->location()); } } // "Unload" the qbs module again. if (qbsValue) productItem->setProperty(qbsKey, qbsValue); else productItem->removeProperty(qbsKey); productItem->removeModules(); QList additionalProductItems; const QString profileKey = QLatin1String("profile"); productItem->setProperty(profileKey, VariantValue::create(profileNames.first())); Settings settings(m_parameters.settingsDirectory()); for (int i = 0; i < profileNames.count(); ++i) { Profile profile(profileNames.at(i), &settings); if (!profile.exists()) { throw ErrorInfo(Tr::tr("The profile '%1' does not exist.").arg(profile.name()), productItem->location()); // TODO: profilesValue->location() is invalid, why? } if (i == 0) continue; // We use the original item for the first profile. Item * const cloned = productItem->clone(); cloned->setProperty(profileKey, VariantValue::create(profileNames.at(i))); additionalProductItems << cloned; } return additionalProductItems; } void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productItem) { checkCancelation(); if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] prepareProduct " << productItem->file()->filePath(); ProductContext productContext; productContext.name = m_evaluator->stringValue(productItem, QLatin1String("name")); QBS_CHECK(!productContext.name.isEmpty()); bool profilePropertySet; productContext.profileName = m_evaluator->stringValue(productItem, QLatin1String("profile"), QString(), &profilePropertySet); QBS_CHECK(profilePropertySet); const QVariantMap::ConstIterator it = projectContext->result->profileConfigs.find(productContext.profileName); if (it == projectContext->result->profileConfigs.constEnd()) { const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration( m_parameters.settingsDirectory(), productContext.profileName, m_parameters.buildVariant()); productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( buildConfig, m_parameters.overriddenValues(), m_parameters.buildRoot(), m_parameters.topLevelProfile()); projectContext->result->profileConfigs.insert(productContext.profileName, productContext.moduleProperties); } else { productContext.moduleProperties = it.value().toMap(); } productContext.item = productItem; productContext.project = projectContext; initProductProperties(productContext); ItemValuePtr itemValue = ItemValue::create(productItem); productContext.scope = Item::create(m_pool); productContext.scope->setProperty(QLatin1String("product"), itemValue); productContext.scope->setFile(productItem->file()); productContext.scope->setScope(productContext.project->scope); mergeExportItems(productContext); setScopeForDescendants(productItem, productContext.scope); projectContext->products << productContext; } void ModuleLoader::setupProductDependencies(ProductContext *productContext) { checkCancelation(); Item *item = productContext->item; if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] handleProduct " << item->file()->filePath(); QStringList extraSearchPaths = readExtraSearchPaths(item); Settings settings(m_parameters.settingsDirectory()); const QStringList prefsSearchPaths = Preferences(&settings, productContext->profileName).searchPaths(); foreach (const QString &p, prefsSearchPaths) { if (!m_moduleSearchPaths.contains(p) && FileInfo(p).exists()) extraSearchPaths << p; } SearchPathsManager searchPathsManager(m_reader, extraSearchPaths); DependsContext dependsContext; dependsContext.product = productContext; dependsContext.productDependencies = &productContext->info.usedProducts; resolveDependencies(&dependsContext, item); addTransitiveDependencies(productContext); productContext->project->result->productInfos.insert(item, productContext->info); } // Non-dependencies first. static void createSortedModuleList(const Item::Module &parentModule, QVector &modules) { if (modules.contains(parentModule)) return; foreach (const Item::Module &dep, parentModule.item->modules()) createSortedModuleList(dep, modules); modules << parentModule; return; } void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext) { Item * const item = productContext->item; Item::Modules mergedModules; foreach (const Item::Module &module, item->modules()) { Item::Module mergedModule = module; ModuleMerger(m_logger, item, mergedModule).start(); mergedModules << mergedModule; } item->setModules(mergedModules); // Must happen after all modules have been merged, so needs to be a second loop. QVector sortedModules; foreach (const Item::Module &module, item->modules()) createSortedModuleList(module, sortedModules); QBS_CHECK(sortedModules.count() == item->modules().count()); foreach (const Item::Module &module, sortedModules) { if (!module.item->isPresentModule()) continue; try { resolveProbes(module.item); m_evaluator->boolValue(module.item, QLatin1String("validate")); } catch (const ErrorInfo &error) { if (module.required) { // Error will be thrown for enabled products only module.item->setDelayedError(error); } else { createNonPresentModule(module.name.toString(), QLatin1String("failed validation"), module.item); } } } resolveProbes(item); if (!checkItemCondition(item)) { Item * const productModule = m_productModuleCache.value(productContext->name); if (productModule && productModule->isPresentModule()) createNonPresentModule(productContext->name, QLatin1String("disabled"), productModule); } copyGroupsFromModulesToProduct(*productContext); foreach (Item *child, item->children()) { if (child->type() == ItemType::Group) handleGroup(productContext, child); } } void ModuleLoader::initProductProperties(const ProductContext &product) { QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name, product.profileName); buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir); product.item->setProperty(QLatin1String("buildDirectory"), VariantValue::create(buildDir)); const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath(); product.item->setProperty(QLatin1String("sourceDirectory"), VariantValue::create(sourceDir)); } void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *projectItem, const QSet &referencedFilePaths) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] handleSubProject " << projectItem->file()->filePath(); Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject); bool subProjectEnabled = true; if (propertiesItem) { propertiesItem->setScope(projectItem); subProjectEnabled = checkItemCondition(propertiesItem); } if (!subProjectEnabled) return; const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); const QString relativeFilePath = m_evaluator->stringValue(projectItem, QLatin1String("filePath")); QString subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); if (referencedFilePaths.contains(subProjectFilePath)) throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") .arg(relativeFilePath), projectItem->location()); Item *loadedItem = m_reader->readFile(subProjectFilePath); loadedItem = wrapInProjectIfNecessary(loadedItem); const bool inheritProperties = m_evaluator->boolValue(projectItem, QLatin1String("inheritProperties"), true); if (inheritProperties) copyProperties(projectItem->parent(), loadedItem); if (propertiesItem) { const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); for (Item::PropertyMap::ConstIterator it = overriddenProperties.constBegin(); it != overriddenProperties.constEnd(); ++it) { loadedItem->setProperty(it.key(), overriddenProperties.value(it.key())); } } Item::addChild(projectItem, loadedItem); projectItem->setScope(projectContext->scope); handleProject(projectContext->result, projectContext->topLevelProject, loadedItem, QSet(referencedFilePaths) << subProjectFilePath); } void ModuleLoader::handleGroup(ProductContext *productContext, Item *groupItem) { checkCancelation(); propagateModulesFromProduct(productContext, groupItem); checkItemCondition(groupItem); } static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value) { if (value->type() == Value::ItemValueType) { Item *valueItem = value.staticCast()->item(); Item *subItem = dst->itemProperty(name, true)->item(); for (QMap::const_iterator it = valueItem->properties().constBegin(); it != valueItem->properties().constEnd(); ++it) mergeProperty(subItem, it.key(), it.value()); } else { // If the property already exists set up the base value. if (value->type() == Value::JSSourceValueType) { const ValuePtr baseValue = dst->property(name); if (baseValue) { QBS_CHECK(baseValue->type() == Value::JSSourceValueType); const JSSourceValuePtr jsBaseValue = baseValue->clone().staticCast(); JSSourceValue *jsValue = static_cast(value.data()); jsValue->setBaseValue(jsBaseValue); } } dst->setProperty(name, value); } } bool ModuleLoader::checkExportItemCondition(Item *exportItem, const ProductContext &productContext) { class ScopeHandler { public: ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem) : m_exportItem(exportItem) { if (!*cachedScopeItem) *cachedScopeItem = Item::create(exportItem->pool()); Item * const scope = *cachedScopeItem; QBS_CHECK(productContext.item->file()); scope->setFile(productContext.item->file()); scope->setScope(productContext.item); productContext.project->scope->copyProperty(QLatin1String("project"), scope); productContext.scope->copyProperty(QLatin1String("product"), scope); QBS_CHECK(!exportItem->scope()); exportItem->setScope(scope); } ~ScopeHandler() { m_exportItem->setScope(nullptr); } private: Item * const m_exportItem; } scopeHandler(exportItem, productContext, &m_tempScopeItem); return checkItemCondition(exportItem); } void ModuleLoader::mergeExportItems(const ProductContext &productContext) { QVector exportItems; QList children = productContext.item->children(); for (int i = 0; i < children.count();) { Item * const child = children.at(i); if (child->type() == ItemType::Export) { exportItems << child; children.removeAt(i); } else { ++i; } } // Note that we do not return if there are no Export items: The "merged" item becomes the // "product module", which always needs to exist, regardless of whether the product sources // actually contain an Export item or not. if (!exportItems.isEmpty()) productContext.item->setChildren(children); Item *merged = Item::create(productContext.item->pool(), ItemType::Export); QSet filesWithExportItem; foreach (Item *exportItem, exportItems) { checkCancelation(); if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file()))) throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), exportItem->location()); if (!checkExportItemCondition(exportItem, productContext)) continue; filesWithExportItem += exportItem->file(); foreach (Item *child, exportItem->children()) Item::addChild(merged, child); for (QMap::const_iterator it = exportItem->properties().constBegin(); it != exportItem->properties().constEnd(); ++it) { mergeProperty(merged, it.key(), it.value()); } } merged->setFile(exportItems.isEmpty() ? productContext.item->file() : exportItems.last()->file()); merged->setLocation(exportItems.isEmpty() ? productContext.item->location() : exportItems.last()->location()); Item::addChild(productContext.item, merged); merged->setupForBuiltinType(m_logger); ProductModuleInfo &pmi = productContext.project->topLevelProject->productModules[productContext.name]; pmi.exportItem = merged; } void ModuleLoader::propagateModulesFromProduct(ProductContext *productContext, Item *groupItem) { QBS_CHECK(groupItem->type() == ItemType::Group); for (Item::Modules::const_iterator it = productContext->item->modules().constBegin(); it != productContext->item->modules().constEnd(); ++it) { Item::Module m = *it; Item *targetItem = moduleInstanceItem(groupItem, m.name); targetItem->setPrototype(m.item); targetItem->setType(ItemType::ModuleInstance); targetItem->setScope(m.item->scope()); targetItem->setModules(m.item->modules()); // "parent" should point to the group/artifact parent targetItem->setParent(groupItem->parent()); // the outer item of a module is the product's instance of it targetItem->setOuterItem(m.item); // ### Is this always the same as the scope item? m.item = targetItem; groupItem->addModule(m); } } void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *item) { loadBaseModule(dependsContext->product, item); // Resolve all Depends items. ItemModuleList loadedModules; ProductDependencyResults productDependencies; foreach (Item *child, item->children()) if (child->type() == ItemType::Depends) resolveDependsItem(dependsContext, item, child, &loadedModules, &productDependencies); foreach (const Item::Module &module, loadedModules) item->addModule(module); dependsContext->productDependencies->append(productDependencies); } class RequiredChainManager { public: RequiredChainManager(QStack &requiredChain, bool required) : m_requiredChain(requiredChain) { m_requiredChain.push(required); } ~RequiredChainManager() { m_requiredChain.pop(); } private: QStack &m_requiredChain; }; void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *parentItem, Item *dependsItem, ItemModuleList *moduleResults, ProductDependencyResults *productResults) { checkCancelation(); if (!checkItemCondition(dependsItem)) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "Depends item disabled, ignoring."; return; } bool productTypesIsSet; const FileTags productTypes = m_evaluator->fileTagsValue(dependsItem, QLatin1String("productTypes"), &productTypesIsSet); bool nameIsSet; const QString name = m_evaluator->stringValue(dependsItem, QLatin1String("name"), QString(), &nameIsSet); bool submodulesPropertySet; QStringList submodules = m_evaluator->stringListValue(dependsItem, QLatin1String("submodules"), &submodulesPropertySet); if (productTypesIsSet) { if (nameIsSet) { throw ErrorInfo(Tr::tr("The 'productTypes' and 'name' properties are mutually " "exclusive."), dependsItem->location()); } if (submodulesPropertySet) { throw ErrorInfo(Tr::tr("The 'productTypes' and 'subModules' properties are mutually " "exclusive."), dependsItem->location()); } if (productTypes.isEmpty()) { m_logger.qbsTrace() << "Ignoring Depends item with empty productTypes list."; return; } // TODO: We could also filter by the "profiles" property. This would required a refactoring // (Dependency needs a list of profiles and the multiplexing must happen later). ModuleLoaderResult::ProductInfo::Dependency dependency; dependency.productTypes = productTypes; dependency.limitToSubProject = m_evaluator->boolValue(dependsItem, QLatin1String("limitToSubProject")); productResults->append(dependency); return; } if (submodules.isEmpty() && submodulesPropertySet) { m_logger.qbsTrace() << "Ignoring Depends item with empty submodules list."; return; } if (Q_UNLIKELY(submodules.count() > 1 && !dependsItem->id().isEmpty())) { QString msg = Tr::tr("A Depends item with more than one module cannot have an id."); throw ErrorInfo(msg, dependsItem->location()); } QList moduleNames; const QualifiedId nameParts = QualifiedId::fromString(name); if (submodules.isEmpty()) { // Ignore explicit dependencies on the base module, which has already been loaded. if (name == QStringLiteral("qbs")) return; moduleNames << nameParts; } else { foreach (const QString &submodule, submodules) moduleNames << nameParts + QualifiedId::fromString(submodule); } Item::Module result; foreach (const QualifiedId &moduleName, moduleNames) { bool isRequired = m_evaluator->boolValue(dependsItem, QLatin1String("required")); for (int i = m_requiredChain.count() - 1; i >= 0 && isRequired; --i) { if (!m_requiredChain.at(i)) isRequired = false; } // Don't load the same module twice. Duplicate Depends statements can easily // happen due to inheritance. const auto it = std::find_if(moduleResults->begin(), moduleResults->end(), [moduleName](const Item::Module &m) { return m.name == moduleName; }); if (it != moduleResults->end()) { if (isRequired) it->required = true; continue; } RequiredChainManager requiredChainManager(m_requiredChain, isRequired); Item *moduleItem = loadModule(dependsContext->product, parentItem, dependsItem->location(), dependsItem->id(), moduleName, isRequired, &result.isProduct); if (!moduleItem) { // ### 1.5: change error message to the more generic "Dependency '%1' not found.". throw ErrorInfo(Tr::tr("Product dependency '%1' not found.").arg(moduleName.toString()), dependsItem->location()); } if (m_logger.traceEnabled()) m_logger.qbsTrace() << "module loaded: " << moduleName.toString(); result.name = moduleName; result.item = moduleItem; result.required = isRequired; moduleResults->append(result); if (result.isProduct) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "product dependency loaded: " << moduleName.toString(); const QString profilesKey = QLatin1String("profiles"); const QStringList profiles = m_evaluator->stringListValue(dependsItem, profilesKey); if (profiles.isEmpty()) { ModuleLoaderResult::ProductInfo::Dependency dependency; dependency.name = moduleName.toString(); dependency.profile = QLatin1String("*"); dependency.isRequired = isRequired; productResults->append(dependency); continue; } foreach (const QString &profile, profiles) { ModuleLoaderResult::ProductInfo::Dependency dependency; dependency.name = moduleName.toString(); dependency.profile = profile; dependency.isRequired = isRequired; productResults->append(dependency); } } } } Q_NORETURN static void throwModuleNamePrefixError(const QualifiedId &shortName, const QualifiedId &longName, const CodeLocation &codeLocation) { throw ErrorInfo(Tr::tr("The name of module '%1' is equal to the first component of the " "name of module '%2', which is not allowed") .arg(shortName.toString(), longName.toString()), codeLocation); } Item *ModuleLoader::moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName) { QBS_CHECK(!moduleName.isEmpty()); Item *instance = containerItem; for (int i = 0; i < moduleName.count(); ++i) { const QString &moduleNameSegment = moduleName.at(i); const ValuePtr v = instance->properties().value(moduleName.at(i)); if (v && v->type() == Value::ItemValueType) { instance = v.staticCast()->item(); } else { Item *newItem = Item::create(m_pool); instance->setProperty(moduleNameSegment, ItemValue::create(newItem)); instance = newItem; } if (i < moduleName.count() - 1) { if (instance->type() == ItemType::ModuleInstance) { QualifiedId conflictingName = QStringList(moduleName.mid(0, i + 1)); throwModuleNamePrefixError(conflictingName, moduleName, CodeLocation()); } if (instance->type() != ItemType::ModulePrefix) { QBS_CHECK(instance->type() == ItemType::Unknown); instance->setType(ItemType::ModulePrefix); } } } QBS_CHECK(instance != containerItem); return instance; } Item *ModuleLoader::loadProductModule(ModuleLoader::ProductContext *productContext, const QString &moduleName) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] loadProductModule name: " << moduleName; Item *module = m_productModuleCache.value(moduleName); if (module) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] loadProductModule cache hit."; return module; } ProductModuleInfo &pmi = productContext->project->topLevelProject->productModules[moduleName]; module = pmi.exportItem; if (module) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] loadProductModule cache miss."; DependsContext dependsContext; dependsContext.product = productContext; dependsContext.productDependencies = &pmi.productDependencies; resolveDependencies(&dependsContext, module); m_productModuleCache.insert(moduleName, module); } return module; } Item *ModuleLoader::loadModule(ProductContext *productContext, Item *item, const CodeLocation &dependsItemLocation, const QString &moduleId, const QualifiedId &moduleName, bool isRequired, bool *isProductDependency) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] loadModule name: " << moduleName << ", id: " << moduleId; Item *moduleInstance = moduleId.isEmpty() ? moduleInstanceItem(item, moduleName) : moduleInstanceItem(item, QStringList(moduleId)); if (moduleInstance->type() == ItemType::ModuleInstance) { // already handled return moduleInstance; } if (Q_UNLIKELY(moduleInstance->type() == ItemType::ModulePrefix)) { foreach (const Item::Module &m, item->modules()) { if (m.name.first() == moduleName.first()) throwModuleNamePrefixError(moduleName, m.name, dependsItemLocation); } } QBS_CHECK(moduleInstance->type() == ItemType::Unknown); *isProductDependency = true; Item *modulePrototype = loadProductModule(productContext, moduleName.toString()); if (!modulePrototype) { *isProductDependency = false; QStringList moduleSearchPaths; foreach (const QString &searchPath, m_reader->searchPaths()) addExtraModuleSearchPath(moduleSearchPaths, searchPath); bool cacheHit = false; modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation, moduleName, moduleSearchPaths, isRequired, &cacheHit); static const QualifiedId baseModuleId = QualifiedId(QLatin1String("qbs")); if (modulePrototype && !cacheHit && moduleName == baseModuleId) setupBaseModulePrototype(modulePrototype); } if (!modulePrototype) return 0; instantiateModule(productContext, nullptr, item, moduleInstance, modulePrototype, moduleName, *isProductDependency); return moduleInstance; } Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext, const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, const QStringList &extraSearchPaths, bool isRequired, bool *cacheHit) { QStringList searchPaths = extraSearchPaths; searchPaths.append(m_moduleSearchPaths); bool triedToLoadModule = false; const QString fullName = moduleName.toString(); foreach (const QString &path, searchPaths) { const QString dirPath = findExistingModulePath(path, moduleName); if (dirPath.isEmpty()) continue; QStringList moduleFileNames = m_moduleDirListCache.value(dirPath); if (moduleFileNames.isEmpty()) { QDirIterator dirIter(dirPath, QStringList(QLatin1String("*.qbs"))); while (dirIter.hasNext()) moduleFileNames += dirIter.next(); m_moduleDirListCache.insert(dirPath, moduleFileNames); } foreach (const QString &filePath, moduleFileNames) { triedToLoadModule = true; Item *module = loadModuleFile(productContext, fullName, moduleName.count() == 1 && moduleName.first() == QLatin1String("qbs"), filePath, cacheHit, &triedToLoadModule); if (module) return module; if (!triedToLoadModule) m_moduleDirListCache[dirPath].removeOne(filePath); } } if (!isRequired) return createNonPresentModule(fullName, QLatin1String("not found"), nullptr); if (Q_UNLIKELY(triedToLoadModule)) throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName), dependsItemLocation); return 0; } // returns QVariant::Invalid for types that do not need conversion static QVariant::Type variantType(PropertyDeclaration::Type t) { switch (t) { case PropertyDeclaration::UnknownType: break; case PropertyDeclaration::Boolean: return QVariant::Bool; case PropertyDeclaration::Integer: return QVariant::Int; case PropertyDeclaration::Path: return QVariant::String; case PropertyDeclaration::PathList: return QVariant::StringList; case PropertyDeclaration::String: return QVariant::String; case PropertyDeclaration::StringList: return QVariant::StringList; case PropertyDeclaration::Variant: break; } return QVariant::Invalid; } static QVariant convertToPropertyType(const QVariant &v, PropertyDeclaration::Type t, const QStringList &namePrefix, const QString &key) { if (v.isNull() || !v.isValid()) return v; const QVariant::Type vt = variantType(t); if (vt == QVariant::Invalid) return v; // Handle the foo,bar,bla stringlist syntax. if (t == PropertyDeclaration::StringList && v.type() == QVariant::String) return v.toString().split(QLatin1Char(',')); QVariant c = v; if (!c.convert(vt)) { QStringList name = namePrefix; name << key; throw ErrorInfo(Tr::tr("Value '%1' of property '%2' has incompatible type.") .arg(v.toString(), name.join(QLatin1Char('.')))); } return c; } Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString &fullModuleName, bool isBaseModule, const QString &filePath, bool *cacheHit, bool *triedToLoad) { checkCancelation(); if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] trying to load " << fullModuleName << " from " << filePath; const ModuleItemCache::key_type cacheKey(filePath, productContext->profileName); const ItemCacheValue cacheValue = m_modulePrototypeItemCache.value(cacheKey); if (cacheValue.module) { m_logger.qbsTrace() << "[LDR] loadModuleFile cache hit for " << filePath; *cacheHit = true; return cacheValue.enabled ? cacheValue.module : 0; } *cacheHit = false; Item * const module = m_reader->readFile(filePath); if (module->type() != ItemType::Module) { if (m_logger.traceEnabled()) { m_logger.qbsTrace() << "[MODLDR] Alleged module " << fullModuleName << " has type '" << module->typeName() << "', so it's not a module after all."; } *triedToLoad = false; return 0; } if (!isBaseModule) { DependsContext dependsContext; dependsContext.product = productContext; dependsContext.productDependencies = &productContext->info.usedProducts; resolveDependencies(&dependsContext, module); } // Module properties that are defined in the profile are used as default values. const QVariantMap profileModuleProperties = productContext->moduleProperties.value(fullModuleName).toMap(); QList unknownProfilePropertyErrors; for (QVariantMap::const_iterator vmit = profileModuleProperties.begin(); vmit != profileModuleProperties.end(); ++vmit) { if (Q_UNLIKELY(!module->hasProperty(vmit.key()))) { const ErrorInfo error(Tr::tr("Unknown property: %1.%2").arg(fullModuleName, vmit.key())); unknownProfilePropertyErrors.append(error); continue; } const PropertyDeclaration decl = module->propertyDeclaration(vmit.key()); VariantValuePtr v = VariantValue::create(convertToPropertyType(vmit.value(), decl.type(), QStringList(fullModuleName), vmit.key())); module->setProperty(vmit.key(), v); } // Check the condition last in case the condition needs to evaluate other properties that were // set by the profile if (!checkItemCondition(module)) { m_logger.qbsTrace() << "[LDR] module condition is false"; m_modulePrototypeItemCache.insert(cacheKey, ItemCacheValue(module, false)); return 0; } foreach (const ErrorInfo &error, unknownProfilePropertyErrors) handlePropertyError(error, m_parameters, m_logger); module->setProperty(QLatin1String("name"), VariantValue::create(fullModuleName)); m_modulePrototypeItemCache.insert(cacheKey, ItemCacheValue(module, true)); return module; } void ModuleLoader::loadBaseModule(ProductContext *productContext, Item *item) { const QualifiedId baseModuleName(QLatin1String("qbs")); Item::Module baseModuleDesc; baseModuleDesc.name = baseModuleName; baseModuleDesc.item = loadModule(productContext, item, CodeLocation(), QString(), baseModuleName, true, &baseModuleDesc.isProduct); QBS_CHECK(!baseModuleDesc.isProduct); if (Q_UNLIKELY(!baseModuleDesc.item)) throw ErrorInfo(Tr::tr("Cannot load base qbs module.")); item->addModule(baseModuleDesc); } static QStringList hostOS() { QStringList hostSystem; #if defined(Q_OS_AIX) hostSystem << QLatin1String("aix"); #endif #if defined(Q_OS_ANDROID) hostSystem << QLatin1String("android"); #endif #if defined(Q_OS_BLACKBERRY) hostSystem << QLatin1String("blackberry"); #endif #if defined(Q_OS_BSD4) hostSystem << QLatin1String("bsd") << QLatin1String("bsd4"); #endif #if defined(Q_OS_BSDI) hostSystem << QLatin1String("bsdi"); #endif #if defined(Q_OS_CYGWIN) hostSystem << QLatin1String("cygwin"); #endif #if defined(Q_OS_DARWIN) hostSystem << QLatin1String("darwin"); #endif #if defined(Q_OS_DGUX) hostSystem << QLatin1String("dgux"); #endif #if defined(Q_OS_DYNIX) hostSystem << QLatin1String("dynix"); #endif #if defined(Q_OS_FREEBSD) hostSystem << QLatin1String("freebsd"); #endif #if defined(Q_OS_HPUX) hostSystem << QLatin1String("hpux"); #endif #if defined(Q_OS_HURD) hostSystem << QLatin1String("hurd"); #endif #if defined(Q_OS_INTEGRITY) hostSystem << QLatin1String("integrity"); #endif #if defined(Q_OS_IOS) hostSystem << QLatin1String("ios"); #endif #if defined(Q_OS_IRIX) hostSystem << QLatin1String("irix"); #endif #if defined(Q_OS_LINUX) hostSystem << QLatin1String("linux"); #endif #if defined(Q_OS_LYNX) hostSystem << QLatin1String("lynx"); #endif #if defined(Q_OS_MACOS) || defined(Q_OS_OSX) hostSystem << QLatin1String("macos") << QLatin1String("osx"); #endif #if defined(Q_OS_MSDOS) hostSystem << QLatin1String("msdos"); #endif #if defined(Q_OS_NACL) hostSystem << QLatin1String("nacl"); #endif #if defined(Q_OS_NETBSD) hostSystem << QLatin1String("netbsd"); #endif #if defined(Q_OS_OPENBSD) hostSystem << QLatin1String("openbsd"); #endif #if defined(Q_OS_OS2) hostSystem << QLatin1String("os2"); #endif #if defined(Q_OS_OS2EMX) hostSystem << QLatin1String("os2emx"); #endif #if defined(Q_OS_OSF) hostSystem << QLatin1String("osf"); #endif #if defined(Q_OS_QNX) hostSystem << QLatin1String("qnx"); #endif #if defined(Q_OS_ONX6) hostSystem << QLatin1String("qnx6"); #endif #if defined(Q_OS_RELIANT) hostSystem << QLatin1String("reliant"); #endif #if defined(Q_OS_SCO) hostSystem << QLatin1String("sco"); #endif #if defined(Q_OS_SOLARIS) hostSystem << QLatin1String("solaris"); #endif #if defined(Q_OS_SYMBIAN) hostSystem << QLatin1String("symbian"); #endif #if defined(Q_OS_ULTRIX) hostSystem << QLatin1String("ultrix"); #endif #if defined(Q_OS_UNIX) hostSystem << QLatin1String("unix"); #endif #if defined(Q_OS_UNIXWARE) hostSystem << QLatin1String("unixware"); #endif #if defined(Q_OS_VXWORKS) hostSystem << QLatin1String("vxworks"); #endif #if defined(Q_OS_WIN32) hostSystem << QLatin1String("windows"); #endif #if defined(Q_OS_WINCE) hostSystem << QLatin1String("windowsce"); #endif #if defined(Q_OS_WINPHONE) hostSystem << QLatin1String("windowsphone"); #endif #if defined(Q_OS_WINRT) hostSystem << QLatin1String("winrt"); #endif return hostSystem; } void ModuleLoader::setupBaseModulePrototype(Item *prototype) { prototype->setProperty(QLatin1String("getEnv"), BuiltinValue::create(BuiltinValue::GetEnvFunction)); prototype->setProperty(QLatin1String("currentEnv"), BuiltinValue::create(BuiltinValue::CurrentEnvFunction)); prototype->setProperty(QLatin1String("hostOS"), VariantValue::create(hostOS())); prototype->setProperty(QLatin1String("libexecPath"), VariantValue::create(m_parameters.libexecPath())); const Version qbsVersion = Version::qbsVersion(); prototype->setProperty(QLatin1String("versionMajor"), VariantValue::create(qbsVersion.majorVersion())); prototype->setProperty(QLatin1String("versionMinor"), VariantValue::create(qbsVersion.minorVersion())); prototype->setProperty(QLatin1String("versionPatch"), VariantValue::create(qbsVersion.patchLevel())); } static void collectItemsWithId_impl(Item *item, QList *result) { if (!item->id().isEmpty()) result->append(item); foreach (Item *child, item->children()) collectItemsWithId_impl(child, result); } static QList collectItemsWithId(Item *item) { QList result; collectItemsWithId_impl(item, &result); return result; } static void copyPropertiesFromExportItem(const Item *src, Item *dst) { const Item::PropertyMap &srcProps = src->properties(); for (auto it = srcProps.constBegin(); it != srcProps.constEnd(); ++it) { if (it.value()->type() != Value::JSSourceValueType) continue; QBS_CHECK(!dst->hasOwnProperty(it.key())); dst->setProperty(it.key(), it.value()->clone()); } } void ModuleLoader::instantiateModule(ProductContext *productContext, Item *exportingProduct, Item *instanceScope, Item *moduleInstance, Item *modulePrototype, const QualifiedId &moduleName, bool isProduct) { const QString fullName = moduleName.toString(); moduleInstance->setPrototype(modulePrototype); moduleInstance->setFile(modulePrototype->file()); moduleInstance->setLocation(modulePrototype->location()); QBS_CHECK(moduleInstance->type() != ItemType::ModuleInstance); moduleInstance->setType(ItemType::ModuleInstance); // create module scope Item *moduleScope = Item::create(m_pool); QBS_CHECK(instanceScope->file()); moduleScope->setFile(instanceScope->file()); moduleScope->setScope(instanceScope); QBS_CHECK(productContext->project->scope); productContext->project->scope->copyProperty(QLatin1String("project"), moduleScope); if (productContext->scope) productContext->scope->copyProperty(QLatin1String("product"), moduleScope); else QBS_CHECK(moduleName.toString() == QLatin1String("qbs")); // Dummy product. if (isProduct) { exportingProduct = 0; for (Item *exportItem = modulePrototype; exportItem && !exportingProduct; exportItem = exportItem->prototype()) { // exportItem is either of type ModuleInstance or Export. Only the latter has // a parent item, which is always of type Product. exportingProduct = exportItem->parent(); } QBS_CHECK(exportingProduct); QBS_CHECK(exportingProduct->type() == ItemType::Product); } if (exportingProduct) { if (!isProduct) { copyPropertiesFromExportItem(modulePrototype, moduleInstance); moduleInstance->setPrototype(modulePrototype->prototype()); } Item *exportScope = Item::create(moduleInstance->pool()); exportScope->setFile(instanceScope->file()); exportScope->setScope(instanceScope); // TODO: For consistency with modules, it should be the other way around, i.e. // "exportingProduct" and just "product". exportScope->setProperty(QLatin1String("product"), ItemValue::create(exportingProduct)); exportScope->setProperty(QLatin1String("importingProduct"), ItemValue::create(productContext->item)); exportScope->setProperty(QLatin1String("project"), ItemValue::create(exportingProduct->parent())); for (auto it = moduleInstance->properties().begin(); it != moduleInstance->properties().end(); ++it) { if (it.value()->type() != Value::JSSourceValueType) continue; JSSourceValuePtr v = it.value().staticCast(); do { v->setExportScope(exportScope); v = v->baseValue(); } while (v); } PropertyDeclaration pd(QLatin1String("_qbs_sourceDir"), PropertyDeclaration::String, PropertyDeclaration::PropertyNotAvailableInConfig); moduleInstance->setPropertyDeclaration(pd.name(), pd); ValuePtr v = exportingProduct->property(QLatin1String("sourceDirectory"))->clone(); moduleInstance->setProperty(pd.name(), v); } moduleInstance->setScope(moduleScope); QHash prototypeInstanceMap; prototypeInstanceMap[modulePrototype] = moduleInstance; // create instances for every child of the prototype createChildInstances(productContext, moduleInstance, modulePrototype, &prototypeInstanceMap); // create ids from from the prototype in the instance if (modulePrototype->file()->idScope()) { foreach (Item *itemWithId, collectItemsWithId(modulePrototype)) { Item *idProto = itemWithId; Item *idInstance = prototypeInstanceMap.value(idProto); QBS_ASSERT(idInstance, continue); ItemValuePtr idInstanceValue = ItemValue::create(idInstance); moduleScope->setProperty(itemWithId->id(), idInstanceValue); } } // create module instances for the dependencies of this module foreach (Item::Module m, modulePrototype->modules()) { Item *depinst = moduleInstanceItem(moduleInstance, m.name); const bool safetyCheck = true; if (safetyCheck) { Item *obj = moduleInstance; for (int i = 0; i < m.name.count(); ++i) { ItemValuePtr iv = obj->itemProperty(m.name.at(i)); QBS_CHECK(iv); obj = iv->item(); } QBS_CHECK(obj == depinst); } QBS_ASSERT(depinst != m.item, continue); instantiateModule(productContext, isProduct ? exportingProduct : nullptr, moduleInstance, depinst, m.item, m.name, m.isProduct); m.item = depinst; moduleInstance->addModule(m); } // override module properties given on the command line const QVariantMap userModuleProperties = m_parameters.overriddenValuesTree().value(fullName).toMap(); for (QVariantMap::const_iterator vmit = userModuleProperties.begin(); vmit != userModuleProperties.end(); ++vmit) { if (Q_UNLIKELY(!moduleInstance->hasProperty(vmit.key()))) { const ErrorInfo error(Tr::tr("Unknown property: %1.%2") .arg(moduleName.toString(), vmit.key())); handlePropertyError(error, m_parameters, m_logger); continue; } const PropertyDeclaration decl = moduleInstance->propertyDeclaration(vmit.key()); moduleInstance->setProperty(vmit.key(), VariantValue::create(convertToPropertyType(vmit.value(), decl.type(), moduleName, vmit.key()))); } } void ModuleLoader::createChildInstances(ProductContext *productContext, Item *instance, Item *prototype, QHash *prototypeInstanceMap) const { foreach (Item *childPrototype, prototype->children()) { Item *childInstance = Item::create(m_pool, childPrototype->type()); prototypeInstanceMap->insert(childPrototype, childInstance); childInstance->setPrototype(childPrototype); childInstance->setTypeName(childPrototype->typeName()); childInstance->setFile(childPrototype->file()); childInstance->setLocation(childPrototype->location()); childInstance->setScope(productContext->scope); Item::addChild(instance, childInstance); createChildInstances(productContext, childInstance, childPrototype, prototypeInstanceMap); } } void ModuleLoader::resolveProbes(Item *item) { foreach (Item *child, item->children()) if (child->type() == ItemType::Probe) resolveProbe(item, child); } void ModuleLoader::resolveProbe(Item *parent, Item *probe) { m_logger.qbsTrace() << "Resolving Probe at " << probe->location().toString(); const JSSourceValueConstPtr configureScript = probe->sourceProperty(QLatin1String("configure")); if (Q_UNLIKELY(!configureScript)) throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location()); typedef QPair ProbeProperty; QList probeBindings; for (Item *obj = probe; obj; obj = obj->prototype()) { foreach (const QString &name, obj->properties().keys()) { if (name == QLatin1String("configure")) continue; probeBindings += ProbeProperty(name, m_evaluator->value(probe, name)); } } QScriptValue scope = m_engine->newObject(); m_engine->currentContext()->pushScope(m_evaluator->scriptValue(parent)); m_engine->currentContext()->pushScope(m_evaluator->fileScope(configureScript->file())); m_engine->currentContext()->pushScope(scope); foreach (const ProbeProperty &b, probeBindings) scope.setProperty(b.first, b.second); const bool runProbe = m_evaluator->boolValue(probe, QLatin1String("condition")); ErrorInfo evalError; if (runProbe) { QScriptValue sv = m_engine->evaluate(configureScript->sourceCodeForEvaluation()); if (Q_UNLIKELY(m_engine->hasErrorOrException(sv))) evalError = ErrorInfo(m_engine->lastErrorString(sv), configureScript->location()); } else { m_logger.qbsDebug() << "Probe disabled; skipping"; } foreach (const ProbeProperty &b, probeBindings) { const QVariant newValue = scope.property(b.first).toVariant(); if (newValue != b.second.toVariant()) probe->setProperty(b.first, VariantValue::create(newValue)); } m_engine->currentContext()->popScope(); m_engine->currentContext()->popScope(); m_engine->currentContext()->popScope(); if (evalError.hasError()) throw evalError; } void ModuleLoader::checkCancelation() const { if (m_progressObserver && m_progressObserver->canceled()) { throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") .arg(TopLevelProject::deriveId(m_parameters.topLevelProfile(), m_parameters.finalBuildConfigurationTree()))); } } bool ModuleLoader::checkItemCondition(Item *item) { if (m_evaluator->boolValue(item, QLatin1String("condition"), true)) return true; m_disabledItems += item; return false; } void ModuleLoader::checkItemTypes(Item *item) { const ItemDeclaration decl = BuiltinDeclarations::instance().declarationsForType(item->type()); foreach (Item *child, item->children()) { if (child->type() > ItemType::LastActualItem) continue; checkItemTypes(child); if (!decl.isChildTypeAllowed(child->type())) throw ErrorInfo(Tr::tr("Items of type '%1' cannot contain items of type '%2'.") .arg(item->typeName(), child->typeName()), item->location()); } foreach (const Item::Module &m, item->modules()) checkItemTypes(m.item); } QStringList ModuleLoader::readExtraSearchPaths(Item *item, bool *wasSet) { QStringList result; const QString propertyName = QLatin1String("qbsSearchPaths"); const QStringList paths = m_evaluator->stringListValue(item, propertyName, wasSet); const JSSourceValueConstPtr prop = item->sourceProperty(propertyName); // Value can come from within a project file or as an overridden value from the user // (e.g command line). const QString basePath = FileInfo::path(prop ? prop->file()->filePath() : m_parameters.projectFilePath()); foreach (const QString &path, paths) result += FileInfo::resolvePath(basePath, path); return result; } void ModuleLoader::copyProperties(const Item *sourceProject, Item *targetProject) { if (!sourceProject) return; const QList &builtinProjectProperties = BuiltinDeclarations::instance() .declarationsForType(ItemType::Project).properties(); QSet builtinProjectPropertyNames; foreach (const PropertyDeclaration &p, builtinProjectProperties) builtinProjectPropertyNames << p.name(); for (Item::PropertyDeclarationMap::ConstIterator it = sourceProject->propertyDeclarations().constBegin(); it != sourceProject->propertyDeclarations().constEnd(); ++it) { // We must not inherit built-in properties such as "name", // but there are exceptions. if (it.key() == QLatin1String("qbsSearchPaths") || it.key() == QLatin1String("profile") || it.key() == QLatin1String("buildDirectory") || it.key() == QLatin1String("sourceDirectory")) { const JSSourceValueConstPtr &v = targetProject->property(it.key()).dynamicCast(); QBS_ASSERT(v, continue); if (v->sourceCode() == QLatin1String("undefined")) sourceProject->copyProperty(it.key(), targetProject); continue; } if (builtinProjectPropertyNames.contains(it.key())) continue; if (targetProject->properties().contains(it.key())) continue; // Ignore stuff the target project already has. targetProject->setPropertyDeclaration(it.key(), it.value()); sourceProject->copyProperty(it.key(), targetProject); } } Item *ModuleLoader::wrapInProjectIfNecessary(Item *item) { if (item->type() == ItemType::Project) return item; Item *prj = Item::create(item->pool(), ItemType::Project); Item::addChild(prj, item); prj->setTypeName(QLatin1String("Project")); prj->setFile(item->file()); prj->setLocation(item->location()); prj->setupForBuiltinType(m_logger); return prj; } QString ModuleLoader::findExistingModulePath(const QString &searchPath, const QualifiedId &moduleName) { QString dirPath = searchPath; foreach (const QString &moduleNamePart, moduleName) { dirPath = FileInfo::resolvePath(dirPath, moduleNamePart); if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) return QString(); } return dirPath; } void ModuleLoader::setScopeForDescendants(Item *item, Item *scope) { foreach (Item *child, item->children()) { child->setScope(scope); setScopeForDescendants(child, scope); } } void ModuleLoader::overrideItemProperties(Item *item, const QString &buildConfigKey, const QVariantMap &buildConfig) { const QVariant buildConfigValue = buildConfig.value(buildConfigKey); if (buildConfigValue.isNull()) return; const QVariantMap overridden = buildConfigValue.toMap(); for (QVariantMap::const_iterator it = overridden.constBegin(); it != overridden.constEnd(); ++it) { const PropertyDeclaration decl = item->propertyDeclarations().value(it.key()); if (!decl.isValid()) { ErrorInfo error(Tr::tr("Unknown property: %1.%2").arg(buildConfigKey, it.key())); handlePropertyError(error, m_parameters, m_logger); continue; } item->setProperty(it.key(), VariantValue::create(convertToPropertyType(it.value(), decl.type(), QStringList(buildConfigKey), it.key()))); } } static void collectAllModules(Item *item, QVector *modules) { foreach (const Item::Module &m, item->modules()) { auto it = std::find_if(modules->begin(), modules->end(), [m] (const Item::Module &m2) { return m.name == m2.name; }); if (it != modules->end()) { // If a module is required somewhere, it is required in the top-level item. if (m.required) it->required = true; continue; } modules->append(m); collectAllModules(m.item, modules); } } static QVector allModules(Item *item) { QVector lst; collectAllModules(item, &lst); return lst; } void ModuleLoader::addTransitiveDependencies(ProductContext *ctx) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] addTransitiveDependencies"; QVector transitiveDeps = allModules(ctx->item); std::sort(transitiveDeps.begin(), transitiveDeps.end()); foreach (const Item::Module &m, ctx->item->modules()) { if (m.isProduct) { ctx->info.usedProducts.append( ctx->project->topLevelProject->productModules.value( m.name.toString()).productDependencies); } auto it = std::lower_bound(transitiveDeps.begin(), transitiveDeps.end(), m); QBS_CHECK(it != transitiveDeps.end() && it->name == m.name); transitiveDeps.erase(it); } foreach (const Item::Module &module, transitiveDeps) { if (module.isProduct) { ctx->item->addModule(module); ctx->info.usedProducts.append( ctx->project->topLevelProject->productModules.value( module.name.toString()).productDependencies); } else { Item::Module dep; dep.item = loadModule(ctx, ctx->item, ctx->item->location(), QString(), module.name, module.required, &dep.isProduct); if (!dep.item) { throw ErrorInfo(Tr::tr("Module '%1' not found when setting up transitive " "dependencies for product '%2'.").arg(module.name.toString(), ctx->name), ctx->item->location()); } dep.name = module.name; dep.required = module.required; ctx->item->addModule(dep); } } } Item *ModuleLoader::createNonPresentModule(const QString &name, const QString &reason, Item *module) { if (m_logger.traceEnabled()) { m_logger.qbsTrace() << "Non-required module '" << name << "' not loaded (" << reason << ")." << "Creating dummy module for presence check."; } if (!module) { module = Item::create(m_pool, ItemType::ModuleInstance); module->setFile(FileContext::create()); } module->setProperty(QLatin1String("present"), VariantValue::create(false)); return module; } void ModuleLoader::copyGroupsFromModuleToProduct(const ProductContext &productContext, const Item *modulePrototype) { for (int i = 0; i < modulePrototype->children().count(); ++i) { Item * const child = modulePrototype->children().at(i); if (child->typeName() == QLatin1String("Group")) { Item * const clonedGroup = child->clone(); clonedGroup->setScope(productContext.scope); Item::addChild(productContext.item, clonedGroup); } } } void ModuleLoader::copyGroupsFromModulesToProduct(const ProductContext &productContext) { foreach (const Item::Module &module, productContext.item->modules()) { Item *prototype = module.item; bool modulePassedValidation; while ((modulePassedValidation = prototype->isPresentModule() && !prototype->delayedError().hasError()) && prototype->prototype()) { prototype = prototype->prototype(); } if (modulePassedValidation) copyGroupsFromModuleToProduct(productContext, prototype); } } QString ModuleLoaderResult::ProductInfo::Dependency::uniqueName() const { return ResolvedProduct::uniqueName(name, profile); } } // namespace Internal } // namespace qbs