aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/corelib
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2023-05-09 18:02:48 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2023-05-19 18:46:04 +0000
commit42cdcbf337ad90b66f3417ceebfdbd52b36a2e4b (patch)
tree81ed6ab1f800e786d048efd3ad046b944a23505f /src/lib/corelib
parentdfd1c59f14541558e2684a836725d322b73fff4e (diff)
Loader: Refactor dependency resolving
Namely: - Move code into its own class. - Split up the larger functions into sensible chunks. - Add more documentation. - Introduce loaderutils.{h,cpp} for shared functions and data structures. Change-Id: Ib41bdd1a7c087b2757f6363a3c58e87022b4f00b Reviewed-by: Ivan Komissarov <ABBAPOH@gmail.com>
Diffstat (limited to 'src/lib/corelib')
-rw-r--r--src/lib/corelib/CMakeLists.txt4
-rw-r--r--src/lib/corelib/corelib.qbs4
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.cpp1155
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.h102
-rw-r--r--src/lib/corelib/loader/itemreader.cpp22
-rw-r--r--src/lib/corelib/loader/itemreader.h3
-rw-r--r--src/lib/corelib/loader/loaderutils.cpp130
-rw-r--r--src/lib/corelib/loader/loaderutils.h136
-rw-r--r--src/lib/corelib/loader/moduleloader.cpp29
-rw-r--r--src/lib/corelib/loader/moduleloader.h10
-rw-r--r--src/lib/corelib/loader/moduleproviderloader.h4
-rw-r--r--src/lib/corelib/loader/productitemmultiplexer.h3
-rw-r--r--src/lib/corelib/loader/projectresolver.cpp112
-rw-r--r--src/lib/corelib/loader/projecttreebuilder.cpp1172
-rw-r--r--src/lib/corelib/loader/projecttreebuilder.h11
15 files changed, 1716 insertions, 1181 deletions
diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt
index 321f6bad6..895853dab 100644
--- a/src/lib/corelib/CMakeLists.txt
+++ b/src/lib/corelib/CMakeLists.txt
@@ -240,6 +240,8 @@ set(LOADER_SOURCES
astimportshandler.h
astpropertiesitemhandler.cpp
astpropertiesitemhandler.h
+ dependenciesresolver.cpp
+ dependenciesresolver.h
groupshandler.cpp
groupshandler.h
itemreader.cpp
@@ -248,6 +250,8 @@ set(LOADER_SOURCES
itemreaderastvisitor.h
itemreadervisitorstate.cpp
itemreadervisitorstate.h
+ loaderutils.cpp
+ loaderutils.h
localprofiles.cpp
localprofiles.h
moduleinstantiator.cpp
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 9f14ca8bd..5fc2680c0 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -329,6 +329,8 @@ QbsLibrary {
"astimportshandler.h",
"astpropertiesitemhandler.cpp",
"astpropertiesitemhandler.h",
+ "dependenciesresolver.cpp",
+ "dependenciesresolver.h",
"groupshandler.cpp",
"groupshandler.h",
"itemreader.cpp",
@@ -337,6 +339,8 @@ QbsLibrary {
"itemreaderastvisitor.h",
"itemreadervisitorstate.cpp",
"itemreadervisitorstate.h",
+ "loaderutils.cpp",
+ "loaderutils.h",
"localprofiles.cpp",
"localprofiles.h",
"moduleinstantiator.cpp",
diff --git a/src/lib/corelib/loader/dependenciesresolver.cpp b/src/lib/corelib/loader/dependenciesresolver.cpp
new file mode 100644
index 000000000..e2a95aab7
--- /dev/null
+++ b/src/lib/corelib/loader/dependenciesresolver.cpp
@@ -0,0 +1,1155 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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 "dependenciesresolver.h"
+
+#include "itemreader.h"
+#include "loaderutils.h"
+#include "moduleinstantiator.h"
+#include "moduleloader.h"
+#include "moduleproviderloader.h"
+#include "productitemmultiplexer.h"
+
+#include <language/scriptengine.h>
+#include <language/evaluator.h>
+#include <language/item.h>
+#include <language/itempool.h>
+#include <language/value.h>
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/fileinfo.h>
+#include <tools/preferences.h>
+#include <tools/profiling.h>
+#include <tools/settings.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <optional>
+#include <queue>
+#include <unordered_map>
+
+namespace qbs::Internal {
+namespace {
+enum class HandleDependency { Use, Ignore, Defer };
+
+class LoadModuleResult
+{
+public:
+ Item *moduleItem = nullptr;
+ ProductContext *product = nullptr;
+ HandleDependency handleDependency = HandleDependency::Use;
+};
+
+// Corresponds completely to a Depends item.
+// May result in more than one module, due to "multiplexing" properties such as subModules etc.
+// May also result in no module at all, e.g. if productTypes does not match anything.
+class EvaluatedDependsItem
+{
+public:
+ Item *item = nullptr;
+ QualifiedId name;
+ QStringList subModules;
+ FileTags productTypes;
+ QStringList multiplexIds;
+ std::optional<QStringList> profiles;
+ VersionRange versionRange;
+ QVariantMap parameters;
+ bool limitToSubProject = false;
+ FallbackMode fallbackMode = FallbackMode::Enabled;
+ bool requiredLocally = true;
+ bool requiredGlobally = true;
+};
+
+// As opposed to EvaluatedDependsItem, one of these corresponds exactly to one module
+// to be loaded. Such an attempt might still fail, though, which may or may not result
+// in an error, depending on the value of Depends.required and other circumstances.
+class FullyResolvedDependsItem
+{
+public:
+ FullyResolvedDependsItem(ProductContext *product, const EvaluatedDependsItem &dependency);
+ FullyResolvedDependsItem(const EvaluatedDependsItem &dependency, QualifiedId name,
+ QString profile, QString multiplexId);
+ FullyResolvedDependsItem() = default;
+ static FullyResolvedDependsItem makeBaseDependency();
+
+ QString id() const;
+ CodeLocation location() const;
+ QString displayName() const;
+
+ // If product is non-null, we already know which product the dependency targets.
+ // This happens either if Depends.productTypes was set, or if we tried to load the
+ // dependency before and already identified the product, but could not complete the
+ // procedure because said product had itself not been handled yet.
+ ProductContext *product = nullptr;
+
+ Item *item = nullptr;
+ QualifiedId name;
+ QString profile;
+ QString multiplexId;
+ VersionRange versionRange;
+ QVariantMap parameters;
+ bool limitToSubProject = false;
+ FallbackMode fallbackMode = FallbackMode::Enabled;
+ bool requiredLocally = true;
+ bool requiredGlobally = true;
+ bool checkProduct = true;
+};
+
+class DependenciesResolvingState
+{
+public:
+ Item *loadingItem = nullptr;
+ FullyResolvedDependsItem loadingItemOrigin;
+ std::queue<Item *> pendingDependsItems;
+ std::optional<EvaluatedDependsItem> currentDependsItem;
+ std::queue<FullyResolvedDependsItem> pendingResolvedDependencies;
+ bool requiredByLoadingItem = true;
+};
+} // namespace
+
+static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2);
+static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties);
+static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v);
+
+class DependenciesResolver::Private
+{
+public:
+ Private(const SetupProjectParameters &parameters, Evaluator &evaluator, ItemPool &itemPool,
+ ItemReader &itemReader, ProbesResolver &probesResolver,
+ ModuleInstantiator &moduleInstantiator, Logger &logger)
+ : parameters(parameters), itemPool(itemPool), evaluator(evaluator), itemReader(itemReader),
+ moduleInstantiator(moduleInstantiator), logger(logger),
+ moduleLoader(parameters, probesResolver, itemReader, evaluator, logger)
+ {}
+
+ void initializeState();
+ void evaluateNextDependsItem();
+ HandleDependency handleResolvedDependencies();
+ LoadModuleResult loadModule(Item *loadingItem, const FullyResolvedDependsItem &dependency);
+ std::pair<Item::Module *, Item *> findExistingModule(const FullyResolvedDependsItem &dependency,
+ Item *item);
+ void updateModule(Item::Module &module, const FullyResolvedDependsItem &dependency);
+ ProductContext *findMatchingProduct(const FullyResolvedDependsItem &dependency);
+ Item *findMatchingModule(const FullyResolvedDependsItem &dependency);
+ std::pair<bool, HandleDependency> checkProductDependency(
+ const FullyResolvedDependsItem &depSpec, const ProductContext &dep);
+ void checkModule(const FullyResolvedDependsItem &dependency, Item *moduleItem,
+ ProductContext *productDep);
+ void checkForModuleNamePrefixCollision(const FullyResolvedDependsItem &dependency);
+ Item::Module createModule(const FullyResolvedDependsItem &dependency, Item *item,
+ ProductContext *productDep);
+ void adjustDependsItemForMultiplexing(Item *dependsItem);
+ std::optional<EvaluatedDependsItem> evaluateDependsItem(Item *item);
+ std::queue<FullyResolvedDependsItem> multiplexDependency(
+ const EvaluatedDependsItem &dependency);
+ void setSearchPathsForProduct();
+ QVariantMap extractParameters(Item *dependsItem) const;
+
+ const SetupProjectParameters &parameters;
+ ItemPool &itemPool;
+ Evaluator &evaluator;
+ ItemReader &itemReader;
+ ModuleInstantiator &moduleInstantiator;
+ Logger &logger;
+ ModuleLoader moduleLoader;
+ std::unordered_map<ProductContext *, std::list<DependenciesResolvingState>> statePerProduct;
+ qint64 elapsedTime = 0;
+
+ ProductContext *product = nullptr;
+ std::list<DependenciesResolvingState> *stateStack = nullptr;
+ Deferral deferral = Deferral::Allowed;
+};
+
+DependenciesResolver::DependenciesResolver(
+ const SetupProjectParameters &parameters, ItemPool &itemPool, Evaluator &evaluator,
+ ItemReader &itemReader, ProbesResolver &probesResolver, ModuleInstantiator &moduleInstantiator,
+ Logger &logger)
+ : d(makePimpl<Private>(parameters, evaluator, itemPool, itemReader, probesResolver,
+ moduleInstantiator, logger)) {}
+DependenciesResolver::~DependenciesResolver() = default;
+
+bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral deferral)
+{
+ QBS_CHECK(!product.dependenciesResolved);
+
+ AccumulatingTimer timer(d->parameters.logElapsedTime() ? &d->elapsedTime : nullptr);
+
+ d->product = &product;
+ d->deferral = deferral;
+ d->stateStack = &d->statePerProduct[&product];
+
+ d->initializeState();
+ SearchPathsManager searchPathsMgr(d->itemReader, product.searchPaths);
+
+ while (!d->stateStack->empty()) {
+ auto &state = d->stateStack->front();
+
+ // If we have pending FullyResolvedDependsItems, then these are handled first.
+ if (d->handleResolvedDependencies() == HandleDependency::Defer)
+ return false;
+
+ // The above procedure might have pushed another state to the stack due to recursive
+ // dependencies (i.e. Depends items in the newly loaded module), in which case we
+ // continue with that one.
+ if (&state != &d->stateStack->front())
+ continue;
+
+ // If we have a pending EvaluatedDependsItem, we multiplex it and then handle
+ // the resulting FullyResolvedDependsItems, if there were any.
+ if (state.currentDependsItem) {
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+
+ // We postpone handling Depends.productTypes for as long as possible, because
+ // the full type of a product becomes available only after its modules have been loaded.
+ if (!state.currentDependsItem->productTypes.empty() && deferral == Deferral::Allowed)
+ return false;
+
+ state.pendingResolvedDependencies = d->multiplexDependency(*state.currentDependsItem);
+ state.currentDependsItem.reset();
+ continue;
+ }
+
+ // Here we have no resolved/evaluated Depends items of any kind, so we evaluate the next
+ // pending Depends item.
+ d->evaluateNextDependsItem();
+ if (state.currentDependsItem)
+ continue;
+
+ // No resolved or unresolved Depends items are left, so we're done with the current state.
+ QBS_CHECK(!state.currentDependsItem);
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+ QBS_CHECK(state.pendingDependsItems.empty());
+
+ // This ensures our invariant: A sorted module list in the product
+ // (dependers after dependencies).
+ if (d->stateStack->size() > 1) {
+ QBS_CHECK(state.loadingItem->type() == ItemType::ModuleInstance);
+ Item::Modules &modules = product.item->modules();
+ const auto loadingItemModule = std::find_if(modules.begin(), modules.end(),
+ [&](const Item::Module &m) {
+ return m.item == state.loadingItem;
+ });
+ QBS_CHECK(loadingItemModule != modules.end());
+ const Item::Module tempModule = *loadingItemModule;
+ modules.erase(loadingItemModule);
+ modules.push_back(tempModule);
+ }
+ d->stateStack->pop_front();
+ }
+ return true;
+}
+
+void DependenciesResolver::checkDependencyParameterDeclarations(
+ const Item *productItem, const QString &productName) const
+{
+ d->moduleLoader.checkDependencyParameterDeclarations(productItem, productName);
+}
+
+void DependenciesResolver::setStoredModuleProviderInfo(
+ const StoredModuleProviderInfo &moduleProviderInfo)
+{
+ d->moduleLoader.setStoredModuleProviderInfo(moduleProviderInfo);
+}
+
+StoredModuleProviderInfo DependenciesResolver::storedModuleProviderInfo() const
+{
+ return d->moduleLoader.storedModuleProviderInfo();
+}
+
+const Set<QString> &DependenciesResolver::tempQbsFiles() const
+{
+ return d->moduleLoader.tempQbsFiles();
+}
+
+void DependenciesResolver::printProfilingInfo(int indent)
+{
+ if (!d->parameters.logElapsedTime())
+ return;
+ const QByteArray prefix(indent, ' ');
+ d->logger.qbsLog(LoggerInfo, true)
+ << prefix
+ << Tr::tr("Setting up product dependencies took %1.")
+ .arg(elapsedTimeString(d->elapsedTime));
+ d->moduleLoader.printProfilingInfo(indent + 2);
+}
+
+Item *DependenciesResolver::loadBaseModule(ProductContext &product, Item *item)
+{
+ d->product = &product;
+ d->stateStack = &d->statePerProduct[&product];
+ d->deferral = Deferral::NotAllowed;
+ const auto baseDependency = FullyResolvedDependsItem::makeBaseDependency();
+ Item * const moduleItem = d->loadModule(item, baseDependency).moduleItem;
+ if (Q_UNLIKELY(!moduleItem))
+ throw ErrorInfo(Tr::tr("Cannot load base qbs module."));
+ return moduleItem;
+}
+
+void DependenciesResolver::Private::initializeState()
+{
+ if (!stateStack->empty())
+ return;
+
+ // Initialize the state with the direct Depends items of the product item.
+ // This is executed once per product, while the function might be entered
+ // multiple times due to deferrals.
+ setSearchPathsForProduct();
+ DependenciesResolvingState newState{product->item,};
+ for (Item * const child : product->item->children()) {
+ if (child->type() == ItemType::Depends)
+ newState.pendingDependsItems.push(child);
+ }
+ stateStack->push_front(std::move(newState));
+ stateStack->front().pendingResolvedDependencies.push(
+ FullyResolvedDependsItem::makeBaseDependency());
+}
+
+void DependenciesResolver::Private::evaluateNextDependsItem()
+{
+ auto &state = stateStack->front();
+ while (!state.pendingDependsItems.empty()) {
+ QBS_CHECK(!state.currentDependsItem);
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+ Item * const dependsItem = state.pendingDependsItems.front();
+ state.pendingDependsItems.pop();
+ adjustDependsItemForMultiplexing(dependsItem);
+ if (auto evaluated = evaluateDependsItem(dependsItem)) {
+ evaluated->requiredGlobally = evaluated->requiredLocally
+ && state.loadingItemOrigin.requiredGlobally;
+ state.currentDependsItem = evaluated;
+ return;
+ }
+ }
+}
+
+HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
+{
+ DependenciesResolvingState &state = stateStack->front();
+ while (!state.pendingResolvedDependencies.empty()) {
+ QBS_CHECK(!state.currentDependsItem);
+ const FullyResolvedDependsItem dependency = state.pendingResolvedDependencies.front();
+ try {
+ const LoadModuleResult res = loadModule(state.loadingItem, dependency);
+ switch (res.handleDependency) {
+ case HandleDependency::Defer:
+ QBS_CHECK(deferral == Deferral::Allowed);
+
+ // Optimization: We already looked up the product, so let's not do that again
+ // next time.
+ if (res.product)
+ state.pendingResolvedDependencies.front().product = res.product;
+
+ return HandleDependency::Defer;
+ case HandleDependency::Ignore:
+ // This happens if the dependency was not required or the module was already
+ // loaded via another path.
+ state.pendingResolvedDependencies.pop();
+ continue;
+ case HandleDependency::Use:
+ if (dependency.name.toString() == StringConstants::qbsModule()) {
+ // Shortcut: No need to look for recursive dependencies in the base module.
+ state.pendingResolvedDependencies.pop();
+ continue;
+ }
+ break;
+ }
+
+ QBS_CHECK(res.moduleItem);
+
+ // Now continue with the dependencies of the just-loaded module.
+ std::queue<Item *> moduleDependsItems;
+ for (Item * const child : res.moduleItem->children()) {
+ if (child->type() == ItemType::Depends)
+ moduleDependsItems.push(child);
+ }
+ state.pendingResolvedDependencies.pop();
+ stateStack->push_front(
+ {res.moduleItem, dependency, moduleDependsItems, {}, {},
+ dependency.requiredGlobally || state.requiredByLoadingItem});
+ stateStack->front().pendingResolvedDependencies.push(
+ FullyResolvedDependsItem::makeBaseDependency());
+ break;
+ } catch (const ErrorInfo &e) {
+ if (dependency.name.toString() == StringConstants::qbsModule())
+ throw e;
+
+ // This can happen when a property is set unconditionally on a non-required,
+ // non-present dependency. We allow this for user convenience.
+ if (!dependency.requiredLocally) {
+ state.pendingResolvedDependencies.pop();
+ continue;
+ }
+
+ // See QBS-1338 for why we do not abort handling the product.
+ state.pendingResolvedDependencies.pop();
+ Item::Modules &modules = product->item->modules();
+
+ // Unwind.
+ while (stateStack->size() > 1) {
+ const auto loadingItemModule = std::find_if(
+ modules.begin(), modules.end(), [&](const Item::Module &m) {
+ return m.item == stateStack->front().loadingItem;
+ });
+ for (auto it = loadingItemModule; it != modules.end(); ++it) {
+ createNonPresentModule(itemPool, it->name.toString(),
+ QLatin1String("error in Depends chain"), it->item);
+ }
+ modules.erase(loadingItemModule, modules.end());
+ stateStack->pop_front();
+ }
+
+ product->handleError(e);
+ return HandleDependency::Ignore;
+ }
+ }
+ return HandleDependency::Ignore;
+}
+
+// Produces an item of type ModuleInstance corresponding to the specified dependency.
+// The instance's prototype item is either of type Export (if the dependency is a product)
+// or of type Module (for an actual module).
+// The loadingItem parameter is either the depending product or another module. The newly
+// created module is added to the module list of the product item and additionally to the
+// loading item's one, if it is not the product. Its name is also injected into the respective
+// scopes.
+LoadModuleResult DependenciesResolver::Private::loadModule(
+ Item *loadingItem, const FullyResolvedDependsItem &dependency)
+{
+ qCDebug(lcModuleLoader) << "loadModule name:" << dependency.name.toString()
+ << "id:" << dependency.id();
+
+ QBS_CHECK(loadingItem);
+
+ ProductContext *productDep = nullptr;
+ Item *moduleItem = nullptr;
+
+ // The module might already have been loaded for this product (directly or indirectly).
+ const auto &[existingModule, moduleWithSameName]
+ = findExistingModule(dependency, product->item);
+ if (existingModule) {
+ // Merge version range and required property. These will be checked again
+ // after probes resolving.
+ if (existingModule->name.toString() != StringConstants::qbsModule())
+ updateModule(*existingModule, dependency);
+
+ QBS_CHECK(existingModule->item);
+ moduleItem = existingModule->item;
+ if (!contains(existingModule->loadingItems, loadingItem))
+ existingModule->loadingItems.push_back(loadingItem);
+ } else if (dependency.product) {
+ productDep = dependency.product; // We have already done the look-up.
+ } else if (!(productDep = findMatchingProduct(dependency))) {
+ moduleItem = findMatchingModule(dependency);
+ }
+
+ if (productDep) {
+ const auto checkResult = checkProductDependency(dependency, *productDep);
+
+ // We found a product dependency, but that product has not been handled yet,
+ // so stop dependency resolving for the current product and resume it later, when the
+ // dependency is ready.
+ if (checkResult.second == HandleDependency::Defer)
+ return {nullptr, productDep, HandleDependency::Defer};
+
+ if (checkResult.first) {
+ QBS_CHECK(productDep->mergedExportItem);
+ moduleItem = productDep->mergedExportItem->clone();
+ moduleItem->setParent(nullptr);
+
+ // Needed for isolated Export item evaluation.
+ moduleItem->setPrototype(productDep->mergedExportItem);
+ } else {
+ // The product dependency is faulty, but Depends.reqired was false.
+ productDep = nullptr;
+ }
+ }
+
+ if (moduleItem)
+ checkModule(dependency, moduleItem, productDep);
+
+ // Can only happen with multiplexing.
+ if (moduleWithSameName && moduleWithSameName != moduleItem)
+ QBS_CHECK(productDep);
+
+ // The loading name is only used to ensure consistent sorting in case of equal
+ // value priorities; see ModulePropertyMerger.
+ QString loadingName;
+ if (loadingItem == product->item) {
+ loadingName = product->name;
+ } else if (!stateStack->empty()) {
+ const auto &loadingItemOrigin = stateStack->front().loadingItemOrigin;
+ loadingName = loadingItemOrigin.name.toString() + loadingItemOrigin.multiplexId
+ + loadingItemOrigin.profile;
+ }
+ moduleInstantiator.instantiate({
+ product->item, product->name, loadingItem, loadingName, moduleItem, moduleWithSameName,
+ productDep ? productDep->item : nullptr, product->scope, product->project->scope,
+ dependency.name, dependency.id(), bool(existingModule)});
+
+ // At this point, a null module item is only possible for a non-required dependency.
+ // Note that we still needed to to the instantiation above, as that injects the module
+ // name into the surrounding item for the ".present" check.
+ if (!moduleItem) {
+ QBS_CHECK(!dependency.requiredGlobally);
+ return {nullptr, nullptr, HandleDependency::Ignore};
+ }
+
+ const auto addLocalModule = [&] {
+ if (loadingItem->type() == ItemType::ModuleInstance
+ && !findExistingModule(dependency, loadingItem).first) {
+ loadingItem->addModule(createModule(dependency, moduleItem, productDep));
+ }
+ };
+
+ // The module has already been loaded, so we don't need to add it to the product's list of
+ // modules, nor do we need to handle its dependencies. The only thing we might need to
+ // do is to add it to the "local" list of modules of the loading item, if it isn't in there yet.
+ if (existingModule) {
+ addLocalModule();
+ return {nullptr, nullptr, HandleDependency::Ignore};
+ }
+
+ qCDebug(lcModuleLoader) << "module loaded:" << dependency.name.toString();
+ if (product->item) {
+ Item::Module module = createModule(dependency, moduleItem, productDep);
+
+ if (module.name.toString() != StringConstants::qbsModule()) {
+ // TODO: Why do we have default parameters only for Export items and
+ // property declarations only for modules? Does that make any sense?
+ if (productDep)
+ module.parameters = productDep->defaultParameters;
+ mergeParameters(module.parameters, dependency.parameters);
+ }
+ module.required = dependency.requiredGlobally;
+ module.loadingItems.push_back(loadingItem);
+ module.maxDependsChainLength = stateStack->size();
+ product->item->addModule(module);
+ addLocalModule();
+ }
+ return {moduleItem, nullptr, HandleDependency::Use};
+}
+
+std::pair<Item::Module *, Item *> DependenciesResolver::Private::findExistingModule(
+ const FullyResolvedDependsItem &dependency, Item *item)
+{
+ if (!item) // Happens if and only if called via loadBaseModule().
+ return {};
+ Item *moduleWithSameName = nullptr;
+ for (Item::Module &m : item->modules()) {
+ if (m.name != dependency.name)
+ continue;
+ if (!m.productInfo) {
+ QBS_CHECK(!dependency.product);
+ return {&m, m.item};
+ }
+ if ((dependency.profile.isEmpty() || (m.productInfo->profile == dependency.profile))
+ && m.productInfo->multiplexId == dependency.multiplexId) {
+ return {&m, m.item};
+ }
+
+ // This can happen if there are dependencies to several variants of a multiplexed
+ // product.
+ moduleWithSameName = m.item;
+ }
+ return {nullptr, moduleWithSameName};
+}
+
+void DependenciesResolver::Private::updateModule(
+ Item::Module &module, const FullyResolvedDependsItem &dependency)
+{
+ moduleLoader.forwardParameterDeclarations(dependency.item, product->item->modules());
+
+ // TODO: Use priorities like for property values. See QBS-1300.
+ mergeParameters(module.parameters, dependency.parameters);
+
+ module.versionRange.narrowDown(dependency.versionRange);
+ module.required |= dependency.requiredGlobally;
+ if (int(stateStack->size()) > module.maxDependsChainLength)
+ module.maxDependsChainLength = stateStack->size();
+}
+
+ProductContext *DependenciesResolver::Private::findMatchingProduct(
+ const FullyResolvedDependsItem &dependency)
+{
+ const auto candidates = product->project->topLevelProject
+ ->productsByName.equal_range(dependency.name.toString());
+ for (auto it = candidates.first; it != candidates.second; ++it) {
+ ProductContext * const candidate = it->second;
+ if (candidate->multiplexConfigurationId != dependency.multiplexId)
+ continue;
+ if (!dependency.profile.isEmpty() && dependency.profile != candidate->profileName)
+ continue;
+ if (dependency.limitToSubProject && !haveSameSubProject(*product, *candidate))
+ continue;
+ return candidate;
+ }
+ return nullptr;
+}
+
+Item *DependenciesResolver::Private::findMatchingModule(
+ const FullyResolvedDependsItem &dependency)
+{
+ // If we can tell that this is supposed to be a product dependency, we can skip
+ // the module look-up.
+ if (!dependency.profile.isEmpty() || !dependency.multiplexId.isEmpty()) {
+ if (dependency.requiredGlobally) {
+ if (!dependency.profile.isEmpty()) {
+ throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
+ "for the requested profile '%3'.")
+ .arg(product->displayName(), dependency.displayName(),
+ dependency.profile),
+ product->item->location());
+ }
+ throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
+ .arg(product->displayName(), dependency.displayName()),
+ product->item->location());
+ }
+ return nullptr;
+ }
+
+ const ModuleLoader::ProductContext loaderContext{
+ product->item, product->project->item, product->name, product->uniqueName(),
+ product->profileName, product->multiplexConfigurationId, product->moduleProperties,
+ product->profileModuleProperties};
+ const ModuleLoader::Result loaderResult = moduleLoader.searchAndLoadModuleFile(
+ loaderContext, dependency.location(), dependency.name, dependency.fallbackMode,
+ dependency.requiredGlobally);
+
+ Item *moduleItem = loaderResult.moduleItem;
+ product->info.probes << loaderResult.providerProbes;
+ if (moduleItem) {
+ Item * const proto = moduleItem;
+ moduleItem = moduleItem->clone();
+ moduleItem->setPrototype(proto); // For parameter declarations.
+ } else if (dependency.requiredGlobally) {
+ throw ErrorInfo(Tr::tr("Dependency '%1' not found for product '%2'.")
+ .arg(dependency.name.toString(), product->displayName()),
+ dependency.location());
+ }
+ return moduleItem;
+}
+
+std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDependency(
+ const FullyResolvedDependsItem &depSpec, const ProductContext &dep)
+{
+ // Optimization: If we already checked the product earlier and then deferred, we don't
+ // need to check it again.
+ if (!depSpec.checkProduct)
+ return {true, HandleDependency::Use};
+
+ if (&dep == product) {
+ throw ErrorInfo(Tr::tr("Dependency '%1' refers to itself.").arg(depSpec.name.toString()),
+ depSpec.location());
+ }
+
+ if (any_of(product->project->topLevelProject->productsToHandle, [&dep](const auto &e) {
+ return e.first == &dep;
+ })) {
+ if (deferral == Deferral::Allowed)
+ return {false, HandleDependency::Defer};
+ ErrorInfo e;
+ e.append(Tr::tr("Cyclic dependencies detected:"));
+ e.append(Tr::tr("First product is '%1'.")
+ .arg(product->displayName()), product->item->location());
+ e.append(Tr::tr("Second product is '%1'.")
+ .arg(dep.displayName()), dep.item->location());
+ e.append(Tr::tr("Requested here."), depSpec.location());
+ throw e;
+ }
+
+ // This covers both the case of user-disabled products and products with errors.
+ // The latter are force-disabled in ProductContext::handleError().
+ if (product->project->topLevelProject->disabledItems.contains(dep.item)) {
+ if (depSpec.requiredGlobally) {
+ ErrorInfo e;
+ e.append(Tr::tr("Product '%1' depends on '%2',")
+ .arg(product->displayName(), dep.displayName()),
+ product->item->location());
+ e.append(Tr::tr("but product '%1' is disabled.").arg(dep.displayName()),
+ dep.item->location());
+ throw e;
+ }
+ return {false, HandleDependency::Ignore};
+ }
+ return {true, HandleDependency::Use};
+}
+
+void DependenciesResolver::Private::checkModule(
+ const FullyResolvedDependsItem &dependency, Item *moduleItem, ProductContext *productDep)
+{
+ for (auto it = stateStack->begin(); it != stateStack->end(); ++it) {
+ Item *itemToCheck = moduleItem;
+ if (it->loadingItem != itemToCheck) {
+ if (!productDep)
+ continue;
+ itemToCheck = productDep->item;
+ }
+ if (it->loadingItem != itemToCheck)
+ continue;
+ ErrorInfo e;
+ e.append(Tr::tr("Cyclic dependencies detected:"));
+ while (true) {
+ e.append(it->loadingItemOrigin.name.toString(),
+ it->loadingItemOrigin.location());
+ if (it->loadingItem->type() == ItemType::ModuleInstance) {
+ createNonPresentModule(itemPool, it->loadingItemOrigin.name.toString(),
+ QLatin1String("cyclic dependency"), it->loadingItem);
+ }
+ if (it == stateStack->begin())
+ break;
+ --it;
+ }
+ e.append(dependency.name.toString(), dependency.location());
+ throw e;
+ }
+ checkForModuleNamePrefixCollision(dependency);
+}
+
+void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *dependsItem)
+{
+ const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty());
+ const bool productIsMultiplexed = !product->multiplexConfigurationId.isEmpty();
+ if (name == product->name) {
+ QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator.
+ return;
+ }
+ const auto productRange = product->project->topLevelProject->productsByName.equal_range(name);
+ if (productRange.first == productRange.second)
+ return; // Dependency is a module. Nothing to adjust.
+
+ bool profilesPropertyIsSet;
+ const QStringList profiles = evaluator.stringListValue(
+ dependsItem, StringConstants::profilesProperty(), &profilesPropertyIsSet);
+
+ std::vector<const ProductContext *> multiplexedDependencies;
+ bool hasNonMultiplexedDependency = false;
+ for (auto it = productRange.first; it != productRange.second; ++it) {
+ if (!it->second->multiplexConfigurationId.isEmpty())
+ multiplexedDependencies.push_back(it->second);
+ else
+ hasNonMultiplexedDependency = true;
+ }
+ bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
+
+ static const auto multiplexConfigurationIntersects = [](const QVariantMap &lhs,
+ const QVariantMap &rhs) {
+ QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty());
+ for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) {
+ const auto rhsProperty = rhs.find(lhsProperty.key());
+ const bool isCommonProperty = rhsProperty != rhs.constEnd();
+ if (isCommonProperty && lhsProperty.value() != rhsProperty.value())
+ return false;
+ }
+ return true;
+ };
+
+ // These are the allowed cases:
+ // (1) Normal dependency with no multiplexing whatsoever.
+ // (2) Both product and dependency are multiplexed.
+ // (2a) The profiles property is not set, we want to depend on the best
+ // matching variant.
+ // (2b) The profiles property is set, we want to depend on all variants
+ // with a matching profile.
+ // (3) The product is not multiplexed, but the dependency is.
+ // (3a) The profiles property is not set, the dependency has an aggregator.
+ // We want to depend on the aggregator.
+ // (3b) The profiles property is not set, the dependency does not have an
+ // aggregator. We want to depend on all the multiplexed variants.
+ // (3c) The profiles property is set, we want to depend on all variants
+ // with a matching profile regardless of whether an aggregator exists or not.
+ // (4) The product is multiplexed, but the dependency is not. We don't have to adapt
+ // any Depends items.
+ // (5) The product is a "shadow product". In that case, we know which product
+ // it should have a dependency on, and we make sure we depend on that.
+
+ // (1) and (4)
+ if (!hasMultiplexedDependencies)
+ return;
+
+ // (3a)
+ if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet)
+ return;
+
+ QStringList multiplexIds;
+ const ShadowProductInfo shadowProductInfo = getShadowProductInfo(*product);
+ const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name;
+ const auto productMultiplexConfig
+ = ProductItemMultiplexer::multiplexIdToVariantMap(product->multiplexConfigurationId);
+
+ for (const ProductContext *dependency : multiplexedDependencies) {
+ const bool depMatchesShadowProduct = isShadowProduct
+ && dependency->item == product->item->parent();
+ const QString depMultiplexId = dependency->multiplexConfigurationId;
+ if (depMatchesShadowProduct) { // (5)
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ VariantValue::create(depMultiplexId));
+ return;
+ }
+ if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a
+ if (dependency->multiplexConfigurationId == product->multiplexConfigurationId) {
+ const ValuePtr &multiplexId = product->item->property(
+ StringConstants::multiplexConfigurationIdProperty());
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ multiplexId);
+ return;
+
+ }
+ // Otherwise collect partial matches and decide later
+ const auto dependencyMultiplexConfig = ProductItemMultiplexer::multiplexIdToVariantMap(
+ dependency->multiplexConfigurationId);
+
+ if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig))
+ multiplexIds << dependency->multiplexConfigurationId;
+ } else {
+ // (2b), (3b) or (3c)
+ const bool profileMatch = !profilesPropertyIsSet || profiles.empty()
+ || profiles.contains(dependency->profileName);
+ if (profileMatch)
+ multiplexIds << depMultiplexId;
+ }
+ }
+ if (multiplexIds.empty()) {
+ const QString productName = ProductItemMultiplexer::fullProductDisplayName(
+ product->name, product->multiplexConfigurationId);
+ throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. "
+ "There are no eligible multiplex candidates.").arg(productName,
+ name),
+ dependsItem->location());
+ }
+
+ // In case of (2a), at most 1 match is allowed
+ if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) {
+ QStringList candidateNames;
+ for (const auto &id : qAsConst(multiplexIds))
+ candidateNames << ProductItemMultiplexer::fullProductDisplayName(name, id);
+ throw ErrorInfo(
+ Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. "
+ "Eligible multiplex candidates: %3.").arg(
+ product->displayName(), name, candidateNames.join(QLatin1String(", "))),
+ dependsItem->location());
+ }
+
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ VariantValue::create(multiplexIds));
+
+}
+
+std::optional<EvaluatedDependsItem> DependenciesResolver::Private::evaluateDependsItem(Item *item)
+{
+ if (!product->project->topLevelProject->checkItemCondition(item, evaluator)) {
+ qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
+ return {};
+ }
+
+ const QString name = evaluator.stringValue(item, StringConstants::nameProperty());
+ if (name == StringConstants::qbsModule()) // Redundant
+ return {};
+
+ bool submodulesPropertySet;
+ const QStringList submodules = evaluator.stringListValue(
+ item, StringConstants::submodulesProperty(), &submodulesPropertySet);
+ if (submodules.empty() && submodulesPropertySet) {
+ qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list.";
+ return {};
+ }
+ if (Q_UNLIKELY(submodules.size() > 1 && !item->id().isEmpty())) {
+ QString msg = Tr::tr("A Depends item with more than one module cannot have an id.");
+ throw ErrorInfo(msg, item->location());
+ }
+ bool productTypesWasSet;
+ const QStringList productTypes = evaluator.stringListValue(
+ item, StringConstants::productTypesProperty(), &productTypesWasSet);
+ if (!name.isEmpty() && !productTypes.isEmpty()) {
+ throw ErrorInfo(Tr::tr("The name and productTypes are mutually exclusive "
+ "in a Depends item."), item->location());
+ }
+ if (productTypes.isEmpty() && productTypesWasSet) {
+ qCDebug(lcModuleLoader) << "Ignoring Depends item with empty productTypes list.";
+ return {};
+ }
+ if (name.isEmpty() && !productTypesWasSet) {
+ throw ErrorInfo(Tr::tr("Either name or productTypes must be set in a Depends item"),
+ item->location());
+ }
+
+ const FallbackMode fallbackMode
+ = parameters.fallbackProviderEnabled()
+ && evaluator.boolValue(item, StringConstants::enableFallbackProperty())
+ ? FallbackMode::Enabled : FallbackMode::Disabled;
+
+ bool profilesPropertyWasSet = false;
+ std::optional<QStringList> profiles;
+ bool required = true;
+ if (productTypes.isEmpty()) {
+ const QStringList profileList = evaluator.stringListValue(
+ item, StringConstants::profilesProperty(), &profilesPropertyWasSet);
+ if (profilesPropertyWasSet)
+ profiles.emplace(profileList);
+ required = evaluator.boolValue(item, StringConstants::requiredProperty());
+ }
+ const Version minVersion = Version::fromString(
+ evaluator.stringValue(item, StringConstants::versionAtLeastProperty()));
+ const Version maxVersion = Version::fromString(
+ evaluator.stringValue(item, StringConstants::versionBelowProperty()));
+ const bool limitToSubProject = evaluator.boolValue(
+ item, StringConstants::limitToSubProjectProperty());
+ const QStringList multiplexIds = evaluator.stringListValue(
+ item, StringConstants::multiplexConfigurationIdsProperty());
+ adjustParametersScopes(item, item);
+ moduleLoader.forwardParameterDeclarations(item, product->item->modules());
+ const QVariantMap parameters = extractParameters(item);
+
+ return EvaluatedDependsItem{
+ item, QualifiedId::fromString(name), submodules, FileTags::fromStringList(productTypes),
+ multiplexIds, profiles, {minVersion, maxVersion}, parameters, limitToSubProject,
+ fallbackMode, required};
+}
+
+// Potentially multiplexes a dependency along Depends.productTypes, Depends.subModules and
+// Depends.profiles, as well as internally set up multiplexing axes.
+// Each entry in the resulting queue corresponds to exactly one product or module to pull in.
+std::queue<FullyResolvedDependsItem>
+DependenciesResolver::Private::multiplexDependency(const EvaluatedDependsItem &dependency)
+{
+ std::queue<FullyResolvedDependsItem> dependencies;
+ if (!dependency.productTypes.empty()) {
+ std::vector<ProductContext *> matchingProducts;
+ for (const FileTag &typeTag : dependency.productTypes) {
+ const auto range = product->project->topLevelProject->productsByType.equal_range(typeTag);
+ for (auto it = range.first; it != range.second; ++it) {
+ if (it->second != product
+ && it->second->name != product->name
+ && (!dependency.limitToSubProject
+ || haveSameSubProject(*product, *it->second))) {
+ matchingProducts.push_back(it->second);
+ }
+ }
+ }
+ if (matchingProducts.empty()) {
+ qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything."
+ << dependency.item->location();
+ return {};
+ }
+ for (ProductContext * const match : matchingProducts)
+ dependencies.emplace(match, dependency);
+ return dependencies;
+ }
+
+ const QStringList profiles = dependency.profiles && !dependency.profiles->isEmpty()
+ ? *dependency.profiles : QStringList(QString());
+ const QStringList multiplexIds = !dependency.multiplexIds.isEmpty()
+ ? dependency.multiplexIds : QStringList(QString());
+ const QStringList subModules = !dependency.subModules.isEmpty()
+ ? dependency.subModules : QStringList(QString());
+ for (const QString &profile : profiles) {
+ for (const QString &multiplexId : multiplexIds) {
+ for (const QString &subModule : subModules) {
+ QualifiedId name = dependency.name;
+ if (!subModule.isEmpty())
+ name << subModule.split(QLatin1Char('.'));
+ dependencies.emplace(dependency, name, profile, multiplexId);
+ }
+ }
+ }
+ return dependencies;
+}
+
+void DependenciesResolver::Private::setSearchPathsForProduct()
+{
+ QBS_CHECK(product->searchPaths.isEmpty());
+
+ product->searchPaths = itemReader.readExtraSearchPaths(product->item, evaluator);
+ Settings settings(parameters.settingsDirectory());
+ const QStringList prefsSearchPaths = Preferences(&settings, product->profileModuleProperties)
+ .searchPaths();
+ const QStringList &currentSearchPaths = itemReader.allSearchPaths();
+ for (const QString &p : prefsSearchPaths) {
+ if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
+ product->searchPaths << p;
+ }
+}
+
+QVariantMap DependenciesResolver::Private::extractParameters(Item *dependsItem) const
+{
+ QVariantMap result;
+ const Item::PropertyMap &itemProperties = filterItemProperties(dependsItem->properties());
+ if (itemProperties.empty())
+ return result;
+ auto origProperties = dependsItem->properties();
+
+ // TODO: This is not exception-safe. Also, can't we do the item value check along the
+ // way, without allocationg an extra map and exchanging the list of children?
+ dependsItem->setProperties(itemProperties);
+
+ JSValue sv = evaluator.scriptValue(dependsItem);
+ try {
+ result = safeToVariant(evaluator.engine()->context(), sv);
+ } catch (const ErrorInfo &exception) {
+ auto ei = exception;
+ ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location());
+ throw ei;
+ }
+ dependsItem->setProperties(origProperties);
+ return result;
+}
+
+void DependenciesResolver::Private::checkForModuleNamePrefixCollision(
+ const FullyResolvedDependsItem &dependency)
+{
+ if (!product->item)
+ return;
+
+ for (const Item::Module &m : product->item->modules()) {
+ if (m.name.length() == dependency.name.length()
+ || m.name.front() != dependency.name.front()) {
+ continue;
+ }
+ QualifiedId shortName;
+ QualifiedId longName;
+ if (m.name < dependency.name) {
+ shortName = m.name;
+ longName = dependency.name;
+ } else {
+ shortName = dependency.name;
+ longName = m.name;
+ }
+ 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()), dependency.location());
+ }
+}
+
+Item::Module DependenciesResolver::Private::createModule(
+ const FullyResolvedDependsItem &dependency, Item *item, ProductContext *productDep)
+{
+ Item::Module m;
+ m.item = item;
+ if (productDep) {
+ m.productInfo.emplace(productDep->item, productDep->multiplexConfigurationId,
+ productDep->profileName);
+ }
+ m.name = dependency.name;
+ m.required = dependency.requiredLocally;
+ m.versionRange = dependency.versionRange;
+ return m;
+}
+
+FullyResolvedDependsItem::FullyResolvedDependsItem(
+ ProductContext *product, const EvaluatedDependsItem &dependency)
+ : product(product), item(dependency.item), name(product->name),
+ versionRange(dependency.versionRange), parameters(dependency.parameters),
+ fallbackMode(FallbackMode::Disabled), checkProduct(false) {}
+
+FullyResolvedDependsItem FullyResolvedDependsItem::makeBaseDependency()
+{
+ FullyResolvedDependsItem item;
+ item.fallbackMode = FallbackMode::Disabled;
+ item.name = StringConstants::qbsModule();
+ return item;
+}
+
+FullyResolvedDependsItem::FullyResolvedDependsItem(
+ const EvaluatedDependsItem &dependency, QualifiedId name, QString profile, QString multiplexId)
+ : item(dependency.item), name(std::move(name)),
+ profile(std::move(profile)), multiplexId(std::move(multiplexId)),
+ versionRange(dependency.versionRange),
+ parameters(dependency.parameters),
+ limitToSubProject(dependency.limitToSubProject),
+ fallbackMode(dependency.fallbackMode),
+ requiredLocally(dependency.requiredLocally),
+ requiredGlobally(dependency.requiredGlobally) {}
+
+QString FullyResolvedDependsItem::id() const
+{
+ if (!item) {
+ QBS_CHECK(name.toString() == StringConstants::qbsModule());
+ return {};
+ }
+ return item->id();
+}
+
+CodeLocation FullyResolvedDependsItem::location() const
+{
+ if (!item) {
+ QBS_CHECK(name.toString() == StringConstants::qbsModule());
+ return {};
+ }
+ return item->location();
+}
+
+QString FullyResolvedDependsItem::displayName() const
+{
+ return ProductItemMultiplexer::fullProductDisplayName(name.toString(), multiplexId);
+}
+
+bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2)
+{
+ for (const Item *otherParent = p2.item->parent(); otherParent;
+ otherParent = otherParent->parent()) {
+ if (otherParent == p1.item->parent())
+ return true;
+ }
+ return false;
+}
+
+Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties)
+{
+ Item::PropertyMap result;
+ for (auto it = properties.begin(); it != properties.end(); ++it) {
+ if (it.value()->type() == Value::ItemValueType)
+ result.insert(it.key(), it.value());
+ }
+ return result;
+}
+
+QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
+{
+ QVariantMap result;
+ handleJsProperties(ctx, v, [&](const JSAtom &prop, const JSPropertyDescriptor &desc) {
+ const JSValue u = desc.value;
+ if (JS_IsError(ctx, u))
+ throw ErrorInfo(getJsString(ctx, u));
+ const QString name = getJsString(ctx, prop);
+ result[name] = (JS_IsObject(u) && !JS_IsArray(ctx, u) && !JS_IsRegExp(ctx, u))
+ ? safeToVariant(ctx, u) : getJsVariant(ctx, u);
+ });
+ return result;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/dependenciesresolver.h b/src/lib/corelib/loader/dependenciesresolver.h
new file mode 100644
index 000000000..dde1207b4
--- /dev/null
+++ b/src/lib/corelib/loader/dependenciesresolver.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/pimpl.h>
+#include <tools/set.h>
+
+#include <QtGlobal>
+
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
+namespace qbs {
+class SetupProjectParameters;
+namespace Internal {
+class Evaluator;
+class Item;
+class ItemPool;
+class ItemReader;
+class Logger;
+class ModuleInstantiator;
+class ProbesResolver;
+class ProductContext;
+class StoredModuleProviderInfo;
+enum class Deferral;
+
+// Collects the products' dependencies and builds the list of modules from them.
+// Actual loading of module files is offloaded to ModuleLoader.
+class DependenciesResolver
+{
+public:
+ DependenciesResolver(const SetupProjectParameters &parameters, ItemPool &itemPool,
+ Evaluator &evaluator, ItemReader &itemReader,
+ ProbesResolver &probesResolver, ModuleInstantiator &moduleInstantiator,
+ Logger &logger);
+ ~DependenciesResolver();
+
+ // Returns false if the product has unhandled product dependencies and thus needs
+ // to be deferred, true otherwise.
+ bool resolveDependencies(ProductContext &product, Deferral deferral);
+
+ void checkDependencyParameterDeclarations(const Item *productItem,
+ const QString &productName) const;
+
+ void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
+ StoredModuleProviderInfo storedModuleProviderInfo() const;
+ const Set<QString> &tempQbsFiles() const;
+
+ void printProfilingInfo(int indent);
+
+ // Note: This function is never called for regular loading of the base module into a product,
+ // but only for the special cases of loading the dummy base module into a project
+ // and temporarily providing a base module for product multiplexing.
+ Item *loadBaseModule(ProductContext &product, Item *item);
+
+private:
+ class Private;
+ Pimpl<Private> d;
+};
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/loader/itemreader.cpp b/src/lib/corelib/loader/itemreader.cpp
index 6aece2044..9eb08d8b9 100644
--- a/src/lib/corelib/loader/itemreader.cpp
+++ b/src/lib/corelib/loader/itemreader.cpp
@@ -43,9 +43,11 @@
#include <language/deprecationinfo.h>
#include <language/evaluator.h>
+#include <language/filecontext.h>
#include <language/item.h>
#include <language/value.h>
#include <logging/categories.h>
+#include <tools/fileinfo.h>
#include <tools/profiling.h>
#include <tools/setupprojectparameters.h>
#include <tools/stringconstants.h>
@@ -67,7 +69,8 @@ static void makePathsCanonical(QStringList &paths)
}
ItemReader::ItemReader(const SetupProjectParameters &parameters, Logger &logger)
- : m_visitorState(std::make_unique<ItemReaderVisitorState>(logger))
+ : m_visitorState(std::make_unique<ItemReaderVisitorState>(logger)),
+ m_projectFilePath(parameters.projectFilePath())
{
setSearchPaths(parameters.searchPaths());
m_visitorState->setDeprecationWarningMode(parameters.deprecationWarningMode());
@@ -216,6 +219,23 @@ Item *ItemReader::setupItemFromFile(
return item;
}
+QStringList ItemReader::readExtraSearchPaths(Item *item, Evaluator &evaluator, bool *wasSet)
+{
+ QStringList result;
+ const QStringList paths = evaluator.stringListValue(
+ item, StringConstants::qbsSearchPathsProperty(), wasSet);
+ const JSSourceValueConstPtr prop = item->sourceProperty(
+ StringConstants::qbsSearchPathsProperty());
+
+ // 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_projectFilePath);
+ for (const QString &path : paths)
+ result += FileInfo::resolvePath(basePath, path);
+ return result;
+}
+
SearchPathsManager::SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths)
: m_itemReader(itemReader),
m_oldSize(itemReader.extraSearchPathsStack().size())
diff --git a/src/lib/corelib/loader/itemreader.h b/src/lib/corelib/loader/itemreader.h
index 8c485e8fb..7c5d7d11c 100644
--- a/src/lib/corelib/loader/itemreader.h
+++ b/src/lib/corelib/loader/itemreader.h
@@ -84,6 +84,8 @@ public:
Item *setupItemFromFile(const QString &filePath, const CodeLocation &referencingLocation,
Evaluator &evaluator);
+ QStringList readExtraSearchPaths(Item *item, Evaluator &evaluator, bool *wasSet = nullptr);
+
Set<QString> filesRead() const;
qint64 elapsedTime() const { return m_elapsedTime; }
@@ -100,6 +102,7 @@ private:
std::vector<QStringList> m_extraSearchPaths;
mutable QStringList m_allSearchPaths;
const std::unique_ptr<ItemReaderVisitorState> m_visitorState;
+ const QString m_projectFilePath;
qint64 m_elapsedTime = -1;
};
diff --git a/src/lib/corelib/loader/loaderutils.cpp b/src/lib/corelib/loader/loaderutils.cpp
new file mode 100644
index 000000000..b22f644ae
--- /dev/null
+++ b/src/lib/corelib/loader/loaderutils.cpp
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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 "loaderutils.h"
+
+#include "productitemmultiplexer.h"
+
+#include <language/evaluator.h>
+#include <language/item.h>
+#include <language/language.h>
+#include <language/value.h>
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/stringconstants.h>
+
+namespace qbs::Internal {
+
+void mergeParameters(QVariantMap &dst, const QVariantMap &src)
+{
+ for (auto it = src.begin(); it != src.end(); ++it) {
+ if (it.value().userType() == QMetaType::QVariantMap) {
+ QVariant &vdst = dst[it.key()];
+ QVariantMap mdst = vdst.toMap();
+ mergeParameters(mdst, it.value().toMap());
+ vdst = mdst;
+ } else {
+ dst[it.key()] = it.value();
+ }
+ }
+}
+
+ShadowProductInfo getShadowProductInfo(const ProductContext &product)
+{
+ const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix());
+ return std::make_pair(isShadowProduct,
+ isShadowProduct
+ ? product.name.mid(StringConstants::shadowProductPrefix().size())
+ : QString());
+}
+
+void adjustParametersScopes(Item *item, Item *scope)
+{
+ if (item->type() == ItemType::ModuleParameters) {
+ item->setScope(scope);
+ return;
+ }
+
+ for (const auto &value : item->properties()) {
+ if (value->type() == Value::ItemValueType)
+ adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope);
+ }
+}
+
+QString ProductContext::uniqueName() const
+{
+ return ResolvedProduct::uniqueName(name, multiplexConfigurationId);
+}
+
+QString ProductContext::displayName() const
+{
+ return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId);
+}
+
+void ProductContext::handleError(const ErrorInfo &error)
+{
+ const bool alreadyHadError = info.delayedError.hasError();
+ if (!alreadyHadError) {
+ info.delayedError.append(Tr::tr("Error while handling product '%1':")
+ .arg(name), item->location());
+ }
+ if (error.isInternalError()) {
+ if (alreadyHadError) {
+ qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString()
+ << "in product" << name;
+ return;
+ }
+ }
+ const auto errorItems = error.items();
+ for (const ErrorItem &ei : errorItems)
+ info.delayedError.append(ei.description(), ei.codeLocation());
+ project->topLevelProject->productInfos[item] = info;
+ project->topLevelProject->disabledItems << item;
+ project->topLevelProject->erroneousProducts.insert(name);
+}
+
+bool TopLevelProjectContext::checkItemCondition(Item *item, Evaluator &evaluator)
+{
+ if (evaluator.boolValue(item, StringConstants::conditionProperty()))
+ return true;
+ disabledItems += item;
+ return false;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/loaderutils.h b/src/lib/corelib/loader/loaderutils.h
new file mode 100644
index 000000000..15e36b014
--- /dev/null
+++ b/src/lib/corelib/loader/loaderutils.h
@@ -0,0 +1,136 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <language/filetags.h>
+#include <language/forward_decls.h>
+#include <language/qualifiedid.h>
+#include <tools/set.h>
+#include <tools/version.h>
+
+#include <QStringList>
+#include <QVariant>
+
+#include <vector>
+
+namespace qbs::Internal {
+class Evaluator;
+class Item;
+class ProductContext;
+class ProjectContext;
+
+using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>;
+using ShadowProductInfo = std::pair<bool, QString>;
+
+enum class FallbackMode { Enabled, Disabled };
+enum class Deferral { Allowed, NotAllowed };
+
+class ProductInfo
+{
+public:
+ std::vector<ProbeConstPtr> probes;
+ ModulePropertiesPerGroup modulePropertiesSetInGroups;
+ ErrorInfo delayedError;
+};
+
+class ProductContext
+{
+public:
+ QString uniqueName() const;
+ QString displayName() const;
+ void handleError(const ErrorInfo &error);
+
+ QString name;
+ Item *item = nullptr;
+ Item *scope = nullptr;
+ ProjectContext *project = nullptr;
+ Item *mergedExportItem = nullptr;
+ ProductInfo info;
+ QString profileName;
+ QString multiplexConfigurationId;
+ QVariantMap profileModuleProperties; // Tree-ified module properties from profile.
+ QVariantMap moduleProperties; // Tree-ified module properties from profile + overridden values.
+ QVariantMap defaultParameters; // In Export item.
+ QStringList searchPaths;
+
+ bool dependenciesResolved = false;
+};
+
+class TopLevelProjectContext
+{
+public:
+ TopLevelProjectContext() = default;
+ TopLevelProjectContext(const TopLevelProjectContext &) = delete;
+ TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete;
+ ~TopLevelProjectContext() { qDeleteAll(projects); }
+
+ bool checkItemCondition(Item *item, Evaluator &evaluator);
+
+ std::vector<ProjectContext *> projects;
+ std::list<std::pair<ProductContext *, int>> productsToHandle;
+ std::multimap<QString, ProductContext *> productsByName;
+ std::unordered_map<Item *, ProductInfo> productInfos;
+ Set<Item *> disabledItems;
+ Set<QString> erroneousProducts;
+ std::vector<ProbeConstPtr> probes;
+ QString buildDirectory;
+ QVariantMap profileConfigs;
+
+ // For fast look-up when resolving Depends.productTypes.
+ // The contract is that it contains fully handled, error-free, enabled products.
+ std::multimap<FileTag, ProductContext *> productsByType;
+};
+
+class ProjectContext
+{
+public:
+ QString name;
+ Item *item = nullptr;
+ Item *scope = nullptr;
+ TopLevelProjectContext *topLevelProject = nullptr;
+ std::vector<ProductContext> products;
+ std::vector<QStringList> searchPathsStack;
+};
+
+void mergeParameters(QVariantMap &dst, const QVariantMap &src);
+ShadowProductInfo getShadowProductInfo(const ProductContext &product);
+void adjustParametersScopes(Item *item, Item *scope);
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/moduleloader.cpp b/src/lib/corelib/loader/moduleloader.cpp
index 4be08616a..df8318d12 100644
--- a/src/lib/corelib/loader/moduleloader.cpp
+++ b/src/lib/corelib/loader/moduleloader.cpp
@@ -66,10 +66,12 @@ namespace qbs::Internal {
class ModuleLoader::Private
{
public:
- Private(const SetupProjectParameters &setupParameters, ModuleProviderLoader &providerLoader,
+ Private(const SetupProjectParameters &setupParameters, ProbesResolver &probesResolver,
ItemReader &itemReader, Evaluator &evaluator, Logger &logger)
- : setupParameters(setupParameters), providerLoader(providerLoader),
- itemReader(itemReader), evaluator(evaluator), logger(logger) {}
+ : setupParameters(setupParameters), itemReader(itemReader), evaluator(evaluator),
+ logger(logger),
+ providerLoader(setupParameters, itemReader, evaluator, probesResolver, logger)
+ {}
std::pair<Item *, bool> loadModuleFile(const ProductContext &product,
const QString &moduleName, const QString &filePath);
@@ -81,10 +83,10 @@ public:
const Item::Modules &modules);
const SetupProjectParameters &setupParameters;
- ModuleProviderLoader &providerLoader;
ItemReader &itemReader;
Evaluator &evaluator;
Logger &logger;
+ ModuleProviderLoader providerLoader;
// The keys are file paths, the values are module prototype items accompanied by a profile.
std::unordered_map<QString, std::vector<std::pair<Item *, QString>>> modulePrototypes;
@@ -103,9 +105,9 @@ public:
};
ModuleLoader::ModuleLoader(
- const SetupProjectParameters &setupParameters, ModuleProviderLoader &providerLoader,
+ const SetupProjectParameters &setupParameters, ProbesResolver &probesResolver,
ItemReader &itemReader, Evaluator &evaluator, Logger &logger)
- : d(makePimpl<Private>(setupParameters, providerLoader, itemReader, evaluator, logger)) { }
+ : d(makePimpl<Private>(setupParameters, probesResolver, itemReader, evaluator, logger)) { }
ModuleLoader::~ModuleLoader() = default;
@@ -286,6 +288,21 @@ ModuleLoader::Result ModuleLoader::searchAndLoadModuleFile(
return loadResult;
}
+void ModuleLoader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo)
+{
+ d->providerLoader.setStoredModuleProviderInfo(moduleProviderInfo);
+}
+
+StoredModuleProviderInfo ModuleLoader::storedModuleProviderInfo() const
+{
+ return d->providerLoader.storedModuleProviderInfo();
+}
+
+const Set<QString> &ModuleLoader::tempQbsFiles() const
+{
+ return d->providerLoader.tempQbsFiles();
+}
+
std::pair<Item *, bool> ModuleLoader::Private::loadModuleFile(
const ProductContext &product, const QString &moduleName, const QString &filePath)
{
diff --git a/src/lib/corelib/loader/moduleloader.h b/src/lib/corelib/loader/moduleloader.h
index 35c7102d7..28693426f 100644
--- a/src/lib/corelib/loader/moduleloader.h
+++ b/src/lib/corelib/loader/moduleloader.h
@@ -43,6 +43,7 @@
#include <language/forward_decls.h>
#include <language/item.h>
#include <tools/pimpl.h>
+#include <tools/set.h>
#include <QString>
#include <QVariantMap>
@@ -57,13 +58,14 @@ class Evaluator;
enum class FallbackMode;
class ItemReader;
class Logger;
-class ModuleProviderLoader;
+class ProbesResolver;
+class StoredModuleProviderInfo;
class ModuleLoader
{
public:
ModuleLoader(const SetupProjectParameters &setupParameters,
- ModuleProviderLoader &providerLoader, ItemReader &itemReader,
+ ProbesResolver &probesResolver, ItemReader &itemReader,
Evaluator &evaluator, Logger &logger);
~ModuleLoader();
@@ -86,6 +88,10 @@ public:
const QualifiedId &moduleName,
FallbackMode fallbackMode, bool isRequired);
+ void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
+ StoredModuleProviderInfo storedModuleProviderInfo() const;
+ const Set<QString> &tempQbsFiles() const;
+
void checkDependencyParameterDeclarations(const Item *productItem,
const QString &productName) const;
void forwardParameterDeclarations(const Item *dependsItem, const Item::Modules &modules);
diff --git a/src/lib/corelib/loader/moduleproviderloader.h b/src/lib/corelib/loader/moduleproviderloader.h
index eabb5ead5..acd158bb4 100644
--- a/src/lib/corelib/loader/moduleproviderloader.h
+++ b/src/lib/corelib/loader/moduleproviderloader.h
@@ -41,6 +41,8 @@
#ifndef MODULEPROVIDERLOADER_H
#define MODULEPROVIDERLOADER_H
+#include "loaderutils.h"
+
#include <language/forward_decls.h>
#include <language/moduleproviderinfo.h>
@@ -59,8 +61,6 @@ class ItemReader;
class Logger;
class ProbesResolver;
-enum class FallbackMode { Enabled, Disabled };
-
class ModuleProviderLoader
{
public:
diff --git a/src/lib/corelib/loader/productitemmultiplexer.h b/src/lib/corelib/loader/productitemmultiplexer.h
index 795b77b08..3af79cecb 100644
--- a/src/lib/corelib/loader/productitemmultiplexer.h
+++ b/src/lib/corelib/loader/productitemmultiplexer.h
@@ -73,8 +73,7 @@ public:
const std::function<void()> &dropTempQbsModule
);
- QVariantMap multiplexIdToVariantMap(const QString &multiplexId);
-
+ static QVariantMap multiplexIdToVariantMap(const QString &multiplexId);
static QString fullProductDisplayName(const QString &name, const QString &multiplexId);
private:
diff --git a/src/lib/corelib/loader/projectresolver.cpp b/src/lib/corelib/loader/projectresolver.cpp
index 97b701624..388115e8e 100644
--- a/src/lib/corelib/loader/projectresolver.cpp
+++ b/src/lib/corelib/loader/projectresolver.cpp
@@ -95,9 +95,9 @@ static const FileTag unknownFileTag()
return tag;
}
-struct ProjectContext
+struct ResolverProjectContext
{
- ProjectContext *parentContext = nullptr;
+ ResolverProjectContext *parentContext = nullptr;
ResolvedProjectPtr project;
std::vector<FileTaggerConstPtr> fileTaggers;
std::vector<RulePtr> rules;
@@ -106,7 +106,7 @@ struct ProjectContext
};
using FileLocations = QHash<std::pair<QString, QString>, CodeLocation>;
-struct ProductContext
+struct ResolverProductContext
{
ResolvedProductPtr product;
QString buildDirectory;
@@ -142,36 +142,36 @@ public:
QString verbatimValue(Item *item, const QString &name, bool *propertyWasSet = nullptr) const;
ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const;
ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const;
- void ignoreItem(Item *item, ProjectContext *projectContext);
+ void ignoreItem(Item *item, ResolverProjectContext *projectContext);
TopLevelProjectPtr resolveTopLevelProject();
- void resolveProject(Item *item, ProjectContext *projectContext);
- void resolveProjectFully(Item *item, ProjectContext *projectContext);
- void resolveSubProject(Item *item, ProjectContext *projectContext);
- void resolveProduct(Item *item, ProjectContext *projectContext);
- void resolveProductFully(Item *item, ProjectContext *projectContext);
- void resolveModules(const Item *item, ProjectContext *projectContext);
+ void resolveProject(Item *item, ResolverProjectContext *projectContext);
+ void resolveProjectFully(Item *item, ResolverProjectContext *projectContext);
+ void resolveSubProject(Item *item, ResolverProjectContext *projectContext);
+ void resolveProduct(Item *item, ResolverProjectContext *projectContext);
+ void resolveProductFully(Item *item, ResolverProjectContext *projectContext);
+ void resolveModules(const Item *item, ResolverProjectContext *projectContext);
void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
const QVariantMap &parameters, JobLimits &jobLimits,
- ProjectContext *projectContext);
+ ResolverProjectContext *projectContext);
void gatherProductTypes(ResolvedProduct *product, Item *item);
QVariantMap resolveAdditionalModuleProperties(const Item *group,
const QVariantMap &currentValues);
- void resolveGroup(Item *item, ProjectContext *projectContext);
- void resolveGroupFully(Item *item, ProjectContext *projectContext, bool isEnabled);
- void resolveShadowProduct(Item *item, ProjectContext *);
- void resolveExport(Item *exportItem, ProjectContext *);
+ void resolveGroup(Item *item, ResolverProjectContext *projectContext);
+ void resolveGroupFully(Item *item, ResolverProjectContext *projectContext, bool isEnabled);
+ void resolveShadowProduct(Item *item, ResolverProjectContext *);
+ void resolveExport(Item *exportItem, ResolverProjectContext *);
std::unique_ptr<ExportedItem> resolveExportChild(const Item *item,
const ExportedModule &module);
- void resolveRule(Item *item, ProjectContext *projectContext);
+ void resolveRule(Item *item, ResolverProjectContext *projectContext);
void resolveRuleArtifact(const RulePtr &rule, Item *item);
void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item,
const QStringList &namePrefix,
QualifiedIdSet *seenBindings);
- void resolveFileTagger(Item *item, ProjectContext *projectContext);
- void resolveJobLimit(Item *item, ProjectContext *projectContext);
- void resolveScanner(Item *item, ProjectContext *projectContext);
+ void resolveFileTagger(Item *item, ResolverProjectContext *projectContext);
+ void resolveJobLimit(Item *item, ResolverProjectContext *projectContext);
+ void resolveScanner(Item *item, ResolverProjectContext *projectContext);
void resolveProductDependencies();
- void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const;
+ void postProcess(const ResolvedProductPtr &product, ResolverProjectContext *projectContext) const;
void applyFileTaggers(const ResolvedProductPtr &product) const;
QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true);
QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors);
@@ -184,7 +184,7 @@ public:
const QVariant &v, const CodeLocation &loc, const PropertyDeclaration &decl,
const QString &key) const;
void createProductConfig(ResolvedProduct *product);
- ProjectContext createProjectContext(ProjectContext *parentProjectContext) const;
+ ResolverProjectContext createProjectContext(ResolverProjectContext *parentProjectContext) const;
void adaptExportedPropertyValues(const Item *shadowProductItem);
void collectExportedProductDependencies();
@@ -216,9 +216,9 @@ public:
std::vector<ExportedProperty> &properties);
using ItemFuncPtr = void (ProjectResolver::Private::*)(Item *item,
- ProjectContext *projectContext);
+ ResolverProjectContext *projectContext);
using ItemFuncMap = QMap<ItemType, ItemFuncPtr>;
- void callItemFunction(const ItemFuncMap &mappings, Item *item, ProjectContext *projectContext);
+ void callItemFunction(const ItemFuncMap &mappings, Item *item, ResolverProjectContext *projectContext);
ScriptEngine * const engine;
mutable Logger logger;
@@ -227,7 +227,7 @@ public:
SetupProjectParameters setupParams;
ProjectTreeBuilder::Result loadResult;
ProgressObserver *progressObserver = nullptr;
- ProductContext *productContext = nullptr;
+ ResolverProductContext *productContext = nullptr;
ModuleContext *moduleContext = nullptr;
QHash<Item *, ResolvedProductPtr> productsByItem;
QHash<FileTag, QList<ResolvedProductPtr>> productsByType;
@@ -392,7 +392,7 @@ QString ProjectResolver::Private::verbatimValue(Item *item, const QString &name,
return verbatimValue(item->property(name), propertyWasSet);
}
-void ProjectResolver::Private::ignoreItem(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::ignoreItem(Item *item, ResolverProjectContext *projectContext)
{
Q_UNUSED(item);
Q_UNUSED(projectContext);
@@ -433,7 +433,7 @@ TopLevelProjectPtr ProjectResolver::Private::resolveTopLevelProject()
project->profileConfigs = loadResult.profileConfigs;
project->probes = loadResult.projectProbes;
project->moduleProviderInfo = loadResult.storedModuleProviderInfo;
- ProjectContext projectContext;
+ ResolverProjectContext projectContext;
projectContext.project = project;
resolveProject(loadResult.root, &projectContext);
@@ -473,7 +473,7 @@ TopLevelProjectPtr ProjectResolver::Private::resolveTopLevelProject()
return project;
}
-void ProjectResolver::Private::resolveProject(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveProject(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
@@ -494,7 +494,7 @@ void ProjectResolver::Private::resolveProject(Item *item, ProjectContext *projec
}
}
-void ProjectResolver::Private::resolveProjectFully(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveProjectFully(Item *item, ResolverProjectContext *projectContext)
{
projectContext->project->enabled = projectContext->project->enabled
&& evaluator.boolValue(item, StringConstants::conditionProperty());
@@ -546,9 +546,9 @@ void ProjectResolver::Private::resolveProjectFully(Item *item, ProjectContext *p
postProcess(product, projectContext);
}
-void ProjectResolver::Private::resolveSubProject(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveSubProject(Item *item, ResolverProjectContext *projectContext)
{
- ProjectContext subProjectContext = createProjectContext(projectContext);
+ ResolverProjectContext subProjectContext = createProjectContext(projectContext);
Item * const projectItem = item->child(ItemType::Project);
if (projectItem) {
@@ -565,11 +565,11 @@ void ProjectResolver::Private::resolveSubProject(Item *item, ProjectContext *pro
}
}
-void ProjectResolver::Private::resolveProduct(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveProduct(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
evaluator.clearPropertyDependencies();
- ProductContext productContext;
+ ResolverProductContext productContext;
productContext.item = item;
ResolvedProductPtr product = ResolvedProduct::create();
product->enabled = projectContext->project->enabled;
@@ -579,7 +579,7 @@ void ProjectResolver::Private::resolveProduct(Item *item, ProjectContext *projec
product->location = item->location();
class ProductContextSwitcher {
public:
- ProductContextSwitcher(ProjectResolver::Private &resolver, ProductContext &newContext,
+ ProductContextSwitcher(ProjectResolver::Private &resolver, ResolverProductContext &newContext,
ProgressObserver *progressObserver)
: m_resolver(resolver), m_progressObserver(progressObserver) {
QBS_CHECK(!m_resolver.productContext);
@@ -595,7 +595,7 @@ void ProjectResolver::Private::resolveProduct(Item *item, ProjectContext *projec
ProgressObserver * const m_progressObserver;
} contextSwitcher(*this, productContext, progressObserver);
const auto errorFromDelayedError = [&] {
- ProjectTreeBuilder::Result::ProductInfo &pi = loadResult.productInfos[item];
+ ProductInfo &pi = loadResult.productInfos[item];
if (pi.delayedError.hasError()) {
ErrorInfo errorInfo;
@@ -638,7 +638,7 @@ void ProjectResolver::Private::resolveProduct(Item *item, ProjectContext *projec
}
}
-void ProjectResolver::Private::resolveProductFully(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveProductFully(Item *item, ResolverProjectContext *projectContext)
{
const ResolvedProductPtr product = productContext->product;
productItemMap.insert(product, item);
@@ -654,7 +654,7 @@ void ProjectResolver::Private::resolveProductFully(Item *item, ProjectContext *p
productsByItem.insert(item, product);
product->enabled = product->enabled
&& evaluator.boolValue(item, StringConstants::conditionProperty());
- ProjectTreeBuilder::Result::ProductInfo &pi = loadResult.productInfos[item];
+ ProductInfo &pi = loadResult.productInfos[item];
gatherProductTypes(product.get(), item);
product->targetName = evaluator.stringValue(item, StringConstants::targetNameProperty());
product->sourceDirectory = evaluator.stringValue(
@@ -707,7 +707,7 @@ void ProjectResolver::Private::resolveProductFully(Item *item, ProjectContext *p
for (Item * const child : qAsConst(subItems))
callItemFunction(mapping, child, projectContext);
- for (const ProjectContext *p = projectContext; p; p = p->parentContext) {
+ for (const ResolverProjectContext *p = projectContext; p; p = p->parentContext) {
JobLimits tempLimits = p->jobLimits;
product->jobLimits = tempLimits.update(product->jobLimits);
}
@@ -718,7 +718,7 @@ void ProjectResolver::Private::resolveProductFully(Item *item, ProjectContext *p
productsByType[t].push_back(product);
}
-void ProjectResolver::Private::resolveModules(const Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveModules(const Item *item, ResolverProjectContext *projectContext)
{
JobLimits jobLimits;
for (const Item::Module &m : item->modules()) {
@@ -734,7 +734,7 @@ void ProjectResolver::Private::resolveModules(const Item *item, ProjectContext *
void ProjectResolver::Private::resolveModule(
const QualifiedId &moduleName, Item *item, bool isProduct, const QVariantMap &parameters,
- JobLimits &jobLimits, ProjectContext *projectContext)
+ JobLimits &jobLimits, ResolverProjectContext *projectContext)
{
checkCancelation();
if (!item->isPresentModule())
@@ -901,7 +901,7 @@ QVariantMap ProjectResolver::Private::resolveAdditionalModuleProperties(
return modulesMap;
}
-void ProjectResolver::Private::resolveGroup(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveGroup(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
const bool parentEnabled = productContext->currentGroup
@@ -924,7 +924,7 @@ void ProjectResolver::Private::resolveGroup(Item *item, ProjectContext *projectC
}
void ProjectResolver::Private::resolveGroupFully(
- Item *item, ProjectContext *projectContext, bool isEnabled)
+ Item *item, ResolverProjectContext *projectContext, bool isEnabled)
{
AccumulatingTimer groupTimer(setupParams.logElapsedTime() ? &elapsedTimeGroups : nullptr);
@@ -963,7 +963,7 @@ void ProjectResolver::Private::resolveGroupFully(
if (!isEnabled)
return;
- ProductContext::ArtifactPropertiesInfo &apinfo
+ ResolverProductContext::ArtifactPropertiesInfo &apinfo
= productContext->artifactPropertiesPerFilter[fileTagsFilter];
if (apinfo.first) {
const auto it = std::find_if(apinfo.second.cbegin(), apinfo.second.cend(),
@@ -1054,13 +1054,13 @@ void ProjectResolver::Private::resolveGroupFully(
class GroupContextSwitcher {
public:
- GroupContextSwitcher(ProductContext &context, const GroupConstPtr &newGroup)
+ GroupContextSwitcher(ResolverProductContext &context, const GroupConstPtr &newGroup)
: m_context(context), m_oldGroup(context.currentGroup) {
m_context.currentGroup = newGroup;
}
~GroupContextSwitcher() { m_context.currentGroup = m_oldGroup; }
private:
- ProductContext &m_context;
+ ResolverProductContext &m_context;
const GroupConstPtr m_oldGroup;
};
GroupContextSwitcher groupSwitcher(*productContext, group);
@@ -1171,7 +1171,7 @@ void ProjectResolver::Private::collectExportedProductDependencies()
}
}
-void ProjectResolver::Private::resolveShadowProduct(Item *item, ProjectContext *)
+void ProjectResolver::Private::resolveShadowProduct(Item *item, ResolverProjectContext *)
{
if (!productContext->product->enabled)
return;
@@ -1292,7 +1292,7 @@ static QString getLineAtLocation(const CodeLocation &loc, const QString &content
return content.mid(pos, eolPos - pos);
}
-void ProjectResolver::Private::resolveExport(Item *exportItem, ProjectContext *)
+void ProjectResolver::Private::resolveExport(Item *exportItem, ResolverProjectContext *)
{
ExportedModule &exportedModule = productContext->product->exportedModule;
setupExportedProperties(exportItem, QString(), exportedModule.m_properties);
@@ -1392,7 +1392,7 @@ ResolvedFileContextPtr ProjectResolver::Private::resolvedFileContext(
return result;
}
-void ProjectResolver::Private::resolveRule(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveRule(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
@@ -1527,7 +1527,7 @@ void ProjectResolver::Private::resolveRuleArtifactBinding(
}
}
-void ProjectResolver::Private::resolveFileTagger(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveFileTagger(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
@@ -1553,7 +1553,7 @@ void ProjectResolver::Private::resolveFileTagger(Item *item, ProjectContext *pro
fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority));
}
-void ProjectResolver::Private::resolveJobLimit(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveJobLimit(Item *item, ResolverProjectContext *projectContext)
{
if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
return;
@@ -1582,7 +1582,7 @@ void ProjectResolver::Private::resolveJobLimit(Item *item, ProjectContext *proje
jobLimits.setJobLimit(jobLimit);
}
-void ProjectResolver::Private::resolveScanner(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveScanner(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
if (!evaluator.boolValue(item, StringConstants::conditionProperty())) {
@@ -1737,7 +1737,7 @@ void ProjectResolver::Private::resolveProductDependencies()
}
void ProjectResolver::Private::postProcess(const ResolvedProductPtr &product,
- ProjectContext *projectContext) const
+ ResolverProjectContext *projectContext) const
{
product->fileTaggers << projectContext->fileTaggers;
std::sort(std::begin(product->fileTaggers), std::end(product->fileTaggers),
@@ -1977,22 +1977,22 @@ void ProjectResolver::Private::createProductConfig(ResolvedProduct *product)
}
void ProjectResolver::Private::callItemFunction(const ItemFuncMap &mappings, Item *item,
- ProjectContext *projectContext)
+ ResolverProjectContext *projectContext)
{
const ItemFuncPtr f = mappings.value(item->type());
QBS_CHECK(f);
if (item->type() == ItemType::Project) {
- ProjectContext subProjectContext = createProjectContext(projectContext);
+ ResolverProjectContext subProjectContext = createProjectContext(projectContext);
(this->*f)(item, &subProjectContext);
} else {
(this->*f)(item, projectContext);
}
}
-ProjectContext ProjectResolver::Private::createProjectContext(
- ProjectContext *parentProjectContext) const
+ResolverProjectContext ProjectResolver::Private::createProjectContext(
+ ResolverProjectContext *parentProjectContext) const
{
- ProjectContext subProjectContext;
+ ResolverProjectContext subProjectContext;
subProjectContext.parentContext = parentProjectContext;
subProjectContext.project = ResolvedProject::create();
parentProjectContext->project->subProjects.push_back(subProjectContext.project);
diff --git a/src/lib/corelib/loader/projecttreebuilder.cpp b/src/lib/corelib/loader/projecttreebuilder.cpp
index a5d224a16..3fea3e856 100644
--- a/src/lib/corelib/loader/projecttreebuilder.cpp
+++ b/src/lib/corelib/loader/projecttreebuilder.cpp
@@ -39,11 +39,11 @@
#include "projecttreebuilder.h"
+#include "dependenciesresolver.h"
#include "groupshandler.h"
#include "itemreader.h"
#include "localprofiles.h"
#include "moduleinstantiator.h"
-#include "moduleloader.h"
#include "modulepropertymerger.h"
#include "probesresolver.h"
#include "productitemmultiplexer.h"
@@ -52,6 +52,7 @@
#include <language/evaluator.h>
#include <language/filecontext.h>
#include <language/filetags.h>
+#include <language/item.h>
#include <language/language.h>
#include <language/scriptengine.h>
#include <language/value.h>
@@ -85,127 +86,9 @@ namespace qbs::Internal {
namespace {
-class ContextBase
-{
-public:
- Item *item = nullptr;
- Item *scope = nullptr;
- QString name;
-};
-
-class ProjectContext;
-
-class ProductContext : public ContextBase
-{
-public:
- ProjectContext *project = nullptr;
- Item *mergedExportItem = nullptr;
- ProjectTreeBuilder::Result::ProductInfo info;
- QString profileName;
- QString multiplexConfigurationId;
- QVariantMap profileModuleProperties; // Tree-ified module properties from profile.
- QVariantMap moduleProperties; // Tree-ified module properties from profile + overridden values.
- QVariantMap defaultParameters; // In Export item.
- QStringList searchPaths;
-
- struct ResolvedDependsItem {
- Item *item = nullptr;
- QualifiedId name;
- QStringList subModules;
- FileTags productTypes;
- QStringList multiplexIds;
- std::optional<QStringList> profiles;
- VersionRange versionRange;
- QVariantMap parameters;
- bool limitToSubProject = false;
- FallbackMode fallbackMode = FallbackMode::Enabled;
- bool requiredLocally = true;
- bool requiredGlobally = true;
- };
- struct ResolvedAndMultiplexedDependsItem {
- ResolvedAndMultiplexedDependsItem(ProductContext *product,
- const ResolvedDependsItem &dependency)
- : product(product), item(dependency.item), name(product->name),
- versionRange(dependency.versionRange), parameters(dependency.parameters),
- fallbackMode(FallbackMode::Disabled), checkProduct(false) {}
- ResolvedAndMultiplexedDependsItem(const ResolvedDependsItem &dependency,
- QualifiedId name, QString profile, QString multiplexId)
- : item(dependency.item), name(std::move(name)), profile(std::move(profile)),
- multiplexId(std::move(multiplexId)),
- versionRange(dependency.versionRange), parameters(dependency.parameters),
- limitToSubProject(dependency.limitToSubProject), fallbackMode(dependency.fallbackMode),
- requiredLocally(dependency.requiredLocally),
- requiredGlobally(dependency.requiredGlobally) {}
- ResolvedAndMultiplexedDependsItem() = default;
- static ResolvedAndMultiplexedDependsItem makeBaseDependency() {
- ResolvedAndMultiplexedDependsItem item;
- item.fallbackMode = FallbackMode::Disabled;
- item.name = StringConstants::qbsModule();
- return item;
- }
-
- QString id() const;
- CodeLocation location() const;
- QString displayName() const;
-
- ProductContext *product = nullptr;
- Item *item = nullptr;
- QualifiedId name;
- QString profile;
- QString multiplexId;
- VersionRange versionRange;
- QVariantMap parameters;
- bool limitToSubProject = false;
- FallbackMode fallbackMode = FallbackMode::Enabled;
- bool requiredLocally = true;
- bool requiredGlobally = true;
- bool checkProduct = true;
- };
- struct DependenciesResolvingState {
- Item *loadingItem = nullptr;
- ResolvedAndMultiplexedDependsItem loadingItemOrigin;
- std::queue<Item *> pendingDependsItems;
- std::optional<ResolvedDependsItem> currentDependsItem;
- std::queue<ResolvedAndMultiplexedDependsItem> pendingResolvedDependencies;
- bool requiredByLoadingItem = true;
- };
- std::list<DependenciesResolvingState> resolveDependenciesState;
-
- QString uniqueName() const;
-};
-
-class TopLevelProjectContext
-{
-public:
- TopLevelProjectContext() = default;
- TopLevelProjectContext(const TopLevelProjectContext &) = delete;
- TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete;
- ~TopLevelProjectContext() { qDeleteAll(projects); }
-
- std::vector<ProjectContext *> projects;
- std::list<std::pair<ProductContext *, int>> productsToHandle;
- std::vector<ProbeConstPtr> probes;
- QString buildDirectory;
-};
-
-class ProjectContext : public ContextBase
-{
-public:
- TopLevelProjectContext *topLevelProject = nullptr;
- ProjectTreeBuilder::Result *result = nullptr;
- std::vector<ProductContext> products;
- std::vector<QStringList> searchPathsStack;
-};
-
-
-using ShadowProductInfo = std::pair<bool, QString>;
-enum class Deferral { Allowed, NotAllowed };
-enum class HandleDependency { Use, Ignore, Defer };
-
class TimingData {
public:
qint64 prepareProducts = 0;
- qint64 productDependencies = 0;
qint64 handleProducts = 0;
qint64 propertyChecking = 0;
};
@@ -224,28 +107,21 @@ public:
void collectNameFromOverride(const QString &overrideString);
Item *loadItemFromFile(const QString &filePath, const CodeLocation &referencingLocation);
Item *wrapInProjectIfNecessary(Item *item);
- void handleTopLevelProject(Result &loadResult, const Set<QString> &referencedFilePaths);
- void handleProject(Result *loadResult, TopLevelProjectContext *topLevelProjectContext,
- Item *projectItem, const Set<QString> &referencedFilePaths);
+ void handleTopLevelProject(Item *projectItem, const Set<QString> &referencedFilePaths);
+ void handleProject(Item *projectItem, const Set<QString> &referencedFilePaths);
void prepareProduct(ProjectContext &projectContext, Item *productItem);
- void handleNextProduct(TopLevelProjectContext &tlp);
+ void handleNextProduct();
void handleProduct(ProductContext &productContext, Deferral deferral);
- bool resolveDependencies(ProductContext &product, Deferral deferral);
- void setSearchPathsForProduct(ProductContext &product);
void handleSubProject(ProjectContext &projectContext, Item *projectItem,
const Set<QString> &referencedFilePaths);
void initProductProperties(const ProductContext &product);
void printProfilingInfo();
- void checkProjectNamesInOverrides(const TopLevelProjectContext &tlp);
+ void checkProjectNamesInOverrides();
void checkProductNamesInOverrides();
- void collectProductsByName(const TopLevelProjectContext &topLevelProject);
- void adjustDependsItemForMultiplexing(const ProductContext &product, Item *dependsItem);
- ShadowProductInfo getShadowProductInfo(const ProductContext &product) const;
- void handleProductError(const ErrorInfo &error, ProductContext &productContext);
+ void collectProductsByName();
void handleModuleSetupError(ProductContext &product, const Item::Module &module,
const ErrorInfo &error);
bool checkItemCondition(Item *item);
- QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr);
QList<Item *> multiplexProductItem(ProductContext &dummyContext, Item *productItem);
void checkCancelation() const;
QList<Item *> loadReferencedFile(const QString &relativePath,
@@ -257,25 +133,6 @@ public:
bool checkExportItemCondition(Item *exportItem, const ProductContext &product);
void resolveProbes(ProductContext &product, Item *item);
- Item *loadBaseModule(ProductContext &product, Item *item);
-
- struct LoadModuleResult {
- Item *moduleItem = nullptr;
- ProductContext *product = nullptr;
- HandleDependency handleDependency = HandleDependency::Use;
- };
- LoadModuleResult loadModule(ProductContext &product, Item *loadingItem,
- const ProductContext::ResolvedAndMultiplexedDependsItem &dependency,
- Deferral deferral);
-
- std::optional<ProductContext::ResolvedDependsItem>
- resolveDependsItem(const ProductContext &product, Item *dependsItem);
- std::queue<ProductContext::ResolvedAndMultiplexedDependsItem>
- multiplexDependency(const ProductContext &product,
- const ProductContext::ResolvedDependsItem &dependency);
- static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2);
- QVariantMap extractParameters(Item *dependsItem) const;
-
const SetupProjectParameters &parameters;
ItemPool &itemPool;
Evaluator &evaluator;
@@ -284,8 +141,6 @@ public:
TimingData timingData;
ItemReader reader{parameters, logger};
ProbesResolver probesResolver{parameters, evaluator, logger};
- ModuleProviderLoader moduleProviderLoader{parameters, reader, evaluator, probesResolver, logger};
- ModuleLoader moduleLoader{parameters, moduleProviderLoader, reader, evaluator, logger};
ModulePropertyMerger propertyMerger{parameters, evaluator, logger};
ModuleInstantiator moduleInstantiator{parameters, itemPool, propertyMerger, logger};
ProductItemMultiplexer multiplexer{parameters, evaluator, logger, [this](Item *productItem) {
@@ -293,20 +148,16 @@ public:
}};
GroupsHandler groupsHandler{parameters, moduleInstantiator, evaluator, logger};
LocalProfiles localProfiles{parameters, evaluator, logger};
+ DependenciesResolver dependenciesResolver{parameters, itemPool, evaluator, reader,
+ probesResolver, moduleInstantiator, logger};
FileTime lastResolveTime;
QVariantMap storedProfiles;
- Set<Item *> disabledItems;
Settings settings{parameters.settingsDirectory()};
Set<QString> projectNamesUsedInOverrides;
Set<QString> productNamesUsedInOverrides;
Set<QString> disabledProjects;
- Set<QString> erroneousProducts;
- std::multimap<QString, ProductContext *> productsByName;
-
- // For fast look-up when resolving Depends.productTypes.
- // The contract is that it contains fully handled, error-free, enabled products.
- std::multimap<FileTag, ProductContext *> productsByType;
+ TopLevelProjectContext topLevelProject;
Version qbsVersion;
Item *tempScopeItem = nullptr;
@@ -357,7 +208,7 @@ void ProjectTreeBuilder::setStoredProfiles(const QVariantMap &profiles)
void ProjectTreeBuilder::setStoredModuleProviderInfo(
const StoredModuleProviderInfo &moduleProviderInfo)
{
- d->moduleProviderLoader.setStoredModuleProviderInfo(moduleProviderInfo);
+ d->dependenciesResolver.setStoredModuleProviderInfo(moduleProviderInfo);
}
ProjectTreeBuilder::Result ProjectTreeBuilder::load()
@@ -370,15 +221,19 @@ ProjectTreeBuilder::Result ProjectTreeBuilder::load()
d->reader.setPool(&d->itemPool);
Result result;
- result.profileConfigs = d->storedProfiles;
+ d->topLevelProject.profileConfigs = d->storedProfiles;
result.root = d->loadTopLevelProjectItem();
- d->handleTopLevelProject(result, {QDir::cleanPath(d->parameters.projectFilePath())});
+ d->handleTopLevelProject(result.root, {QDir::cleanPath(d->parameters.projectFilePath())});
- result.qbsFiles = d->reader.filesRead() - d->moduleProviderLoader.tempQbsFiles();
+ result.qbsFiles = d->reader.filesRead() - d->dependenciesResolver.tempQbsFiles();
+ result.productInfos = d->topLevelProject.productInfos;
+ result.profileConfigs = d->topLevelProject.profileConfigs;
for (auto it = d->localProfiles.profiles().begin(); it != d->localProfiles.profiles().end();
++it) {
result.profileConfigs.remove(it.key());
}
+ result.projectProbes = d->topLevelProject.probes;
+ result.storedModuleProviderInfo = d->dependenciesResolver.storedModuleProviderInfo();
d->printProfilingInfo();
@@ -483,50 +338,43 @@ Item *ProjectTreeBuilder::Private::wrapInProjectIfNecessary(Item *item)
return prj;
}
-void ProjectTreeBuilder::Private::handleTopLevelProject(Result &loadResult,
+void ProjectTreeBuilder::Private::handleTopLevelProject(Item *projectItem,
const Set<QString> &referencedFilePaths)
{
- TopLevelProjectContext tlp;
- tlp.buildDirectory = TopLevelProject::deriveBuildDirectory(
+ topLevelProject.buildDirectory = TopLevelProject::deriveBuildDirectory(
parameters.buildRoot(),
TopLevelProject::deriveId(parameters.finalBuildConfigurationTree()));
- Item * const projectItem = loadResult.root;
projectItem->setProperty(StringConstants::sourceDirectoryProperty(),
VariantValue::create(QFileInfo(projectItem->file()->filePath())
.absolutePath()));
projectItem->setProperty(StringConstants::buildDirectoryProperty(),
- VariantValue::create(tlp.buildDirectory));
+ VariantValue::create(topLevelProject.buildDirectory));
projectItem->setProperty(StringConstants::profileProperty(),
VariantValue::create(parameters.topLevelProfile()));
- handleProject(&loadResult, &tlp, projectItem, referencedFilePaths);
- checkProjectNamesInOverrides(tlp);
- collectProductsByName(tlp);
+ handleProject(projectItem, referencedFilePaths);
+ checkProjectNamesInOverrides();
+ collectProductsByName();
checkProductNamesInOverrides();
- for (ProjectContext * const projectContext : qAsConst(tlp.projects)) {
+ for (ProjectContext * const projectContext : qAsConst(topLevelProject.projects)) {
for (ProductContext &productContext : projectContext->products)
- tlp.productsToHandle.emplace_back(&productContext, -1);
+ topLevelProject.productsToHandle.emplace_back(&productContext, -1);
}
- while (!tlp.productsToHandle.empty())
- handleNextProduct(tlp);
-
- loadResult.projectProbes = tlp.probes;
- loadResult.storedModuleProviderInfo = moduleProviderLoader.storedModuleProviderInfo();
+ while (!topLevelProject.productsToHandle.empty())
+ handleNextProduct();
reader.clearExtraSearchPathsStack();
AccumulatingTimer timer(parameters.logElapsedTime()
? &timingData.propertyChecking : nullptr);
- checkPropertyDeclarations(projectItem, disabledItems, parameters, logger);
+ checkPropertyDeclarations(projectItem, topLevelProject.disabledItems, parameters, logger);
}
-void ProjectTreeBuilder::Private::handleProject(
- Result *loadResult, TopLevelProjectContext *topLevelProjectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths)
+void ProjectTreeBuilder::Private::handleProject(Item *projectItem,
+ const Set<QString> &referencedFilePaths)
{
auto p = std::make_unique<ProjectContext>();
auto &projectContext = *p;
- projectContext.topLevelProject = topLevelProjectContext;
- projectContext.result = loadResult;
+ projectContext.topLevelProject = &topLevelProject;
ItemValuePtr itemValue = ItemValue::create(projectItem);
projectContext.scope = Item::create(&itemPool, ItemType::Scope);
projectContext.scope->setFile(projectItem->file());
@@ -536,7 +384,7 @@ void ProjectTreeBuilder::Private::handleProject(
dummyProductContext.moduleProperties = parameters.finalBuildConfigurationTree();
dummyProductContext.profileModuleProperties = dummyProductContext.moduleProperties;
dummyProductContext.profileName = parameters.topLevelProfile();
- loadBaseModule(dummyProductContext, projectItem);
+ dependenciesResolver.loadBaseModule(dummyProductContext, projectItem);
projectItem->overrideProperties(parameters.overriddenValuesTree(),
StringConstants::projectPrefix(), parameters, logger);
@@ -553,8 +401,8 @@ void ProjectTreeBuilder::Private::handleProject(
disabledProjects.insert(projectContext.name);
return;
}
- topLevelProjectContext->projects.push_back(p.release());
- SearchPathsManager searchPathsManager(reader, readExtraSearchPaths(projectItem)
+ topLevelProject.projects.push_back(p.release());
+ SearchPathsManager searchPathsManager(reader, reader.readExtraSearchPaths(projectItem, evaluator)
<< projectItem->file()->dirPath());
projectContext.searchPathsStack = reader.extraSearchPathsStack();
projectContext.item = projectItem;
@@ -603,7 +451,7 @@ void ProjectTreeBuilder::Private::handleProject(
break;
case ItemType::Project:
copyProperties(projectItem, child);
- handleProject(loadResult, topLevelProjectContext, child, referencedFilePaths);
+ handleProject(child, referencedFilePaths);
break;
default:
break;
@@ -633,7 +481,7 @@ void ProjectTreeBuilder::Private::handleProject(
break;
case ItemType::Project:
copyProperties(projectItem, subItem);
- handleProject(loadResult, topLevelProjectContext, subItem,
+ handleProject(subItem,
Set<QString>(referencedFilePaths) << subItem->file()->filePath());
break;
default:
@@ -670,19 +518,19 @@ void ProjectTreeBuilder::Private::prepareProduct(ProjectContext &projectContext,
QBS_CHECK(!productContext.profileName.isEmpty());
// Set up full module property map based on the profile.
- const auto it = projectContext.result->profileConfigs.constFind(productContext.profileName);
+ const auto it = topLevelProject.profileConfigs.constFind(productContext.profileName);
QVariantMap flatConfig;
- if (it == projectContext.result->profileConfigs.constEnd()) {
+ if (it == topLevelProject.profileConfigs.constEnd()) {
const Profile profile(productContext.profileName, &settings, localProfiles.profiles());
if (!profile.exists()) {
ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()),
productItem->location());
- handleProductError(error, productContext);
+ productContext.handleError(error);
return;
}
flatConfig = SetupProjectParameters::expandedBuildConfiguration(
profile, parameters.configurationName());
- projectContext.result->profileConfigs.insert(productContext.profileName, flatConfig);
+ topLevelProject.profileConfigs.insert(productContext.profileName, flatConfig);
} else {
flatConfig = it.value().toMap();
}
@@ -731,24 +579,24 @@ void ProjectTreeBuilder::Private::prepareProduct(ProjectContext &projectContext,
prepareProduct(projectContext, importer);
}
-void ProjectTreeBuilder::Private::handleNextProduct(TopLevelProjectContext &tlp)
+void ProjectTreeBuilder::Private::handleNextProduct()
{
- auto [product, queueSizeOnInsert] = tlp.productsToHandle.front();
- tlp.productsToHandle.pop_front();
+ auto [product, queueSizeOnInsert] = topLevelProject.productsToHandle.front();
+ topLevelProject.productsToHandle.pop_front();
// If the queue of in-progress products has shrunk since the last time we tried handling
// this product, there has been forward progress and we can allow a deferral.
const Deferral deferral = queueSizeOnInsert == -1
- || queueSizeOnInsert > int(tlp.productsToHandle.size())
+ || queueSizeOnInsert > int(topLevelProject.productsToHandle.size())
? Deferral::Allowed : Deferral::NotAllowed;
reader.setExtraSearchPathsStack(product->project->searchPathsStack);
try {
handleProduct(*product, deferral);
if (product->name.startsWith(StringConstants::shadowProductPrefix()))
- tlp.probes << product->info.probes;
+ topLevelProject.probes << product->info.probes;
} catch (const ErrorInfo &err) {
- handleProductError(err, *product);
+ product->handleError(err);
}
// The search paths stack can change during dependency resolution (due to module providers);
@@ -757,8 +605,10 @@ void ProjectTreeBuilder::Private::handleNextProduct(TopLevelProjectContext &tlp)
// If we encountered a dependency to an in-progress product or to a bulk dependency,
// we defer handling this product if it hasn't failed yet and there is still forward progress.
- if (!product->info.delayedError.hasError() && !product->resolveDependenciesState.empty())
- tlp.productsToHandle.emplace_back(product, int(tlp.productsToHandle.size()));
+ if (!product->info.delayedError.hasError() && !product->dependenciesResolved) {
+ topLevelProject.productsToHandle.emplace_back(
+ product, int(topLevelProject.productsToHandle.size()));
+ }
}
void ProjectTreeBuilder::Private::handleProduct(ProductContext &product, Deferral deferral)
@@ -769,14 +619,15 @@ void ProjectTreeBuilder::Private::handleProduct(ProductContext &product, Deferra
if (product.info.delayedError.hasError())
return;
- if (!resolveDependencies(product, deferral))
+ product.dependenciesResolved = dependenciesResolver.resolveDependencies(product, deferral);
+ if (!product.dependenciesResolved)
return;
// Run probes for modules and product.
for (const Item::Module &module : product.item->modules()) {
if (!module.item->isPresentModule())
continue;
- if (module.productInfo && disabledItems.contains(module.productInfo->item)) {
+ if (module.productInfo && topLevelProject.disabledItems.contains(module.productInfo->item)) {
createNonPresentModule(itemPool, module.name.toString(),
QLatin1String("module's exporting product is disabled"),
module.item);
@@ -863,7 +714,7 @@ void ProjectTreeBuilder::Private::handleProduct(ProductContext &product, Deferra
continue;
if (loadingItem->prototype() && loadingItem->prototype()->type() == ItemType::Export) {
QBS_CHECK(loadingItem->prototype()->parent()->type() == ItemType::Product);
- if (disabledItems.contains(loadingItem->prototype()->parent()))
+ if (topLevelProject.disabledItems.contains(loadingItem->prototype()->parent()))
continue;
}
hasPresentLoadingItem = true;
@@ -883,179 +734,21 @@ void ProjectTreeBuilder::Private::handleProduct(ProductContext &product, Deferra
propertyMerger.doFinalMerge(product.item);
const bool enabled = checkItemCondition(product.item);
- moduleLoader.checkDependencyParameterDeclarations(product.item, product.name);
+ dependenciesResolver.checkDependencyParameterDeclarations(product.item, product.name);
groupsHandler.setupGroups(product.item, product.scope);
product.info.modulePropertiesSetInGroups = groupsHandler.modulePropertiesSetInGroups();
- disabledItems.unite(groupsHandler.disabledGroups());
+ topLevelProject.disabledItems.unite(groupsHandler.disabledGroups());
// Collect the full list of fileTags, including the values contributed by modules.
if (!product.info.delayedError.hasError() && enabled
&& !product.name.startsWith(StringConstants::shadowProductPrefix())) {
for (const FileTag &tag : fileTags)
- productsByType.insert({tag, &product});
+ topLevelProject.productsByType.insert({tag, &product});
product.item->setProperty(StringConstants::typeProperty(),
VariantValue::create(sorted(fileTags.toStringList())));
}
- product.project->result->productInfos[product.item] = product.info;
-}
-
-bool ProjectTreeBuilder::Private::resolveDependencies(ProductContext &product, Deferral deferral)
-{
- AccumulatingTimer timer(parameters.logElapsedTime()
- ? &timingData.productDependencies : nullptr);
-
- // Initialize the state with the direct Depends items of the product item.
- // This branch is executed once per product, while the function might be entered
- // multiple times due to deferrals.
- if (product.resolveDependenciesState.empty()) {
- setSearchPathsForProduct(product);
- std::queue<Item *> topLevelDependsItems;
- for (Item * const child : product.item->children()) {
- if (child->type() == ItemType::Depends)
- topLevelDependsItems.push(child);
- }
- product.resolveDependenciesState.push_front({product.item, {}, topLevelDependsItems, });
- product.resolveDependenciesState.front().pendingResolvedDependencies.push(
- ProductContext::ResolvedAndMultiplexedDependsItem::makeBaseDependency());
- }
-
- SearchPathsManager searchPathsMgr(reader, product.searchPaths);
-
- while (!product.resolveDependenciesState.empty()) {
-fixme:
- auto &state = product.resolveDependenciesState.front();
- while (!state.pendingResolvedDependencies.empty()) {
- QBS_CHECK(!state.currentDependsItem);
- const auto dependency = state.pendingResolvedDependencies.front();
- try {
- const LoadModuleResult res = loadModule(product, state.loadingItem, dependency,
- deferral);
- switch (res.handleDependency) {
- case HandleDependency::Defer:
- QBS_CHECK(deferral == Deferral::Allowed);
- if (res.product)
- state.pendingResolvedDependencies.front().product = res.product;
- return false;
- case HandleDependency::Ignore:
- state.pendingResolvedDependencies.pop();
- continue;
- case HandleDependency::Use:
- if (dependency.name.toString() == StringConstants::qbsModule()) {
- state.pendingResolvedDependencies.pop();
- continue;
- }
- break;
- }
-
- QBS_CHECK(res.moduleItem);
- std::queue<Item *> moduleDependsItems;
- for (Item * const child : res.moduleItem->children()) {
- if (child->type() == ItemType::Depends)
- moduleDependsItems.push(child);
- }
-
- state.pendingResolvedDependencies.pop();
- product.resolveDependenciesState.push_front(
- {res.moduleItem, dependency, moduleDependsItems, {}, {},
- dependency.requiredGlobally || state.requiredByLoadingItem});
- product.resolveDependenciesState.front().pendingResolvedDependencies.push(
- ProductContext::ResolvedAndMultiplexedDependsItem::makeBaseDependency());
- break;
- } catch (const ErrorInfo &e) {
- if (dependency.name.toString() == StringConstants::qbsModule())
- throw e;
-
- if (!dependency.requiredLocally) {
- state.pendingResolvedDependencies.pop();
- continue;
- }
-
- // See QBS-1338 for why we do not abort handling the product.
- state.pendingResolvedDependencies.pop();
- Item::Modules &modules = product.item->modules();
- while (product.resolveDependenciesState.size() > 1) {
- const auto loadingItemModule = std::find_if(
- modules.begin(), modules.end(), [&](const Item::Module &m) {
- return m.item == product.resolveDependenciesState.front().loadingItem;
- });
- for (auto it = loadingItemModule; it != modules.end(); ++it) {
- createNonPresentModule(itemPool, it->name.toString(),
- QLatin1String("error in Depends chain"), it->item);
- }
- modules.erase(loadingItemModule, modules.end());
- product.resolveDependenciesState.pop_front();
- }
- handleProductError(e, product);
- goto fixme;
- }
- }
- if (&state != &product.resolveDependenciesState.front())
- continue;
-
- if (state.currentDependsItem) {
- QBS_CHECK(state.pendingResolvedDependencies.empty());
-
- // We postpone handling Depends.productTypes for as long as possible, because
- // the full type of a product becomes available only after its modules have been loaded.
- if (!state.currentDependsItem->productTypes.empty() && deferral == Deferral::Allowed)
- return false;
-
- state.pendingResolvedDependencies = multiplexDependency(product,
- *state.currentDependsItem);
- state.currentDependsItem.reset();
- continue;
- }
-
- while (!state.pendingDependsItems.empty()) {
- QBS_CHECK(!state.currentDependsItem);
- QBS_CHECK(state.pendingResolvedDependencies.empty());
- Item * const dependsItem = state.pendingDependsItems.front();
- state.pendingDependsItems.pop();
- adjustDependsItemForMultiplexing(product, dependsItem);
- state.currentDependsItem = resolveDependsItem(product, dependsItem);
- if (!state.currentDependsItem)
- continue;
- state.currentDependsItem->requiredGlobally = state.currentDependsItem->requiredLocally
- && state.loadingItemOrigin.requiredGlobally;
- goto fixme;
- }
-
- QBS_CHECK(!state.currentDependsItem);
- QBS_CHECK(state.pendingResolvedDependencies.empty());
- QBS_CHECK(state.pendingDependsItems.empty());
-
- // This ensures a sorted module list in the product (dependers after dependencies).
- if (product.resolveDependenciesState.size() > 1) {
- QBS_CHECK(state.loadingItem->type() == ItemType::ModuleInstance);
- Item::Modules &modules = product.item->modules();
- const auto loadingItemModule = std::find_if(modules.begin(), modules.end(),
- [&](const Item::Module &m) {
- return m.item == state.loadingItem;
- });
- QBS_CHECK(loadingItemModule != modules.end());
- const Item::Module tempModule = *loadingItemModule;
- modules.erase(loadingItemModule);
- modules.push_back(tempModule);
- }
- product.resolveDependenciesState.pop_front();
- }
- return true;
-}
-
-void ProjectTreeBuilder::Private::setSearchPathsForProduct(ProductContext &product)
-{
- QBS_CHECK(product.searchPaths.isEmpty());
-
- product.searchPaths = readExtraSearchPaths(product.item);
- Settings settings(parameters.settingsDirectory());
- const QStringList prefsSearchPaths = Preferences(&settings, product.profileModuleProperties)
- .searchPaths();
- const QStringList &currentSearchPaths = reader.allSearchPaths();
- for (const QString &p : prefsSearchPaths) {
- if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
- product.searchPaths << p;
- }
+ topLevelProject.productInfos[product.item] = product.info;
}
void ProjectTreeBuilder::Private::handleSubProject(
@@ -1104,8 +797,7 @@ void ProjectTreeBuilder::Private::handleSubProject(
Item::addChild(projectItem, loadedItem);
projectItem->setScope(projectContext.scope);
- handleProject(projectContext.result, projectContext.topLevelProject, loadedItem,
- Set<QString>(referencedFilePaths) << subProjectFilePath);
+ handleProject(loadedItem, Set<QString>(referencedFilePaths) << subProjectFilePath);
}
void ProjectTreeBuilder::Private::initProductProperties(const ProductContext &product)
@@ -1133,10 +825,7 @@ void ProjectTreeBuilder::Private::printProfilingInfo()
logger.qbsLog(LoggerInfo, true) << " "
<< Tr::tr("Handling products took %1.")
.arg(elapsedTimeString(timingData.handleProducts));
- logger.qbsLog(LoggerInfo, true) << " "
- << Tr::tr("Setting up product dependencies took %1.")
- .arg(elapsedTimeString(timingData.productDependencies));
- moduleLoader.printProfilingInfo(6);
+ dependenciesResolver.printProfilingInfo(4);
moduleInstantiator.printProfilingInfo(6);
propertyMerger.printProfilingInfo(6);
groupsHandler.printProfilingInfo(4);
@@ -1146,12 +835,12 @@ void ProjectTreeBuilder::Private::printProfilingInfo()
.arg(elapsedTimeString(timingData.propertyChecking));
}
-void ProjectTreeBuilder::Private::checkProjectNamesInOverrides(const TopLevelProjectContext &tlp)
+void ProjectTreeBuilder::Private::checkProjectNamesInOverrides()
{
for (const QString &projectNameInOverride : projectNamesUsedInOverrides) {
if (disabledProjects.contains(projectNameInOverride))
continue;
- if (!any_of(tlp.projects, [&projectNameInOverride](const ProjectContext *p) {
+ if (!any_of(topLevelProject.projects, [&projectNameInOverride](const ProjectContext *p) {
return p->name == projectNameInOverride; })) {
handlePropertyError(Tr::tr("Unknown project '%1' in property override.")
.arg(projectNameInOverride), parameters, logger);
@@ -1162,9 +851,9 @@ void ProjectTreeBuilder::Private::checkProjectNamesInOverrides(const TopLevelPro
void ProjectTreeBuilder::Private::checkProductNamesInOverrides()
{
for (const QString &productNameInOverride : productNamesUsedInOverrides) {
- if (erroneousProducts.contains(productNameInOverride))
+ if (topLevelProject.erroneousProducts.contains(productNameInOverride))
continue;
- if (!any_of(productsByName, [&productNameInOverride](
+ if (!any_of(topLevelProject.productsByName, [&productNameInOverride](
const std::pair<QString, ProductContext *> &elem) {
// In an override string such as "a.b.c:d, we cannot tell whether we have a product
// "a" and a module "b.c" or a product "a.b" and a module "c", so we need to take
@@ -1178,183 +867,19 @@ void ProjectTreeBuilder::Private::checkProductNamesInOverrides()
}
}
-void ProjectTreeBuilder::Private::collectProductsByName(
- const TopLevelProjectContext &topLevelProject)
+void ProjectTreeBuilder::Private::collectProductsByName()
{
for (ProjectContext * const project : topLevelProject.projects) {
for (ProductContext &product : project->products)
- productsByName.insert({product.name, &product});
- }
-}
-
-void ProjectTreeBuilder::Private::adjustDependsItemForMultiplexing(const ProductContext &product,
- Item *dependsItem)
-{
- const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty());
- const bool productIsMultiplexed = !product.multiplexConfigurationId.isEmpty();
- if (name == product.name) {
- QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator.
- return;
- }
- const auto productRange = productsByName.equal_range(name);
- if (productRange.first == productRange.second)
- return; // Dependency is a module. Nothing to adjust.
-
- bool profilesPropertyIsSet;
- const QStringList profiles = evaluator.stringListValue(
- dependsItem, StringConstants::profilesProperty(), &profilesPropertyIsSet);
-
- std::vector<const ProductContext *> multiplexedDependencies;
- bool hasNonMultiplexedDependency = false;
- for (auto it = productRange.first; it != productRange.second; ++it) {
- if (!it->second->multiplexConfigurationId.isEmpty())
- multiplexedDependencies.push_back(it->second);
- else
- hasNonMultiplexedDependency = true;
- }
- bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
-
- static const auto multiplexConfigurationIntersects = [](const QVariantMap &lhs,
- const QVariantMap &rhs) {
- QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty());
- for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) {
- const auto rhsProperty = rhs.find(lhsProperty.key());
- const bool isCommonProperty = rhsProperty != rhs.constEnd();
- if (isCommonProperty && lhsProperty.value() != rhsProperty.value())
- return false;
- }
- return true;
- };
-
- // These are the allowed cases:
- // (1) Normal dependency with no multiplexing whatsoever.
- // (2) Both product and dependency are multiplexed.
- // (2a) The profiles property is not set, we want to depend on the best
- // matching variant.
- // (2b) The profiles property is set, we want to depend on all variants
- // with a matching profile.
- // (3) The product is not multiplexed, but the dependency is.
- // (3a) The profiles property is not set, the dependency has an aggregator.
- // We want to depend on the aggregator.
- // (3b) The profiles property is not set, the dependency does not have an
- // aggregator. We want to depend on all the multiplexed variants.
- // (3c) The profiles property is set, we want to depend on all variants
- // with a matching profile regardless of whether an aggregator exists or not.
- // (4) The product is multiplexed, but the dependency is not. We don't have to adapt
- // any Depends items.
- // (5) The product is a "shadow product". In that case, we know which product
- // it should have a dependency on, and we make sure we depend on that.
-
- // (1) and (4)
- if (!hasMultiplexedDependencies)
- return;
-
- // (3a)
- if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet)
- return;
-
- QStringList multiplexIds;
- const ShadowProductInfo shadowProductInfo = getShadowProductInfo(product);
- const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name;
- const auto productMultiplexConfig
- = multiplexer.multiplexIdToVariantMap(product.multiplexConfigurationId);
-
- for (const ProductContext *dependency : multiplexedDependencies) {
- const bool depMatchesShadowProduct = isShadowProduct
- && dependency->item == product.item->parent();
- const QString depMultiplexId = dependency->multiplexConfigurationId;
- if (depMatchesShadowProduct) { // (5)
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- VariantValue::create(depMultiplexId));
- return;
- }
- if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a
- if (dependency->multiplexConfigurationId == product.multiplexConfigurationId) {
- const ValuePtr &multiplexId = product.item->property(
- StringConstants::multiplexConfigurationIdProperty());
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- multiplexId);
- return;
-
- }
- // Otherwise collect partial matches and decide later
- const auto dependencyMultiplexConfig
- = multiplexer.multiplexIdToVariantMap(dependency->multiplexConfigurationId);
-
- if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig))
- multiplexIds << dependency->multiplexConfigurationId;
- } else {
- // (2b), (3b) or (3c)
- const bool profileMatch = !profilesPropertyIsSet || profiles.empty()
- || profiles.contains(dependency->profileName);
- if (profileMatch)
- multiplexIds << depMultiplexId;
- }
- }
- if (multiplexIds.empty()) {
- const QString productName = ProductItemMultiplexer::fullProductDisplayName(
- product.name, product.multiplexConfigurationId);
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. "
- "There are no eligible multiplex candidates.").arg(productName,
- name),
- dependsItem->location());
+ topLevelProject.productsByName.insert({product.name, &product});
}
-
- // In case of (2a), at most 1 match is allowed
- if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) {
- const QString productName = ProductItemMultiplexer::fullProductDisplayName(
- product.name, product.multiplexConfigurationId);
- QStringList candidateNames;
- for (const auto &id : qAsConst(multiplexIds))
- candidateNames << ProductItemMultiplexer::fullProductDisplayName(name, id);
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. "
- "Eligible multiplex candidates: %3.").arg(
- productName, name, candidateNames.join(QLatin1String(", "))),
- dependsItem->location());
- }
-
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- VariantValue::create(multiplexIds));
-}
-
-ShadowProductInfo ProjectTreeBuilder::Private::getShadowProductInfo(
- const ProductContext &product) const
-{
- const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix());
- return std::make_pair(isShadowProduct, isShadowProduct
- ? product.name.mid(StringConstants::shadowProductPrefix().size())
- : QString());
-}
-
-void ProjectTreeBuilder::Private::handleProductError(const ErrorInfo &error,
- ProductContext &productContext)
-{
- const bool alreadyHadError = productContext.info.delayedError.hasError();
- if (!alreadyHadError) {
- productContext.info.delayedError.append(Tr::tr("Error while handling product '%1':")
- .arg(productContext.name),
- productContext.item->location());
- }
- if (error.isInternalError()) {
- if (alreadyHadError) {
- qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString()
- << "in product" << productContext.name;
- return;
- }
- }
- const auto errorItems = error.items();
- for (const ErrorItem &ei : errorItems)
- productContext.info.delayedError.append(ei.description(), ei.codeLocation());
- productContext.project->result->productInfos[productContext.item] = productContext.info;
- disabledItems << productContext.item;
- erroneousProducts.insert(productContext.name);
}
void ProjectTreeBuilder::Private::handleModuleSetupError(
ProductContext &product, const Item::Module &module, const ErrorInfo &error)
{
if (module.required) {
- handleProductError(error, product);
+ product.handleError(error);
} else {
qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString()
<< "found, but not usable in product" << product.name
@@ -1366,27 +891,7 @@ void ProjectTreeBuilder::Private::handleModuleSetupError(
bool ProjectTreeBuilder::Private::checkItemCondition(Item *item)
{
- if (evaluator.boolValue(item, StringConstants::conditionProperty()))
- return true;
- disabledItems += item;
- return false;
-}
-
-QStringList ProjectTreeBuilder::Private::readExtraSearchPaths(Item *item, bool *wasSet)
-{
- QStringList result;
- const QStringList paths = evaluator.stringListValue(
- item, StringConstants::qbsSearchPathsProperty(), wasSet);
- const JSSourceValueConstPtr prop = item->sourceProperty(
- StringConstants::qbsSearchPathsProperty());
-
- // 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()
- : parameters.projectFilePath());
- for (const QString &path : paths)
- result += FileInfo::resolvePath(basePath, path);
- return result;
+ return topLevelProject.checkItemCondition(item, evaluator);
}
QList<Item *> ProjectTreeBuilder::Private::multiplexProductItem(ProductContext &dummyContext,
@@ -1501,33 +1006,6 @@ void ProjectTreeBuilder::Private::copyProperties(const Item *sourceProject, Item
}
}
-static void mergeParameters(QVariantMap &dst, const QVariantMap &src)
-{
- for (auto it = src.begin(); it != src.end(); ++it) {
- if (it.value().userType() == QMetaType::QVariantMap) {
- QVariant &vdst = dst[it.key()];
- QVariantMap mdst = vdst.toMap();
- mergeParameters(mdst, it.value().toMap());
- vdst = mdst;
- } else {
- dst[it.key()] = it.value();
- }
- }
-}
-
-static void adjustParametersScopes(Item *item, Item *scope)
-{
- if (item->type() == ItemType::ModuleParameters) {
- item->setScope(scope);
- return;
- }
-
- for (const auto &value : item->properties()) {
- if (value->type() == Value::ItemValueType)
- adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope);
- }
-}
-
static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
{
if (value->type() == Value::ItemValueType) {
@@ -1662,516 +1140,6 @@ void ProjectTreeBuilder::Private::resolveProbes(ProductContext &product, Item *i
product.info.probes << probesResolver.resolveProbes({product.name, product.uniqueName()}, item);
}
-static void checkForModuleNamePrefixCollision(
- const Item *product, const ProductContext::ResolvedAndMultiplexedDependsItem &dependency)
-{
- if (!product)
- return;
-
- for (const Item::Module &m : product->modules()) {
- if (m.name.length() == dependency.name.length()
- || m.name.front() != dependency.name.front()) {
- continue;
- }
- QualifiedId shortName;
- QualifiedId longName;
- if (m.name < dependency.name) {
- shortName = m.name;
- longName = dependency.name;
- } else {
- shortName = dependency.name;
- longName = m.name;
- }
- 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()), dependency.location());
- }
-}
-
-// Note: This function is never called for regular loading of the base module into a product,
-// but only for the special cases of loading the dummy base module into a project
-// and temporarily providing a base module for product multiplexing.
-Item *ProjectTreeBuilder::Private::loadBaseModule(ProductContext &product, Item *item)
-{
- const QualifiedId baseModuleName(StringConstants::qbsModule());
- const auto baseDependency
- = ProductContext::ResolvedAndMultiplexedDependsItem::makeBaseDependency();
- Item * const moduleItem = loadModule(product, item, baseDependency, Deferral::NotAllowed)
- .moduleItem;
- if (Q_UNLIKELY(!moduleItem))
- throw ErrorInfo(Tr::tr("Cannot load base qbs module."));
- return moduleItem;
-}
-
-ProjectTreeBuilder::Private::LoadModuleResult
-ProjectTreeBuilder::Private::loadModule(ProductContext &product, Item *loadingItem,
- const ProductContext::ResolvedAndMultiplexedDependsItem &dependency,
- Deferral deferral)
-{
- qCDebug(lcModuleLoader) << "loadModule name:" << dependency.name.toString()
- << "id:" << dependency.id();
-
- QBS_CHECK(loadingItem);
-
- const auto findExistingModule = [&dependency](Item *item) -> std::pair<Item::Module *, Item *> {
- if (!item) // Happens if and only if called via loadBaseModule().
- return {};
- Item *moduleWithSameName = nullptr;
- for (Item::Module &m : item->modules()) {
- if (m.name != dependency.name)
- continue;
- if (!m.productInfo) {
- QBS_CHECK(!dependency.product);
- return {&m, m.item};
- }
- if ((dependency.profile.isEmpty() || (m.productInfo->profile == dependency.profile))
- && m.productInfo->multiplexId == dependency.multiplexId) {
- return {&m, m.item};
- }
- moduleWithSameName = m.item;
- }
- return {nullptr, moduleWithSameName};
- };
- ProductContext *productDep = nullptr;
- Item *moduleItem = nullptr;
- static const auto displayName = [](const ProductContext &p) {
- return ProductItemMultiplexer::fullProductDisplayName(p.name, p.multiplexConfigurationId);
- };
- const auto &[existingModule, moduleWithSameName] = findExistingModule(product.item);
- if (existingModule) {
- // Merge version range and required property. These will be checked again
- // after probes resolving.
- if (existingModule->name.toString() != StringConstants::qbsModule()) {
- moduleLoader.forwardParameterDeclarations(dependency.item, product.item->modules());
-
- // TODO: Use priorities like for property values. See QBS-1300.
- mergeParameters(existingModule->parameters, dependency.parameters);
-
- existingModule->versionRange.narrowDown(dependency.versionRange);
- existingModule->required |= dependency.requiredGlobally;
- if (int(product.resolveDependenciesState.size())
- > existingModule->maxDependsChainLength) {
- existingModule->maxDependsChainLength = product.resolveDependenciesState.size();
- }
- }
- QBS_CHECK(existingModule->item);
- moduleItem = existingModule->item;
- if (!contains(existingModule->loadingItems, loadingItem))
- existingModule->loadingItems.push_back(loadingItem);
- } else if (dependency.product) {
- // We have already done the look-up.
- productDep = dependency.product;
- } else {
- // First check if there's a matching product.
- const auto candidates = productsByName.equal_range(dependency.name.toString());
- for (auto it = candidates.first; it != candidates.second; ++it) {
- const auto candidate = it->second;
- if (candidate->multiplexConfigurationId != dependency.multiplexId)
- continue;
- if (!dependency.profile.isEmpty() && dependency.profile != candidate->profileName)
- continue;
- if (dependency.limitToSubProject && !haveSameSubProject(product, *candidate))
- continue;
- productDep = candidate;
- break;
- }
- if (!productDep) {
- // If we can tell that this is supposed to be a product dependency, we can skip
- // the module look-up.
- if (!dependency.profile.isEmpty() || !dependency.multiplexId.isEmpty()) {
- if (dependency.requiredGlobally) {
- if (!dependency.profile.isEmpty()) {
- throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
- "for the requested profile '%3'.")
- .arg(displayName(product), dependency.displayName(),
- dependency.profile),
- product.item->location());
- }
- throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
- .arg(displayName(product), dependency.displayName()),
- product.item->location());
- }
- } else {
- // No matching product found, look for a "real" module.
- const ModuleLoader::ProductContext loaderContext{
- product.item, product.project->item, product.name, product.uniqueName(),
- product.profileName, product.multiplexConfigurationId, product.moduleProperties,
- product.profileModuleProperties};
- const ModuleLoader::Result loaderResult = moduleLoader.searchAndLoadModuleFile(
- loaderContext, dependency.location(), dependency.name, dependency.fallbackMode,
- dependency.requiredGlobally);
- moduleItem = loaderResult.moduleItem;
- product.info.probes << loaderResult.providerProbes;
-
- if (moduleItem) {
- Item * const proto = moduleItem;
- moduleItem = moduleItem->clone();
- moduleItem->setPrototype(proto); // For parameter declarations.
- } else if (dependency.requiredGlobally) {
- throw ErrorInfo(Tr::tr("Dependency '%1' not found for product '%2'.")
- .arg(dependency.name.toString(), displayName(product)),
- dependency.location());
- }
- }
- }
- }
-
- if (productDep && dependency.checkProduct) {
- if (productDep == &product) {
- throw ErrorInfo(Tr::tr("Dependency '%1' refers to itself.")
- .arg(dependency.name.toString()),
- dependency.location());
- }
-
- if (any_of(product.project->topLevelProject->productsToHandle, [productDep](const auto &e) {
- return e.first == productDep;
- })) {
- if (deferral == Deferral::Allowed)
- return {nullptr, productDep, HandleDependency::Defer};
- ErrorInfo e;
- e.append(Tr::tr("Cyclic dependencies detected:"));
- e.append(Tr::tr("First product is '%1'.")
- .arg(displayName(product)), product.item->location());
- e.append(Tr::tr("Second product is '%1'.")
- .arg(displayName(*productDep)), productDep->item->location());
- e.append(Tr::tr("Requested here."), dependency.location());
- throw e;
- }
-
- // This covers both the case of user-disabled products and products with errors.
- // The latter are force-disabled in handleProductError().
- if (disabledItems.contains(productDep->item)) {
- if (dependency.requiredGlobally) {
- ErrorInfo e;
- e.append(Tr::tr("Product '%1' depends on '%2',")
- .arg(displayName(product), displayName(*productDep)),
- product.item->location());
- e.append(Tr::tr("but product '%1' is disabled.").arg(displayName(*productDep)),
- productDep->item->location());
- throw e;
- }
- productDep = nullptr;
- }
- }
-
- if (productDep) {
- QBS_CHECK(productDep->mergedExportItem);
- moduleItem = productDep->mergedExportItem->clone();
- moduleItem->setParent(nullptr);
-
- // Needed for isolated Export item evaluation.
- moduleItem->setPrototype(productDep->mergedExportItem);
- }
-
- if (moduleItem) {
- for (auto it = product.resolveDependenciesState.begin();
- it != product.resolveDependenciesState.end(); ++it) {
- Item *itemToCheck = moduleItem;
- if (it->loadingItem != itemToCheck) {
- if (!productDep)
- continue;
- itemToCheck = productDep->item;
- }
- if (it->loadingItem != itemToCheck)
- continue;
- ErrorInfo e;
- e.append(Tr::tr("Cyclic dependencies detected:"));
- while (true) {
- e.append(it->loadingItemOrigin.name.toString(),
- it->loadingItemOrigin.location());
- if (it->loadingItem->type() == ItemType::ModuleInstance) {
- createNonPresentModule(itemPool, it->loadingItemOrigin.name.toString(),
- QLatin1String("cyclic dependency"), it->loadingItem);
- }
- if (it == product.resolveDependenciesState.begin())
- break;
- --it;
- }
- e.append(dependency.name.toString(), dependency.location());
- throw e;
- }
- checkForModuleNamePrefixCollision(product.item, dependency);
- }
-
- // Can only happen with multiplexing.
- if (moduleWithSameName && moduleWithSameName != moduleItem)
- QBS_CHECK(productDep);
-
- QString loadingName;
- if (loadingItem == product.item) {
- loadingName = product.name;
- } else if (!product.resolveDependenciesState.empty()) {
- const auto &loadingItemOrigin = product.resolveDependenciesState.front().loadingItemOrigin;
- loadingName = loadingItemOrigin.name.toString() + loadingItemOrigin.multiplexId
- + loadingItemOrigin.profile;
- }
- moduleInstantiator.instantiate({
- product.item, product.name, loadingItem, loadingName, moduleItem, moduleWithSameName,
- productDep ? productDep->item : nullptr, product.scope, product.project->scope,
- dependency.name, dependency.id(), bool(existingModule)});
-
- // At this point, a null module item is only possible for a non-required dependency.
- // Note that we still needed to to the instantiation above, as that injects the module
- // name into the surrounding item for the ".present" check.
- if (!moduleItem) {
- QBS_CHECK(!dependency.requiredGlobally);
- return {nullptr, nullptr, HandleDependency::Ignore};
- }
-
- const auto createModule = [&] {
- Item::Module m;
- m.item = moduleItem;
- if (productDep) {
- m.productInfo.emplace(productDep->item, productDep->multiplexConfigurationId,
- productDep->profileName);
- }
- m.name = dependency.name;
- m.required = dependency.requiredLocally;
- m.versionRange = dependency.versionRange;
- return m;
- };
- const auto addLocalModule = [&] {
- if (loadingItem->type() == ItemType::ModuleInstance
- && !findExistingModule(loadingItem).first) {
- loadingItem->addModule(createModule());
- }
- };
-
- // The module has already been loaded, so we don't need to add it to the product's list of
- // modules, nor do we need to handle its dependencies. The only thing we might need to
- // do is to add it to the "local" list of modules of the loading item, if it isn't in there yet.
- if (existingModule) {
- addLocalModule();
- return {nullptr, nullptr, HandleDependency::Ignore};
- }
-
- qCDebug(lcModuleLoader) << "module loaded:" << dependency.name.toString();
- if (product.item) {
- Item::Module module = createModule();
-
- if (module.name.toString() != StringConstants::qbsModule()) {
- // TODO: Why do we have default parameters only for Export items and
- // property declarations only for modules? Does that make any sense?
- if (productDep)
- module.parameters = productDep->defaultParameters;
- mergeParameters(module.parameters, dependency.parameters);
- }
- module.required = dependency.requiredGlobally;
- module.loadingItems.push_back(loadingItem);
- module.maxDependsChainLength = product.resolveDependenciesState.size();
- product.item->addModule(module);
- addLocalModule();
- }
- return {moduleItem, nullptr, HandleDependency::Use};
-}
-
-bool ProjectTreeBuilder::Private::haveSameSubProject(const ProductContext &p1,
- const ProductContext &p2)
-{
- for (const Item *otherParent = p2.item->parent(); otherParent;
- otherParent = otherParent->parent()) {
- if (otherParent == p1.item->parent())
- return true;
- }
- return false;
-}
-
-static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties)
-{
- Item::PropertyMap result;
- for (auto it = properties.begin(); it != properties.end(); ++it) {
- if (it.value()->type() == Value::ItemValueType)
- result.insert(it.key(), it.value());
- }
- return result;
-}
-
-static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
-{
- QVariantMap result;
- handleJsProperties(ctx, v, [&](const JSAtom &prop, const JSPropertyDescriptor &desc) {
- const JSValue u = desc.value;
- if (JS_IsError(ctx, u))
- throw ErrorInfo(getJsString(ctx, u));
- const QString name = getJsString(ctx, prop);
- result[name] = (JS_IsObject(u) && !JS_IsArray(ctx, u) && !JS_IsRegExp(ctx, u))
- ? safeToVariant(ctx, u) : getJsVariant(ctx, u);
- });
- return result;
-}
-
-QVariantMap ProjectTreeBuilder::Private::extractParameters(Item *dependsItem) const
-{
- QVariantMap result;
- const Item::PropertyMap &itemProperties = filterItemProperties(dependsItem->properties());
- if (itemProperties.empty())
- return result;
- auto origProperties = dependsItem->properties();
-
- // TODO: This is not exception-safe. Also, can't we do the item value check along the
- // way, without allocationg an extra map and exchanging the list of children?
- dependsItem->setProperties(itemProperties);
-
- JSValue sv = evaluator.scriptValue(dependsItem);
- try {
- result = safeToVariant(evaluator.engine()->context(), sv);
- } catch (const ErrorInfo &exception) {
- auto ei = exception;
- ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location());
- throw ei;
- }
- dependsItem->setProperties(origProperties);
- return result;
-}
-
-std::optional<ProductContext::ResolvedDependsItem>
-ProjectTreeBuilder::Private::resolveDependsItem(const ProductContext &product, Item *dependsItem)
-{
- if (!checkItemCondition(dependsItem)) {
- qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
- return {};
- }
-
- const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty());
- if (name == StringConstants::qbsModule()) // Redundant
- return {};
-
- bool submodulesPropertySet;
- const QStringList submodules = evaluator.stringListValue(
- dependsItem, StringConstants::submodulesProperty(), &submodulesPropertySet);
- if (submodules.empty() && submodulesPropertySet) {
- qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list.";
- return {};
- }
- if (Q_UNLIKELY(submodules.size() > 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());
- }
- bool productTypesWasSet;
- const QStringList productTypes = evaluator.stringListValue(
- dependsItem, StringConstants::productTypesProperty(), &productTypesWasSet);
- if (!name.isEmpty() && !productTypes.isEmpty()) {
- throw ErrorInfo(Tr::tr("The name and productTypes are mutually exclusive "
- "in a Depends item"), dependsItem->location());
- }
- if (productTypes.isEmpty() && productTypesWasSet) {
- qCDebug(lcModuleLoader) << "Ignoring Depends item with empty productTypes list.";
- return {};
- }
- if (name.isEmpty() && !productTypesWasSet) {
- throw ErrorInfo(Tr::tr("Either name or productTypes must be set in a Depends item"),
- dependsItem->location());
- }
-
- const FallbackMode fallbackMode = parameters.fallbackProviderEnabled()
- && evaluator.boolValue(dependsItem, StringConstants::enableFallbackProperty())
- ? FallbackMode::Enabled : FallbackMode::Disabled;
-
- bool profilesPropertyWasSet = false;
- std::optional<QStringList> profiles;
- bool required = true;
- if (productTypes.isEmpty()) {
- const QStringList profileList = evaluator.stringListValue(
- dependsItem, StringConstants::profilesProperty(), &profilesPropertyWasSet);
- if (profilesPropertyWasSet)
- profiles.emplace(profileList);
- required = evaluator.boolValue(dependsItem, StringConstants::requiredProperty());
- }
- const Version minVersion = Version::fromString(
- evaluator.stringValue(dependsItem, StringConstants::versionAtLeastProperty()));
- const Version maxVersion = Version::fromString(
- evaluator.stringValue(dependsItem, StringConstants::versionBelowProperty()));
- const bool limitToSubProject = evaluator.boolValue(
- dependsItem, StringConstants::limitToSubProjectProperty());
- const QStringList multiplexIds = evaluator.stringListValue(
- dependsItem, StringConstants::multiplexConfigurationIdsProperty());
- adjustParametersScopes(dependsItem, dependsItem);
- moduleLoader.forwardParameterDeclarations(dependsItem, product.item->modules());
- const QVariantMap parameters = extractParameters(dependsItem);
-
- return ProductContext::ResolvedDependsItem{dependsItem, QualifiedId::fromString(name),
- submodules, FileTags::fromStringList(productTypes), multiplexIds, profiles,
- {minVersion, maxVersion}, parameters, limitToSubProject, fallbackMode, required};
-}
-
-// Potentially multiplexes a dependency along Depends.productTypes, Depends.subModules and
-// Depends.profiles, as well as internally set up multiplexing axes.
-// Each entry in the resulting queue corresponds to exactly one product or module to pull in.
-std::queue<ProductContext::ResolvedAndMultiplexedDependsItem>
-ProjectTreeBuilder::Private::multiplexDependency(
- const ProductContext &product, const ProductContext::ResolvedDependsItem &dependency)
-{
- std::queue<ProductContext::ResolvedAndMultiplexedDependsItem> dependencies;
- if (!dependency.productTypes.empty()) {
- std::vector<ProductContext *> matchingProducts;
- for (const FileTag &typeTag : dependency.productTypes) {
- const auto range = productsByType.equal_range(typeTag);
- for (auto it = range.first; it != range.second; ++it) {
- if (it->second != &product
- && it->second->name != product.name
- && (!dependency.limitToSubProject
- || haveSameSubProject(product, *it->second))) {
- matchingProducts.push_back(it->second);
- }
- }
- }
- if (matchingProducts.empty()) {
- qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything."
- << dependency.item->location();
- return {};
- }
- for (ProductContext * const match : matchingProducts)
- dependencies.emplace(match, dependency);
- return dependencies;
- }
-
- const QStringList profiles = dependency.profiles && !dependency.profiles->isEmpty()
- ? *dependency.profiles : QStringList(QString());
- const QStringList multiplexIds = !dependency.multiplexIds.isEmpty()
- ? dependency.multiplexIds : QStringList(QString());
- const QStringList subModules = !dependency.subModules.isEmpty()
- ? dependency.subModules : QStringList(QString());
- for (const QString &profile : profiles) {
- for (const QString &multiplexId : multiplexIds) {
- for (const QString &subModule : subModules) {
- QualifiedId name = dependency.name;
- if (!subModule.isEmpty())
- name << subModule.split(QLatin1Char('.'));
- dependencies.emplace(dependency, name, profile, multiplexId);
- }
- }
- }
- return dependencies;
-}
-
-QString ProductContext::uniqueName() const
-{
- return ResolvedProduct::uniqueName(name, multiplexConfigurationId);
-}
-
-QString ProductContext::ResolvedAndMultiplexedDependsItem::id() const
-{
- if (!item) {
- QBS_CHECK(name.toString() == StringConstants::qbsModule());
- return {};
- }
- return item->id();
-}
-
-CodeLocation ProductContext::ResolvedAndMultiplexedDependsItem::location() const
-{
- if (!item) {
- QBS_CHECK(name.toString() == StringConstants::qbsModule());
- return {};
- }
- return item->location();
-}
-
-QString ProductContext::ResolvedAndMultiplexedDependsItem::displayName() const
-{
- return ProductItemMultiplexer::fullProductDisplayName(name.toString(), multiplexId);
-}
-
ProjectTreeBuilder::Private::TempBaseModuleAttacher::TempBaseModuleAttacher(
ProjectTreeBuilder::Private *d, ProductContext &product)
: m_productItem(product.item)
@@ -2182,7 +1150,7 @@ ProjectTreeBuilder::Private::TempBaseModuleAttacher::TempBaseModuleAttacher(
if (qbsValue)
m_origQbsValue = qbsValue->clone();
- m_tempBaseModule = d->loadBaseModule(product, m_productItem);
+ m_tempBaseModule = d->dependenciesResolver.loadBaseModule(product, m_productItem);
}
void ProjectTreeBuilder::Private::TempBaseModuleAttacher::drop()
diff --git a/src/lib/corelib/loader/projecttreebuilder.h b/src/lib/corelib/loader/projecttreebuilder.h
index d70e9b2b0..a4f08a1f3 100644
--- a/src/lib/corelib/loader/projecttreebuilder.h
+++ b/src/lib/corelib/loader/projecttreebuilder.h
@@ -39,7 +39,7 @@
#pragma once
-#include "moduleproviderloader.h"
+#include "loaderutils.h"
#include <language/forward_decls.h>
#include <language/moduleproviderinfo.h>
@@ -58,8 +58,6 @@ class Item;
class ItemPool;
class ProgressObserver;
-using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>;
-
class ProjectTreeBuilder
{
public:
@@ -69,13 +67,6 @@ public:
struct Result
{
- struct ProductInfo
- {
- std::vector<ProbeConstPtr> probes;
- ModulePropertiesPerGroup modulePropertiesSetInGroups;
- ErrorInfo delayedError;
- };
-
Item *root = nullptr;
std::unordered_map<Item *, ProductInfo> productInfos;
std::vector<ProbeConstPtr> projectProbes;